using System; using System.Collections.Generic; using System.Linq; using GameFix.PaoDeKuaiF; using GameMessage; using GameMessage.PaoDeKuaiF; namespace PdkFriendServer.Logic { /// /// 终局精确求解器 — 手牌≤5张时,枚举所有对手手牌分布,每分布模拟到底。 /// 替代 ISMCTS 采样:残局决策更精确。 /// public static class EndgameSolver { private const int MaxDepth = 30; public static PlayOutCardPdkF? Solve( TCardInfoPdkF[] hand, PdkBotView view, CardTracker tracker, Random rng) { if (hand.Length > 5) return null; if (tracker.UnknownCount(hand) > 15) return null; int myPos = view.MyPos; int opp1 = myPos % 3; int opp2 = (myPos + 1) % 3; int o1c = view.ShenYuCard[opp1]; int o2c = view.ShenYuCard[opp2]; var pool = GeneratePool(hand, tracker); if (pool.Count < o1c + o2c) return null; bool isFirstPlay = view.MaxPlayCard.GameNum <= 0; var tips = PdkCardAlgorithm.GetTipCard(view.MaxPlayCard, hand, null, view.Rule.AAAIsZhaDan); var candidates = new List>(); if (tips != null) foreach (var t in tips) candidates.Add(t); if (!isFirstPlay) candidates.Add(new List()); if (candidates.Count == 0) return null; int samples = Math.Min(100, ComputeCombinations(pool.Count, o1c + o2c)); var bestPlay = candidates[0]; // safe default int bestWins = -1; for (int ci = 0; ci < candidates.Count; ci++) { var play = candidates[ci]; int wins = 0; var myRem = hand.Where(c => c.GameState == 1).ToList(); if (play.Count > 0) foreach (var p in play) myRem.RemoveAll(h => h.ID == p.ID); if (myRem.Count == 0) { bestPlay = play; break; } for (int s = 0; s < samples; s++) { var shuf = new List(pool); Shuffle(shuf, rng); var o1 = shuf.Take(o1c).ToList(); var o2 = shuf.Skip(o1c).Take(o2c).ToList(); PlayOutCardPdkF maxPlay; int maxPlayer; int current; if (play.Count == 0) // pass { maxPlay = Clone(view.MaxPlayCard); maxPlayer = view.MaxPlayCard.Pos > 0 ? view.MaxPlayCard.Pos - 1 : myPos - 1; current = myPos % 3; } else { maxPlay = new PlayOutCardPdkF { GameNum = play[0].GameNum, Type = CardType1.DanZhang }; maxPlayer = myPos - 1; current = myPos % 3; } if (Simulate(o1, o2, myRem, current, maxPlay, maxPlayer, 0)) wins++; } if (wins > bestWins) { bestWins = wins; bestPlay = play; } } if (bestPlay.Count == 0) return new PlayOutCardPdkF { Pos = (byte)myPos, Type = CardType1.None }; return new PlayOutCardPdkF { Pos = (byte)myPos, GameNum = bestPlay[0].GameNum, Ids = bestPlay.Select(c => c.ID).ToArray(), Type = CardType1.None }; } // 简化模拟:给定对手手牌,模拟到终局 private static bool Simulate( List h1, List h2, List myHand, int current, // 0=h1, 1=h2, 2=me PlayOutCardPdkF maxPlay, int maxPlayer, int depth) { if (depth > MaxDepth) return myHand.Count <= Math.Min(h1.Count, h2.Count); if (h1.Count == 0) return false; if (h2.Count == 0) return false; if (myHand.Count == 0) return true; var cur = current == 0 ? h1 : current == 1 ? h2 : myHand; bool isNewRound = maxPlayer == current || maxPlay.GameNum <= 0; int next = (current + 1) % 3; var tips = PdkCardAlgorithm.GetTipCard( isNewRound ? new PlayOutCardPdkF() : maxPlay, cur.ToArray(), null, false); if (tips != null && tips.Count > 0) { var pick = tips[tips.Count - 1]; // 贪心:出最大的 foreach (var c in pick) cur.RemoveAll(h => h.ID == c.ID); var newMax = new PlayOutCardPdkF { GameNum = pick[0].GameNum, Type = CardType1.DanZhang }; return Simulate(h1, h2, myHand, next, newMax, current, depth + 1); } // pass return Simulate(h1, h2, myHand, next, maxPlay, isNewRound ? current : maxPlayer, depth + 1); } private static List GeneratePool(TCardInfoPdkF[] hand, CardTracker tracker) { return tracker.GetUnknownPool(hand); } private static int ComputeCombinations(int n, int k) { if (k > n) return 0; long result = 1; for (int i = 1; i <= k; i++) result = result * (n - i + 1) / i; return (int)Math.Min(result, 100); } private static void Shuffle(List l, Random rng) { 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 Clone(PlayOutCardPdkF s) => new PlayOutCardPdkF { Pos = s.Pos, GameNum = s.GameNum, Type = s.Type, Ids = s.Ids?.ToArray() }; } }