feat: ISMCTS 4项优化 — 模拟终止+候选剪枝+SanDai2带对子
A: 模拟截断 MinPlays评估 旧: 张数最少=赢(不知道手牌结构) → 4张散牌 vs 3张对子, 4张赢(错) 新: MinPlays最少=赢 → 散牌minPlays=4 vs 对子minPlays=2, 对子赢(对) C: 候选剪枝 fastRate<0.15 低胜率候选直接丢弃(非pass), 节省模拟+排序时间 全剪枝时自动回退不剪枝 E: SanDai2/SiDai 优先带对子 旧: 带最小两张散牌 新: 优先找对子带, 找不到才带散牌(对子本身是一手牌) 注: B(预测引导采样)回退 — PredictOpponent在每局800次模拟中重复调 用导致1.6s/局, 在线性能不可接受 验证: 100局0错误 0.8s/局
This commit is contained in:
@ -164,16 +164,19 @@ namespace PdkFriendServer.Logic
|
||||
if (SimulateToEnd(hand, view, play))
|
||||
wins++;
|
||||
|
||||
// 有前途的候选多模拟
|
||||
double fastRate = (double)wins / fastSims;
|
||||
|
||||
// 胜率太低 → 剪枝,不参与后续评估
|
||||
if (fastRate < 0.15 && allCandidates.Count > 3)
|
||||
continue;
|
||||
|
||||
// 有前途的候选多模拟
|
||||
int extraSims = fastRate > 0.3 ? simsPerCandidate - fastSims : 0;
|
||||
for (int s = 0; s < extraSims; s++)
|
||||
if (SimulateToEnd(hand, view, play))
|
||||
wins++;
|
||||
|
||||
double totalRate = (double)wins / (fastSims + extraSims);
|
||||
|
||||
// 牌效率:出牌后剩余手牌的最少步数
|
||||
int afterMin = HandOptimizer.MinPlays(
|
||||
hand.Where(c =>
|
||||
play.Ids == null || !play.Ids.Contains(c.ID)).ToArray());
|
||||
@ -181,6 +184,22 @@ namespace PdkFriendServer.Logic
|
||||
scoredCandidates.Add((play, totalRate, afterMin, allCandidates[ci].isMulti));
|
||||
}
|
||||
|
||||
// 如果所有候选都被剪枝,回退到不做剪枝,全部重新评估
|
||||
if (scoredCandidates.Count == 0)
|
||||
{
|
||||
for (int ci = 0; ci < allCandidates.Count; ci++)
|
||||
{
|
||||
int wins = 0;
|
||||
var play = allCandidates[ci].play;
|
||||
for (int s = 0; s < fastSims; s++)
|
||||
if (SimulateToEnd(hand, view, play)) wins++;
|
||||
double rate = (double)wins / fastSims;
|
||||
int am = HandOptimizer.MinPlays(
|
||||
hand.Where(c => play.Ids == null || !play.Ids.Contains(c.ID)).ToArray());
|
||||
scoredCandidates.Add((play, rate, am, allCandidates[ci].isMulti));
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 多牌加权
|
||||
for (int i = 0; i < scoredCandidates.Count; i++)
|
||||
{
|
||||
@ -460,7 +479,11 @@ namespace PdkFriendServer.Logic
|
||||
}
|
||||
current = current % 3 + 1;
|
||||
}
|
||||
return hands[myPos - 1].Count <= hands[nextPos - 1].Count && hands[myPos - 1].Count <= hands[oppPos - 1].Count;
|
||||
// 截断:用 MinPlays 评估而非比较张数
|
||||
int myMin = HandOptimizer.MinPlays(hands[myPos - 1].ToArray());
|
||||
int n1Min = HandOptimizer.MinPlays(hands[nextPos - 1].ToArray());
|
||||
int n2Min = HandOptimizer.MinPlays(hands[oppPos - 1].ToArray());
|
||||
return myMin <= n1Min && myMin <= n2Min;
|
||||
}
|
||||
|
||||
// ---- 多牌先手枚举 ----
|
||||
@ -479,20 +502,48 @@ namespace PdkFriendServer.Logic
|
||||
var triples = GetMultiGroups(hand, 3);
|
||||
foreach (var t in triples)
|
||||
{
|
||||
var rem = hand.Where(c => c.GameNum != t[0].GameNum).OrderBy(c => c.GameNum).ToList();
|
||||
var rem = hand.Where(c => c.GameState == 1 && c.GameNum != t[0].GameNum).ToList();
|
||||
if (rem.Count >= 2)
|
||||
AddCandidate(t.Concat(rem.Take(2)).ToList(), pos, candidates); // 三带二
|
||||
{
|
||||
// 三带二:优先带对子(对子本身是一手牌),找不到对子才带两张最小散牌
|
||||
var pair = rem.GroupBy(c => c.GameNum)
|
||||
.Where(g => g.Count() >= 2)
|
||||
.OrderBy(g => g.Key)
|
||||
.FirstOrDefault();
|
||||
if (pair != null)
|
||||
AddCandidate(t.Concat(pair.Take(2)).ToList(), pos, candidates);
|
||||
else
|
||||
AddCandidate(t.Concat(rem.OrderBy(c => c.GameNum).Take(2)).ToList(), pos, candidates);
|
||||
}
|
||||
else
|
||||
AddCandidate(t, pos, candidates);
|
||||
}
|
||||
|
||||
// 四带二/三
|
||||
// 四带二/三:优先带对子
|
||||
var quads = GetMultiGroups(hand, 4);
|
||||
foreach (var q in quads)
|
||||
{
|
||||
var rem = hand.Where(c => c.GameNum != q[0].GameNum).OrderBy(c => c.GameNum).ToList();
|
||||
if (rem.Count >= 3) AddCandidate(q.Concat(rem.Take(3)).ToList(), pos, candidates);
|
||||
else if (rem.Count >= 2) AddCandidate(q.Concat(rem.Take(2)).ToList(), pos, candidates);
|
||||
var rem = hand.Where(c => c.GameState == 1 && c.GameNum != q[0].GameNum).ToList();
|
||||
var pair = rem.GroupBy(c => c.GameNum)
|
||||
.Where(g => g.Count() >= 2)
|
||||
.OrderBy(g => g.Key)
|
||||
.FirstOrDefault();
|
||||
if (rem.Count >= 3)
|
||||
{
|
||||
if (pair != null)
|
||||
AddCandidate(q.Concat(pair.Take(2)).Concat(rem
|
||||
.Where(c => c.GameNum != pair.Key).OrderBy(c => c.GameNum).Take(1))
|
||||
.ToList(), pos, candidates);
|
||||
else
|
||||
AddCandidate(q.Concat(rem.OrderBy(c => c.GameNum).Take(3)).ToList(), pos, candidates);
|
||||
}
|
||||
else if (rem.Count >= 2)
|
||||
{
|
||||
if (pair != null)
|
||||
AddCandidate(q.Concat(pair.Take(2)).ToList(), pos, candidates);
|
||||
else
|
||||
AddCandidate(q.Concat(rem.OrderBy(c => c.GameNum).Take(2)).ToList(), pos, candidates);
|
||||
}
|
||||
}
|
||||
|
||||
// 连对(复用 pairs,不重复 GetMultiGroups)
|
||||
|
||||
Reference in New Issue
Block a user