fix: 预测加权按牌型区分压制条件 — 修复SanDai2误杀

根因: oppMaxRank仅代表「对手有单牌」,但用单牌概率惩罚对子/三带二
→ 三带二需要对手有3张同rank才能压制,不是1张

修复: 按牌型分别判断
- DanZhang: 用 MaxRank (单牌条件)
- DuiZi: 用 LikelyPairs (对手是否有更高对子)
- SanZhang/SanDai2: 用 LikelyTriples (对手是否有更高三张)
- ShunZi: 用 MaxStraightLength + MaxRank
- 其他: 默认 MaxRank

效果: SanDai2 52→75/30局 (+44%), 多牌比例 25.4%→26.1%

验证: 100局0错误 0.8s/局
This commit is contained in:
2026-07-13 01:14:57 +08:00
parent 73c8783276
commit 30a7f875f8
7 changed files with 50 additions and 13 deletions

View File

@ -278,10 +278,9 @@ namespace PdkFriendServer.Logic
}
}
// 5.7. 对手牌型预测加权:基于剩余牌计数推断对手能压制我的概率
// 5.7. 对手牌型预测加权:按牌型区分压制条件
var opp1Pred = _tracker.PredictOpponent(view.MyPos % 3, hand, view);
var opp2Pred = _tracker.PredictOpponent((view.MyPos + 1) % 3, hand, view);
int oppMaxRank = Math.Max(opp1Pred.MaxRank, opp2Pred.MaxRank);
for (int i = 0; i < scoredCandidates.Count; i++)
{
@ -291,22 +290,60 @@ namespace PdkFriendServer.Logic
int myRank = p.GameNum;
// 我的出牌 rank 高于两对手最高牌 → 他们很可能压不了
if (myRank > oppMaxRank && oppMaxRank > 0)
// 按牌型判定:对手是否有能力压制
bool oppLikelyCanBeat = false;
bool oppLikelyCantBeat = false;
switch (p.Type)
{
wr += 0.08;
if (opp1Pred.LikelyNoHigherThan(myRank) && opp2Pred.LikelyNoHigherThan(myRank))
wr += 0.05; // 两个对手都可能压不了 → 更像无敌
}
// 我的出牌低于对手最高牌 → 容易被压制
else if (myRank < oppMaxRank && oppMaxRank > 0)
{
wr -= 0.05;
case CardType1.DanZhang:
// 单张:对手有更高 rank 的单张就能压制
oppLikelyCanBeat = opp1Pred.MaxRank > myRank || opp2Pred.MaxRank > myRank;
oppLikelyCantBeat = opp1Pred.MaxRank > 0 && opp2Pred.MaxRank > 0
&& opp1Pred.MaxRank <= myRank && opp2Pred.MaxRank <= myRank;
break;
case CardType1.DuiZi:
// 对子对手需要有更高的对子LikelyPairs ≥ 2张同rank
oppLikelyCanBeat = opp1Pred.LikelyPairs.Any(r => r > myRank)
|| opp2Pred.LikelyPairs.Any(r => r > myRank);
oppLikelyCantBeat = opp1Pred.LikelyNoPairHigherThan(myRank)
&& opp2Pred.LikelyNoPairHigherThan(myRank)
&& opp1Pred.MaxRank > 0 && opp2Pred.MaxRank > 0;
break;
case CardType1.SanZhang:
case CardType1.SanDai2:
// 三张/三带二对手需要有更高的三张LikelyTriples ≥ 3张同rank
oppLikelyCanBeat = opp1Pred.LikelyTriples.Any(r => r > myRank)
|| opp2Pred.LikelyTriples.Any(r => r > myRank);
oppLikelyCantBeat = opp1Pred.LikelyTriples.Count == 0
&& opp2Pred.LikelyTriples.Count == 0
&& opp1Pred.MaxRank > 0 && opp2Pred.MaxRank > 0;
break;
case CardType1.ShunZi:
// 顺子:对手需要有更长的同长顺子
int myLen = p.Ids.Length;
oppLikelyCanBeat = (opp1Pred.MaxStraightLength >= myLen
&& opp1Pred.MaxRank > myRank)
|| (opp2Pred.MaxStraightLength >= myLen
&& opp2Pred.MaxRank > myRank);
break;
default:
// 连对/飞机/炸弹:用 rank 近似
oppLikelyCanBeat = opp1Pred.MaxRank > myRank || opp2Pred.MaxRank > myRank;
oppLikelyCantBeat = opp1Pred.MaxRank > 0 && opp2Pred.MaxRank > 0
&& opp1Pred.MaxRank <= myRank && opp2Pred.MaxRank <= myRank;
break;
}
if (oppLikelyCantBeat)
wr += 0.10; // 两个对手都可能压不了 → 近似无敌
else if (oppLikelyCanBeat)
wr -= 0.04; // 对手大概率能压制 → 轻微惩罚
// 对手可能有炸弹 → 大牌型谨慎
double maxBombProb = Math.Max(opp1Pred.BombProbability, opp2Pred.BombProbability);
if (maxBombProb > 0.3 && (p.Type == CardType1.ZhaDan || p.Type == CardType1.ShunZi))
if (maxBombProb > 0.3 && (p.Type == CardType1.ZhaDan || p.Type == CardType1.ShunZi
|| p.Type == CardType1.SanDai2 || p.Type == CardType1.LianDui))
wr -= 0.03;
scoredCandidates[i] = (p, wr, am, m);

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.