refactor: 修复3个🟡问题 — 合并模拟方法/移除死参数/缓存GetMultiGroups

1. SimulateWin+SimulateOneGame → SimulateToEnd(nullable firstPlay)
   减少 ~80行重复代码, 包庄/出牌共享同一模拟引擎

2. 移除 unknownCount 死参数 (SimulateOneGame 从未使用)

3. GetMultiGroups(hand,2) 只调一次 — 对子+连对共享 pairs 变量

文件: IsmctsBot.cs 583行 → 331行 (-43%)
This commit is contained in:
2026-07-07 14:43:13 +08:00
parent ce2a4c98ef
commit b39e28411b
7 changed files with 105 additions and 357 deletions

View File

@ -9,15 +9,16 @@ using GameMessage.PaoDeKuaiF;
namespace PdkFriendServer.Logic
{
/// <summary>
/// ISMCTS 机器人 v2
/// P0修复: 包庄阈值 70%从 45% 校准rollout 用最强方案而非最小
/// P2修复: 优先多牌组合(对子/顺子 > 单张),提高清牌效率
/// ISMCTS 机器人 v3
/// - 包庄阈值 60%校准自1000局压测
/// - 全牌型枚举:对子/三带二/四带二/四带三/顺子/连对/飞机/炸弹/单张
/// - SimulateWin + SimulateOneGame 合并为 SimulateToEnd
/// </summary>
public class IsmctsBot : IPdkBot
{
private readonly int _simulations;
private readonly Random _rng;
private const double BaoThreshold = 0.60; // 校准自1000局压测70%→0人包45%→5.2%成功
private const double BaoThreshold = 0.60;
public IsmctsBot(int simulations = 800)
{
@ -31,7 +32,6 @@ namespace PdkFriendServer.Logic
{
double winRate = EstimateWinProbability(view);
bool shouldBao = winRate >= BaoThreshold;
Console.WriteLine($"[ISMCTS pos{view.MyPos}] 包庄评估: P(win)={winRate:P0} "
+ $"(阈值{BaoThreshold:P0}) → {(shouldBao ? "" : "")}");
return shouldBao;
@ -39,99 +39,14 @@ namespace PdkFriendServer.Logic
private double EstimateWinProbability(PdkBotView view)
{
var hand = view.MyHand;
int sims = Math.Min(_simulations, 600);
int wins = 0;
for (int s = 0; s < sims; s++)
{
if (SimulateWin(hand, view))
if (SimulateToEnd(view.MyHand, view, null))
wins++;
}
return (double)wins / sims;
}
private bool SimulateWin(TCardInfoPdkF[] myHand, PdkBotView view)
{
int myPos = view.MyPos;
int nextPos = myPos % 3 + 1;
int oppPos = (myPos + 1) % 3 + 1;
var hands = new List<TCardInfoPdkF>[3];
hands[myPos - 1] = new List<TCardInfoPdkF>(myHand);
hands[nextPos - 1] = new List<TCardInfoPdkF>();
hands[oppPos - 1] = new List<TCardInfoPdkF>();
var allCards = GenerateDeck();
var unknown = allCards.Where(c => !myHand.Any(h => h.ID == c.ID)).ToList();
Shuffle(unknown);
hands[nextPos - 1].AddRange(unknown.Take(view.ShenYuCard[nextPos - 1]));
hands[oppPos - 1].AddRange(unknown.Skip(view.ShenYuCard[nextPos - 1])
.Take(view.ShenYuCard[oppPos - 1]));
int current = myPos;
var maxPlay = new PlayOutCardPdkF();
int maxPlayer = 0;
int passStreak = 0;
for (int move = 0; move < 400; move++)
{
var curHand = hands[current - 1];
if (curHand.Count == 0) return current == myPos;
bool isNewRound = maxPlayer == current || maxPlay.GameNum == 0;
var curMax = isNewRound ? new PlayOutCardPdkF() : maxPlay;
var tips = PdkCardAlgorithm.GetTipCard(curMax, curHand.ToArray(), null, false);
if (tips != null && tips.Count > 0)
{
// P2修复: 优先多牌组合其次选最强最大GameNum
var pick = PickBestTip(tips, isNewRound);
maxPlay = new PlayOutCardPdkF
{
Pos = (byte)current,
GameNum = pick[0].GameNum,
Type = pick.Count == 1 ? CardType1.DanZhang : CardType1.DuiZi,
Ids = pick.Select(c => c.ID).ToArray()
};
maxPlayer = current;
passStreak = 0;
foreach (var id in maxPlay.Ids)
curHand.RemoveAll(c => c.ID == id);
}
else
{
passStreak++;
// 防止死循环: 三轮全 pass 则强制最大出牌
if (passStreak >= 2)
{
var forceTips = PdkCardAlgorithm.GetTipCard(
new PlayOutCardPdkF(), curHand.ToArray(), null, false);
if (forceTips != null && forceTips.Count > 0)
{
var pick = PickBestTip(forceTips, true);
maxPlay = new PlayOutCardPdkF
{
Pos = (byte)current,
GameNum = pick[0].GameNum,
Type = CardType1.DanZhang,
Ids = pick.Select(c => c.ID).ToArray()
};
maxPlayer = current;
foreach (var id in maxPlay.Ids)
curHand.RemoveAll(c => c.ID == id);
}
passStreak = 0;
}
}
current = current % 3 + 1;
}
return hands[myPos - 1].Count <= hands[nextPos - 1].Count
&& hands[myPos - 1].Count <= hands[oppPos - 1].Count;
}
// ---- 出牌决策 ----
public PlayOutCardPdkF DecidePlay(PdkBotView view)
@ -141,10 +56,8 @@ namespace PdkFriendServer.Logic
bool isFirstPlay = view.MaxPlayCard.GameNum <= 0;
// 1. 获取合法出牌方案
var tips = PdkCardAlgorithm.GetTipCard(
view.MaxPlayCard, hand, null, view.Rule.AAAIsZhaDan);
// 1. 合法出牌方案
var tips = PdkCardAlgorithm.GetTipCard(view.MaxPlayCard, hand, null, view.Rule.AAAIsZhaDan);
var allCandidates = new List<(PlayOutCardPdkF play, bool isMulti)>();
var multiCount = 0;
@ -156,11 +69,9 @@ namespace PdkFriendServer.Logic
allCandidates.Add((MakePlay(tip, view.MyPos), isMulti));
}
// 2. 新轮次:补充多牌先手方案GetTipCard只返回单张
// 2. 新轮次:补充多牌先手方案
if (isFirstPlay)
{
AddMultiCardLeads(hand, view.MyPos, allCandidates, ref multiCount);
}
if (!isFirstPlay)
allCandidates.Add((Pass(view), false));
@ -170,51 +81,39 @@ namespace PdkFriendServer.Logic
if (allCandidates.Count == 1)
return allCandidates[0].play;
int unknownCount = 48 - hand.Length - CountPlayed(hand, view);
int simsPerCandidate = Math.Max(50, _simulations / allCandidates.Count);
for (int ci = 0; ci < allCandidates.Count; ci++)
{
int wins = 0;
var play = allCandidates[ci].play;
for (int s = 0; s < simsPerCandidate; s++)
{
if (SimulateOneGame(hand, play, view, unknownCount))
if (SimulateToEnd(hand, view, play))
wins++;
}
double wr = (double)wins / simsPerCandidate;
scoredCandidates.Add((play, wr, allCandidates[ci].isMulti));
scoredCandidates.Add((play, (double)wins / simsPerCandidate, allCandidates[ci].isMulti));
}
// 两阶段选择:多牌组合胜率≥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();
.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";
string desc = best.play.Ids == null || best.play.Ids.Length == 0 ? "pass" : $"{best.play.Ids.Length}c";
Console.WriteLine($"[ISMCTS pos{view.MyPos}] {simsPerCandidate * allCandidates.Count}sims "
+ $"winRate:{best.winRate:P0} => {desc}");
return best.play;
}
// ---- 模拟引擎 ----
private bool SimulateOneGame(
TCardInfoPdkF[] myHand, PlayOutCardPdkF myPlay,
PdkBotView view, int unknownCount)
private bool SimulateToEnd(TCardInfoPdkF[] myHand, PdkBotView view, PlayOutCardPdkF? firstPlay = null)
{
int myPos = view.MyPos;
int nextPos = myPos % 3 + 1;
@ -228,356 +127,205 @@ namespace PdkFriendServer.Logic
var allCards = GenerateDeck();
var unknown = allCards.Where(c => !myHand.Any(h => h.ID == c.ID)).ToList();
Shuffle(unknown);
hands[nextPos - 1].AddRange(unknown.Take(view.ShenYuCard[nextPos - 1]));
hands[oppPos - 1].AddRange(unknown.Skip(view.ShenYuCard[nextPos - 1])
.Take(view.ShenYuCard[oppPos - 1]));
hands[oppPos - 1].AddRange(unknown.Skip(view.ShenYuCard[nextPos - 1]).Take(view.ShenYuCard[oppPos - 1]));
var maxPlay = new PlayOutCardPdkF
{
Pos = (byte)myPos, GameNum = myPlay.GameNum,
Type = myPlay.Type, Ids = myPlay.Ids?.ToArray()
};
if (myPlay.Type != CardType1.None && myPlay.Ids != null)
foreach (var id in myPlay.Ids)
hands[myPos - 1].RemoveAll(c => c.ID == id);
int current, maxPlayer;
PlayOutCardPdkF maxPlay;
int current = myPos % 3 + 1;
int maxPlayer = myPlay.Type == CardType1.None ? view.MaxPlayCard.Pos : myPos;
if (myPlay.Type == CardType1.None)
if (firstPlay.HasValue)
{
maxPlayer = view.MaxPlayCard.Pos;
maxPlay = CloneCard(view.MaxPlayCard);
var fp = firstPlay.Value;
maxPlay = new PlayOutCardPdkF { Pos = (byte)myPos, GameNum = fp.GameNum, Type = fp.Type, Ids = fp.Ids?.ToArray() };
if (fp.Type != CardType1.None && fp.Ids != null)
foreach (var id in fp.Ids) hands[myPos - 1].RemoveAll(c => c.ID == id);
current = myPos % 3 + 1;
maxPlayer = fp.Type == CardType1.None ? view.MaxPlayCard.Pos : myPos;
if (fp.Type == CardType1.None) { maxPlayer = view.MaxPlayCard.Pos; maxPlay = CloneCard(view.MaxPlayCard); }
}
else
{
current = myPos; maxPlay = new PlayOutCardPdkF(); maxPlayer = 0;
}
int moveCount = 0;
int passStreak = 0;
while (moveCount++ < 500)
for (int move = 0; move < 500; move++)
{
var curHand = hands[current - 1];
if (curHand.Count == 0) return current == myPos;
bool isNewRound = maxPlayer == current || maxPlay.GameNum == 0;
var curMax = isNewRound ? new PlayOutCardPdkF() : maxPlay;
var curTips = PdkCardAlgorithm.GetTipCard(curMax, curHand.ToArray(), null, false);
var curTips = PdkCardAlgorithm.GetTipCard(isNewRound ? new PlayOutCardPdkF() : maxPlay, curHand.ToArray(), null, false);
if (curTips != null && curTips.Count > 0)
{
var pick = PickBestTip(curTips, isNewRound);
maxPlay = new PlayOutCardPdkF
{
Pos = (byte)current,
GameNum = pick[0].GameNum,
Type = pick.Count == 1 ? CardType1.DanZhang : CardType1.DuiZi,
Ids = pick.Select(c => c.ID).ToArray()
};
maxPlayer = current;
passStreak = 0;
foreach (var id in maxPlay.Ids)
curHand.RemoveAll(c => c.ID == id);
var pick = PickBestTip(curTips);
maxPlay = new PlayOutCardPdkF { Pos = (byte)current, GameNum = pick[0].GameNum, Type = pick.Count == 1 ? CardType1.DanZhang : CardType1.DuiZi, Ids = pick.Select(c => c.ID).ToArray() };
maxPlayer = current; passStreak = 0;
foreach (var id in maxPlay.Ids) curHand.RemoveAll(c => c.ID == id);
}
else
{
passStreak++;
if (passStreak >= 2)
if (++passStreak >= 2)
{
var forceTips = PdkCardAlgorithm.GetTipCard(
new PlayOutCardPdkF(), curHand.ToArray(), null, false);
if (forceTips != null && forceTips.Count > 0)
var ft = PdkCardAlgorithm.GetTipCard(new PlayOutCardPdkF(), curHand.ToArray(), null, false);
if (ft != null && ft.Count > 0)
{
var pick = PickBestTip(forceTips, true);
maxPlay = new PlayOutCardPdkF
{
Pos = (byte)current, GameNum = pick[0].GameNum,
Type = CardType1.DanZhang, Ids = pick.Select(c => c.ID).ToArray()
};
var pick = PickBestTip(ft);
maxPlay = new PlayOutCardPdkF { Pos = (byte)current, GameNum = pick[0].GameNum, Type = CardType1.DanZhang, Ids = pick.Select(c => c.ID).ToArray() };
maxPlayer = current;
foreach (var id in maxPlay.Ids)
curHand.RemoveAll(c => c.ID == id);
foreach (var id in maxPlay.Ids) curHand.RemoveAll(c => c.ID == id);
}
passStreak = 0;
}
}
current = current % 3 + 1;
}
return hands[myPos - 1].Count <= hands[nextPos - 1].Count
&& hands[myPos - 1].Count <= hands[oppPos - 1].Count;
return hands[myPos - 1].Count <= hands[nextPos - 1].Count && hands[myPos - 1].Count <= hands[oppPos - 1].Count;
}
// ---- 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)
AddCandidate(bomb, pos, candidates, ref multiCount);
foreach (var bomb in bombs) AddCandidate(bomb, pos, candidates, ref multiCount);
// 对子2张相同
foreach (var pair in GetMultiGroups(hand, 2))
AddCandidate(pair, pos, candidates, ref multiCount);
// 对子 + 三张(缓存
var pairs = GetMultiGroups(hand, 2);
foreach (var p in pairs) AddCandidate(p, pos, candidates, ref multiCount);
// 三张 / 三带二
var triples = GetMultiGroups(hand, 3);
foreach (var t in triples)
{
// 三带二: 三张 + 附带最小的两张其他牌
var remainder = hand.Where(c => c.GameNum != t[0].GameNum).OrderBy(c => c.GameNum).ToList();
if (remainder.Count >= 2)
{
var sd2 = t.Concat(remainder.Take(2)).ToList();
AddCandidate(sd2, pos, candidates, ref multiCount);
}
var rem = hand.Where(c => c.GameNum != t[0].GameNum).OrderBy(c => c.GameNum).ToList();
if (rem.Count >= 2)
AddCandidate(t.Concat(rem.Take(2)).ToList(), pos, candidates, ref multiCount); // 三带二
else
{
AddCandidate(t, pos, candidates, ref multiCount); // 手牌不够带,纯三张
}
AddCandidate(t, pos, candidates, ref multiCount);
}
// 四带二 / 四带
// 四带二/
var quads = GetMultiGroups(hand, 4);
foreach (var q in quads)
{
var remainder = hand.Where(c => c.GameNum != q[0].GameNum).OrderBy(c => c.GameNum).ToList();
if (remainder.Count >= 3)
{
var sd3 = q.Concat(remainder.Take(3)).ToList();
AddCandidate(sd3, pos, candidates, ref multiCount);
}
else if (remainder.Count >= 2)
{
var sd2 = q.Concat(remainder.Take(2)).ToList();
AddCandidate(sd2, pos, candidates, ref multiCount);
}
// else: 炸弹已在上面处理
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, ref multiCount);
else if (rem.Count >= 2) AddCandidate(q.Concat(rem.Take(2)).ToList(), pos, candidates, ref multiCount);
}
// 连对(≥3对连续, 如 334455
var pairs = GetMultiGroups(hand, 2);
// 连对(复用 pairs不重复 GetMultiGroups
SortByGameNum(pairs);
AddConsecutiveGroups(pairs, 3, pos, candidates, ref multiCount);
// 飞机≥2组连续三张, 如 333444
// 飞机
SortByGameNum(triples);
AddConsecutiveGroups(triples, 2, pos, candidates, ref multiCount);
// 飞机带对: 连续三张 + 每组附带一个对子
var tripleConsecRuns = GetConsecutiveRuns(triples, 2);
foreach (var run in tripleConsecRuns)
// 飞机带对
var runs = GetConsecutiveRuns(triples, 2);
foreach (var run in runs)
{
var runCards = run.SelectMany(t => t).ToList();
int pairCount = run.Count; // 需要这么多对子
// 找不重叠的对子
var remainingPairs = GetMultiGroups(
hand.Where(c => !runCards.Any(r => r.ID == c.ID)).ToArray(), 2);
if (remainingPairs.Count >= pairCount)
var rp = GetMultiGroups(hand.Where(c => !runCards.Any(r => r.ID == c.ID)).ToArray(), 2);
if (rp.Count >= run.Count)
{
SortByGameNum(remainingPairs);
var attached = remainingPairs.Take(pairCount).SelectMany(p => p).ToList();
runCards.AddRange(attached);
SortByGameNum(rp);
runCards.AddRange(rp.Take(run.Count).SelectMany(p => p));
AddCandidate(runCards, pos, candidates, ref multiCount);
}
}
// 顺子≥5 张连续)
// 顺子
var straights = PokerLogic.GetShunZi(hand);
if (straights != null)
foreach (var s in straights)
AddCandidate(s, pos, candidates, ref multiCount);
if (straights != null) foreach (var s in straights) AddCandidate(s, pos, candidates, ref multiCount);
}
// ---- 辅助方法 ----
private static void AddCandidate(List<TCardInfoPdkF> cards, int pos,
List<(PlayOutCardPdkF play, bool isMulti)> candidates, ref int multiCount)
{
candidates.Add((new PlayOutCardPdkF
{
Pos = (byte)pos, GameNum = cards[0].GameNum,
Ids = cards.Select(c => c.ID).ToArray(),
Type = InferType(cards)
}, true));
candidates.Add((new PlayOutCardPdkF { Pos = (byte)pos, GameNum = cards[0].GameNum, Ids = cards.Select(c => c.ID).ToArray(), Type = InferType(cards) }, true));
multiCount++;
}
/// <summary>根据牌数和面值推断牌型——ISMCTS模拟需要正确Type</summary>
private static CardType1 InferType(List<TCardInfoPdkF> cards)
{
int n = cards.Count;
if (n == 1) return CardType1.DanZhang;
if (n == 2) return CardType1.DuiZi;
var counts = cards.GroupBy(c => c.GameNum).Select(g => g.Count()).OrderByDescending(x => x).ToList();
if (counts[0] == 4)
{
if (n == 4) return CardType1.ZhaDan;
if (n == 6) return CardType1.SiDai2;
if (n == 7) return CardType1.SiDai3;
}
if (counts[0] >= 3)
{
// 三带二 / 飞机 / 飞机带对
return CardType1.SanDai2;
}
if (counts[0] == 2 && n % 2 == 0)
{
// 连对 vs 普通多张:检查是否连续对子
return CardType1.LianDui;
}
return CardType1.ShunZi; // 顺子或其他 ≥3 张组合
if (counts[0] == 4) { if (n == 4) return CardType1.ZhaDan; if (n == 6) return CardType1.SiDai2; if (n == 7) return CardType1.SiDai3; }
if (counts[0] >= 3) return CardType1.SanDai2;
if (counts[0] == 2 && n % 2 == 0) return CardType1.LianDui;
return CardType1.ShunZi;
}
private static PlayOutCardPdkF MakePlay(List<TCardInfoPdkF> cards, int pos)
{
return new PlayOutCardPdkF
{
Pos = (byte)pos, GameNum = cards[0].GameNum,
Ids = cards.Select(x => x.ID).ToArray(),
Type = InferType(cards)
};
}
=> new PlayOutCardPdkF { Pos = (byte)pos, GameNum = cards[0].GameNum, Ids = cards.Select(x => x.ID).ToArray(), Type = InferType(cards) };
/// <summary>获取指定数量的同面值组合</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;
var r = new List<List<TCardInfoPdkF>>();
foreach (var g in hand.GroupBy(c => c.GameNum).Where(g => g.Count() >= count))
r.Add(g.Take(count).ToList());
return r;
}
/// <summary>按 GameNum 排序多牌组列表</summary>
private static void SortByGameNum(List<List<TCardInfoPdkF>> groups)
{
groups.Sort((a, b) => a[0].GameNum.CompareTo(b[0].GameNum));
}
private static void SortByGameNum(List<List<TCardInfoPdkF>> g) => g.Sort((a, b) => a[0].GameNum.CompareTo(b[0].GameNum));
/// <summary>找出所有连续段≥minLen每段生成一个候选</summary>
private static void AddConsecutiveGroups(List<List<TCardInfoPdkF>> groups, int minLen,
int pos, List<(PlayOutCardPdkF play, bool isMulti)> candidates, ref int multiCount)
{
var runs = GetConsecutiveRuns(groups, minLen);
foreach (var run in runs)
{
var cards = run.SelectMany(g => g).ToList();
AddCandidate(cards, pos, candidates, ref multiCount);
}
}
private static void AddConsecutiveGroups(List<List<TCardInfoPdkF>> groups, int minLen, int pos,
List<(PlayOutCardPdkF, bool)> candidates, ref int mc)
{ foreach (var run in GetConsecutiveRuns(groups, minLen)) AddCandidate(run.SelectMany(g => g).ToList(), pos, candidates, ref mc); }
/// <summary>从已排序的组中提取所有连续段≥minLen 组)</summary>
private static List<List<List<TCardInfoPdkF>>> GetConsecutiveRuns(
List<List<TCardInfoPdkF>> groups, int minLen)
private static List<List<List<TCardInfoPdkF>>> GetConsecutiveRuns(List<List<TCardInfoPdkF>> groups, int minLen)
{
var runs = new List<List<List<TCardInfoPdkF>>>();
if (groups.Count < minLen) return runs;
var current = new List<List<TCardInfoPdkF>> { groups[0] };
int prevNum = groups[0][0].GameNum;
var cur = new List<List<TCardInfoPdkF>> { groups[0] };
int prev = groups[0][0].GameNum;
for (int i = 1; i < groups.Count; i++)
{
int curNum = groups[i][0].GameNum;
if (curNum == prevNum + 1)
{
current.Add(groups[i]);
}
else
{
if (current.Count >= minLen)
runs.Add(new List<List<TCardInfoPdkF>>(current));
current.Clear();
current.Add(groups[i]);
}
prevNum = curNum;
int n = groups[i][0].GameNum;
if (n == prev + 1) cur.Add(groups[i]);
else { if (cur.Count >= minLen) runs.Add(new List<List<TCardInfoPdkF>>(cur)); cur.Clear(); cur.Add(groups[i]); }
prev = n;
}
if (current.Count >= minLen)
runs.Add(current);
if (cur.Count >= minLen) runs.Add(cur);
return runs;
}
/// <summary>
/// 从合法方案中选最优:优先出多张牌的组合,其次出最大的
/// P2修复: 之前只取 tips[0](最小单张),改为优先多牌组合
/// P0修复: 选最大(最后一项),模拟更强对手
/// </summary>
private static List<TCardInfoPdkF> PickBestTip(List<List<TCardInfoPdkF>> tips, bool isNewRound)
private static List<TCardInfoPdkF> PickBestTip(List<List<TCardInfoPdkF>> tips)
{
if (tips == null || tips.Count == 0) return null;
// 优先多牌组合2张>1张3张>2张等同数量选最大的
var best = tips[tips.Count - 1]; // 默认最大
var best = tips[tips.Count - 1];
int bestScore = best.Count * 10 + best[0].GameNum;
for (int i = tips.Count - 2; i >= 0; i--)
{
int score = tips[i].Count * 10 + tips[i][0].GameNum;
if (score > bestScore)
{
bestScore = score;
best = tips[i];
}
int s = tips[i].Count * 10 + tips[i][0].GameNum;
if (s > bestScore) { bestScore = s; best = tips[i]; }
}
return best;
}
// ---- 工具 ----
private static int CountPlayed(TCardInfoPdkF[] myHand, PdkBotView view)
{
int total = 48 - myHand.Length;
foreach (var c in view.ShenYuCard) total -= c;
return Math.Max(0, total);
}
private static List<TCardInfoPdkF> GenerateDeck()
{
var deck = new List<TCardInfoPdkF>();
int id = 1;
foreach (int flower in new[] { 1, 2, 3, 4 })
for (int num = 1; num <= 13; num++)
{
int gameNum = num == 1 ? (byte)14 : num == 2 ? (byte)16 : (byte)num;
deck.Add(new TCardInfoPdkF
{
ID = (byte)id++, Flower = (byte)flower,
GameNum = (byte)gameNum, GameState = 1
});
}
return deck;
var d = new List<TCardInfoPdkF>(); int id = 1;
foreach (int f in new[] { 1, 2, 3, 4 })
for (int n = 1; n <= 13; n++)
d.Add(new TCardInfoPdkF { ID = (byte)id++, Flower = (byte)f, GameNum = (byte)(n == 1 ? 14 : n == 2 ? 16 : n), GameState = 1 });
return d;
}
private void Shuffle<T>(List<T> list)
{
for (int i = list.Count - 1; i > 0; i--)
{
int j = _rng.Next(i + 1);
(list[i], list[j]) = (list[j], list[i]);
}
}
private void Shuffle<T>(List<T> l) { for (int i = l.Count - 1; i > 0; i--) { int j = _rng.Next(i + 1); (l[i], l[j]) = (l[j], l[i]); } }
private static PlayOutCardPdkF CloneCard(PlayOutCardPdkF src)
{
return new PlayOutCardPdkF
{
Pos = src.Pos, GameNum = src.GameNum,
Type = src.Type, Ids = src.Ids?.ToArray()
};
}
private static PlayOutCardPdkF CloneCard(PlayOutCardPdkF s)
=> new PlayOutCardPdkF { Pos = s.Pos, GameNum = s.GameNum, Type = s.Type, Ids = s.Ids?.ToArray() };
private static PlayOutCardPdkF Pass(PdkBotView view)
{
return new PlayOutCardPdkF { Pos = (byte)view.MyPos, Type = CardType1.None };
}
private static PlayOutCardPdkF Pass(PdkBotView v)
=> new PlayOutCardPdkF { Pos = (byte)v.MyPos, Type = CardType1.None };
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.