fix: FastSim清牌效率优先 — 领牌出多牌+最小rank, 跟牌出最小压制

核心改动: 从'出最大的看谁能压'改为'出最省的清牌快'

领牌(新轮):
- 三张 > 对子 > 单张 (多牌优先)
- 始终用最小rank (保留大牌给跟牌阶段)

跟牌:
- 三张/对子: 找最小rank能压制的同类型
- 单张: 出最小的压制牌(保留大牌)

效果: 多牌29%→34% (+5%), 单张-4.4%, 0.7s/局
vs IsmctsBot: 34% vs 53%, 差距来自straight/飞机等复杂牌型在rollout中缺失
This commit is contained in:
2026-07-12 23:41:50 +08:00
parent 8b30caff1a
commit 6a267738df
7 changed files with 42 additions and 21 deletions

View File

@ -190,7 +190,9 @@ namespace PdkFriendServer.Logic
return FastSim(o1g, o2g, mg, current, myGameNum, CardType1.DanZhang, myPos - 1, 0); return FastSim(o1g, o2g, mg, current, myGameNum, CardType1.DanZhang, myPos - 1, 0);
} }
// 快速模拟核心:全部用 byte[]。支持单张+对子 // 快速模拟核心:清牌效率优先
// 领牌:优先出多牌组合(3张>2张>1张),用最小的 rank 保留大牌
// 跟牌:出最小的能压制的牌,保留大牌
private bool FastSim(List<byte> h1, List<byte> h2, List<byte> my, private bool FastSim(List<byte> h1, List<byte> h2, List<byte> my,
int current, byte maxGameNum, CardType1 maxType, int maxPlayer, int depth) int current, byte maxGameNum, CardType1 maxType, int maxPlayer, int depth)
{ {
@ -201,44 +203,63 @@ namespace PdkFriendServer.Logic
var cur = current == 0 ? h1 : current == 1 ? h2 : my; var cur = current == 0 ? h1 : current == 1 ? h2 : my;
bool isNewRound = maxPlayer == current || maxGameNum == 0; bool isNewRound = maxPlayer == current || maxGameNum == 0;
bool isMe = (current == 2);
if (isNewRound) if (isNewRound)
{ {
if (cur.Count == 0) return false; if (cur.Count == 0) return false;
// 找对子(贪心:出最大的对子)
var groups = cur.GroupBy(g => g).Where(g => g.Count() >= 2).OrderByDescending(g => g.Key).ToList(); // 清牌效率排序: 三张 > 对子 > 单张。始终用最小的 rank
if (groups.Count > 0) var groups = cur.GroupBy(g => g).OrderBy(g => g.Key).ToList();
// 三张(清 3 张)
var triples = groups.Where(g => g.Count() >= 3).ToList();
if (triples.Count > 0)
{ {
var pair = groups[0]; var t = triples[0];
var toRemove = pair.Take(2).ToList(); var toRemove = t.Take(3).ToList();
foreach (var g in toRemove) cur.Remove(g); foreach (var g in toRemove) cur.Remove(g);
return FastSim(h1, h2, my, (current + 1) % 3, pair.Key, CardType1.DuiZi, current, depth + 1); return FastSim(h1, h2, my, (current + 1) % 3, t.Key, CardType1.SanZhang, current, depth + 1);
} }
// 单张
var sorted = cur.OrderByDescending(g => g).ToList(); // 对子(清 2 张)
byte play = sorted[0]; var pairs = groups.Where(g => g.Count() >= 2).ToList();
if (pairs.Count > 0)
{
var p = pairs[0];
var toRemove = p.Take(2).ToList();
foreach (var g in toRemove) cur.Remove(g);
return FastSim(h1, h2, my, (current + 1) % 3, p.Key, CardType1.DuiZi, current, depth + 1);
}
// 单张:出最小的
byte play = groups[0].Key;
cur.Remove(play); cur.Remove(play);
return FastSim(h1, h2, my, (current + 1) % 3, play, CardType1.DanZhang, current, depth + 1); return FastSim(h1, h2, my, (current + 1) % 3, play, CardType1.DanZhang, current, depth + 1);
} }
else else
{ {
// 跟牌:找能压制的同类型 // 跟牌:有牌能压 → 出最小的压制
if (maxType == CardType1.DuiZi || maxType == CardType1.SanZhang)
{
int need = maxType == CardType1.SanZhang ? 3 : 2;
var groups = cur.GroupBy(g => g)
.Where(g => g.Count() >= need && g.Key > maxGameNum)
.OrderBy(g => g.Key).ToList();
if (groups.Count > 0)
{
var g = groups[0];
var toRemove = g.Take(need).ToList();
foreach (var x in toRemove) cur.Remove(x);
return FastSim(h1, h2, my, (current + 1) % 3, g.Key, maxType, current, depth + 1);
}
}
// 单张跟牌:出最小的可压牌
var bigger = cur.Where(g => g > maxGameNum).OrderBy(g => g).ToList(); var bigger = cur.Where(g => g > maxGameNum).OrderBy(g => g).ToList();
if (maxType == CardType1.DuiZi)
{
// 对子:找 ≥2 张且大于 maxGameNum
var pairGroups = cur.GroupBy(g => g).Where(g => g.Count() >= 2 && g.Key > maxGameNum).OrderBy(g => g.Key).ToList();
if (pairGroups.Count > 0)
{
var pair = pairGroups[0];
var toRemove = pair.Take(2).ToList();
foreach (var g in toRemove) cur.Remove(g);
return FastSim(h1, h2, my, (current + 1) % 3, pair.Key, CardType1.DuiZi, current, depth + 1);
}
}
if (bigger.Count > 0) if (bigger.Count > 0)
{ {
byte play = bigger[0]; byte play = bigger[0]; // 最小压制
cur.Remove(play); cur.Remove(play);
return FastSim(h1, h2, my, (current + 1) % 3, play, CardType1.DanZhang, current, depth + 1); return FastSim(h1, h2, my, (current + 1) % 3, play, CardType1.DanZhang, current, depth + 1);
} }

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.