feat: 确定性牌计数推理v2 — AbsoluteHandStrength + 包庄否决 + 混合策略修正

CardTracker:
- 新增 AbsoluteHandStrength: 综合评估手牌实力(rank控制/绝对最高牌/炸弹/效率/已知率)
- 用于包庄决策的安全检查

IsmctsBot:
- EstimateWinProbability: 混合策略回退为纯ISMCTS + 确定性否决
  (混合加成导致虚假信心, v3验证0%胜率)
- 策略: detStrength<0.15 → 直接否决不包, 否则纯ISMCTS
- 阈值保持 v2 水平: minPlays≤3→0.50,≤4→0.60,≤5→0.65

验证: 100局0错误 0.8s/局 ENDGAME=983次 LOCK=3次
This commit is contained in:
2026-07-13 00:59:48 +08:00
parent 5cc2ae8bd0
commit 1b1404f38e
8 changed files with 62 additions and 0 deletions

View File

@ -242,6 +242,63 @@ namespace PdkFriendServer.Logic
return false;
}
/// <summary>
/// 确定性手牌强度基于绝对牌计数评估手牌实力0.0-1.0。
/// 核心思想:我的手牌 + 已见牌 = 对手的牌池完全确定,
/// 可以精确计算哪些 rank 已被我控制、哪些出牌无敌。
/// 用于修正 ISMCTS 模拟噪声。
/// </summary>
public double AbsoluteHandStrength(TCardInfoPdkF[] myHand)
{
if (myHand.Length == 0) return 1.0;
double score = 0.0;
// 1. Rank 绝对控制率:我持有该 rank 所有剩余牌的百分比
int controlledRanks = 0;
int[] ranks = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16 };
foreach (int r in ranks)
{
int remaining = RemainingCount(r, myHand);
int myCount = myHand.Count(c => c.GameState == 1 && c.GameNum == r);
if (remaining == 0 && myCount > 0)
{
// 我持有该 rank 所有剩余牌 → 绝对控制
controlledRanks++;
if (myCount >= 3) controlledRanks++; // 三张以上价值更高
}
}
score += controlledRanks * 0.04; // 每个控制 rank +4%
// 2. 绝对最高牌:持有 ♠2 或所有2
int myTwos = myHand.Count(c => c.GameState == 1 && c.GameNum == 16);
int remainingTwos = RemainingCount(16, myHand);
if (remainingTwos == 0 && myTwos > 0)
{
score += myTwos * 0.06; // 每张独占 2 +6%
}
// 3. 持有的炸弹数
var byRank = myHand.Where(c => c.GameState == 1)
.GroupBy(c => c.GameNum)
.Where(g => g.Count() >= 4);
int myBombs = byRank.Count();
score += myBombs * 0.08; // 每个炸弹 +8%
// 4. 手牌效率(通过 HandOptimizer 间接)
int minPlays = HandOptimizer.MinPlays(myHand);
int handSize = myHand.Count(c => c.GameState == 1);
double efficiency = (double)(handSize - minPlays)
/ Math.Max(handSize, 1);
score += efficiency * 0.15; // 效率贡献最多 15%
// 5. 已知牌越多 → 确定性权重越高
double seenRatio = _seenCardIds.Count / (double)Math.Max(48 - handSize, 1);
score += seenRatio * 0.10; // 已知牌占比 +10%
return Math.Min(score, 1.0);
}
/// <summary>在外面可能存在的炸弹数(按 rank 计)</summary>
public int BombsOutside(TCardInfoPdkF[] myHand)
{

View File

@ -68,6 +68,11 @@ namespace PdkFriendServer.Logic
private double EstimateWinProbability(PdkBotView view)
{
// 确定性安全检查:手牌极弱 → 直接否定包庄
double detStrength = _tracker.AbsoluteHandStrength(view.MyHand);
if (detStrength < 0.15) return 0.0;
// ISMCTS 模拟(确定性只做否决,不混入加成以避免虚假信心)
int sims = Math.Min(_simulations, 600);
int wins = 0;
for (int s = 0; s < sims; s++)