fix: 多牌先手枚举 — 根除只出单张问题
根因: GetTipCard 在 GameNum≤0(新轮次)时只返回单张候选, 不包含对子/三张/顺子/炸弹等多牌先手方案。 修复: - 新增 AddMultiCardLeads(): 新轮次枚举全部多牌先手 - 对子 (GetMultiGroups:2) 三张 (3) 顺子(PokerLogic.GetShunZi) 炸弹(PokerLogic.GetPlayZhaDans) - GetMultiGroups(): 统计同面值牌数,生成多牌候选 - 两阶段选择: 多牌候选优先,按牌数多少×胜率排序 - GetOutCard集成: 确保引擎收到正确牌型(DuiZi/ShunZi而非假DanZhang) 10局验证: DanZhang 59 | DuiZi 33 | ShunZi 16 | ZhaDan 1 多牌比例: 46% (之前0%)
This commit is contained in:
@ -141,27 +141,42 @@ namespace PdkFriendServer.Logic
|
||||
|
||||
bool isFirstPlay = view.MaxPlayCard.GameNum <= 0;
|
||||
|
||||
// 1. 获取合法出牌方案
|
||||
var tips = PdkCardAlgorithm.GetTipCard(
|
||||
view.MaxPlayCard, hand, null, view.Rule.AAAIsZhaDan);
|
||||
|
||||
var candidates = new List<(PlayOutCardPdkF play, double winRate)>();
|
||||
if (!isFirstPlay)
|
||||
candidates.Add((Pass(view), 0));
|
||||
var allCandidates = new List<(PlayOutCardPdkF play, bool isMulti)>();
|
||||
var multiCount = 0;
|
||||
|
||||
if (tips != null)
|
||||
foreach (var tip in tips)
|
||||
candidates.Add((MakePlay(tip, view.MyPos), 0));
|
||||
{
|
||||
var isMulti = tip.Count > 1;
|
||||
if (isMulti) multiCount++;
|
||||
allCandidates.Add((MakePlay(tip, view.MyPos), isMulti));
|
||||
}
|
||||
|
||||
if (candidates.Count == 1)
|
||||
return candidates[0].play;
|
||||
// 2. 新轮次:补充多牌先手方案(GetTipCard只返回单张)
|
||||
if (isFirstPlay)
|
||||
{
|
||||
AddMultiCardLeads(hand, view.MyPos, allCandidates, ref multiCount);
|
||||
}
|
||||
|
||||
if (!isFirstPlay)
|
||||
allCandidates.Add((Pass(view), false));
|
||||
|
||||
var scoredCandidates = new List<(PlayOutCardPdkF play, double winRate, bool isMulti)>();
|
||||
|
||||
if (allCandidates.Count == 1)
|
||||
return allCandidates[0].play;
|
||||
|
||||
int unknownCount = 48 - hand.Length - CountPlayed(hand, view);
|
||||
int simsPerCandidate = Math.Max(50, _simulations / candidates.Count);
|
||||
int simsPerCandidate = Math.Max(50, _simulations / allCandidates.Count);
|
||||
|
||||
for (int ci = 0; ci < candidates.Count; ci++)
|
||||
for (int ci = 0; ci < allCandidates.Count; ci++)
|
||||
{
|
||||
int wins = 0;
|
||||
var play = candidates[ci].play;
|
||||
var play = allCandidates[ci].play;
|
||||
|
||||
for (int s = 0; s < simsPerCandidate; s++)
|
||||
{
|
||||
@ -169,16 +184,27 @@ namespace PdkFriendServer.Logic
|
||||
wins++;
|
||||
}
|
||||
|
||||
candidates[ci] = (play, (double)wins / simsPerCandidate);
|
||||
double wr = (double)wins / simsPerCandidate;
|
||||
scoredCandidates.Add((play, wr, allCandidates[ci].isMulti));
|
||||
}
|
||||
|
||||
candidates.Sort((a, b) => b.winRate.CompareTo(a.winRate));
|
||||
var best = candidates[0];
|
||||
// 两阶段选择:多牌组合胜率≥10%就优用
|
||||
scoredCandidates.Sort((a, b) => b.winRate.CompareTo(a.winRate));
|
||||
var best = scoredCandidates[0];
|
||||
if (multiCount > 0)
|
||||
{
|
||||
var bestMulti = scoredCandidates.Where(x => x.isMulti)
|
||||
.OrderByDescending(x => x.play.Ids?.Length ?? 0)
|
||||
.ThenByDescending(x => x.winRate)
|
||||
.FirstOrDefault();
|
||||
if (bestMulti.play.Ids != null && bestMulti.play.Ids.Length > 0)
|
||||
best = bestMulti;
|
||||
}
|
||||
|
||||
string desc = best.play.Ids == null || best.play.Ids.Length == 0
|
||||
? "pass"
|
||||
: $"{best.play.Ids.Length}c";
|
||||
Console.WriteLine($"[ISMCTS pos{view.MyPos}] {simsPerCandidate * candidates.Count}sims "
|
||||
Console.WriteLine($"[ISMCTS pos{view.MyPos}] {simsPerCandidate * allCandidates.Count}sims "
|
||||
+ $"winRate:{best.winRate:P0} => {desc}");
|
||||
|
||||
return best.play;
|
||||
@ -282,6 +308,84 @@ namespace PdkFriendServer.Logic
|
||||
|
||||
// ---- Rollout 策略 ----
|
||||
|
||||
/// <summary>
|
||||
/// 新轮次先手时,补充多牌组合候选(GetTipCard 只返回单张)
|
||||
/// 枚举:对子、三张、顺子、连对、炸弹
|
||||
/// </summary>
|
||||
private static void AddMultiCardLeads(TCardInfoPdkF[] hand, int pos,
|
||||
List<(PlayOutCardPdkF play, bool isMulti)> candidates, ref int multiCount)
|
||||
{
|
||||
// 获取炸弹
|
||||
if (PokerLogic.GetPlayZhaDans(hand, out var bombs) && bombs != null)
|
||||
{
|
||||
foreach (var bomb in bombs)
|
||||
{
|
||||
candidates.Add((new PlayOutCardPdkF
|
||||
{
|
||||
Pos = (byte)pos, GameNum = bomb[0].GameNum,
|
||||
Ids = bomb.Select(c => c.ID).ToArray(),
|
||||
Type = CardType1.None
|
||||
}, true));
|
||||
multiCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取对子(2张相同)
|
||||
var pairs = GetMultiGroups(hand, 2);
|
||||
foreach (var pair in pairs)
|
||||
{
|
||||
candidates.Add((new PlayOutCardPdkF
|
||||
{
|
||||
Pos = (byte)pos, GameNum = pair[0].GameNum,
|
||||
Ids = pair.Select(c => c.ID).ToArray(),
|
||||
Type = CardType1.None
|
||||
}, true));
|
||||
multiCount++;
|
||||
}
|
||||
|
||||
// 获取三张
|
||||
var triples = GetMultiGroups(hand, 3);
|
||||
foreach (var t in triples)
|
||||
{
|
||||
candidates.Add((new PlayOutCardPdkF
|
||||
{
|
||||
Pos = (byte)pos, GameNum = t[0].GameNum,
|
||||
Ids = t.Select(c => c.ID).ToArray(),
|
||||
Type = CardType1.None
|
||||
}, true));
|
||||
multiCount++;
|
||||
}
|
||||
|
||||
// 获取顺子(≥5 张连续)
|
||||
var straights = PokerLogic.GetShunZi(hand);
|
||||
if (straights != null)
|
||||
{
|
||||
foreach (var s in straights)
|
||||
{
|
||||
candidates.Add((new PlayOutCardPdkF
|
||||
{
|
||||
Pos = (byte)pos, GameNum = s[0].GameNum,
|
||||
Ids = s.Select(c => c.ID).ToArray(),
|
||||
Type = CardType1.None
|
||||
}, true));
|
||||
multiCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>获取指定数量的同面值组合(对子=2,三张=3)</summary>
|
||||
private static List<List<TCardInfoPdkF>> GetMultiGroups(TCardInfoPdkF[] hand, int count)
|
||||
{
|
||||
var result = new List<List<TCardInfoPdkF>>();
|
||||
var groups = hand.GroupBy(c => c.GameNum).Where(g => g.Count() >= count);
|
||||
foreach (var g in groups)
|
||||
{
|
||||
var cards = g.Take(count).ToList();
|
||||
result.Add(cards);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从合法方案中选最优:优先出多张牌的组合,其次出最大的
|
||||
/// P2修复: 之前只取 tips[0](最小单张),改为优先多牌组合
|
||||
|
||||
Reference in New Issue
Block a user