using System; using System.Collections.Generic; using System.Linq; using GameFix.PaoDeKuaiF; using GameFix.Poker; using GameMessage; using GameMessage.PaoDeKuaiF; namespace PdkFriendServer.Logic { /// /// 机器人可见的牌局信息(模拟真人视角——只看自己手牌+桌面公开信息) /// public class PdkBotView { /// 我的手牌(仅本轮可出的牌,GameState==1) public TCardInfoPdkF[] MyHand { get; set; } /// 桌上最后一手牌(谁打的什么),GameNum==0表示桌上无牌/新一轮 public PlayOutCardPdkF MaxPlayCard { get; set; } /// 当前轮到谁出牌(1-indexed) public int WhoPlay { get; set; } /// 我在第几个位置(1-indexed) public int MyPos { get; set; } /// 各玩家剩余牌数 [pos0,pos1,pos2] public byte[] ShenYuCard { get; set; } /// 各玩家已出炸弹数 public int[] ZhaDans { get; set; } /// 游戏规则 public PdkRule Rule { get; set; } /// 当前游戏阶段 3=包庄 4=打牌 public int GameZT { get; set; } } /// /// 机器人决策接口——外挂接入点 /// public interface IPdkBot { /// 包庄决策 bool DecideBaoZhuang(PdkBotView view); /// 打牌决策:返回要出的牌(空=不要) PlayOutCardPdkF DecidePlay(PdkBotView view); } /// /// 跑得快最优出牌机器人 — 有限信息策略 /// 只能看到自己手牌+桌面公开信息(最后出牌、各家剩余张数) /// public class PdkBot : IPdkBot { public bool DecideBaoZhuang(PdkBotView view) { // 启发式:手牌中 ≥J 的大牌多则包庄(速算,不如 ISMCTS 精确) int bigCards = view.MyHand.Count(c => c.GameNum >= 11); return bigCards >= 6; } public PlayOutCardPdkF DecidePlay(PdkBotView view) { var hand = view.MyHand; bool isFirstPlay = view.MaxPlayCard.GameNum <= 0; // 获取合法出牌方案 var maxHand = isFirstPlay ? null : GetMaxPlayerHand(view); var tips = PdkCardAlgorithm.GetTipCard( view.MaxPlayCard, hand, maxHand, // null = 不限上家手牌(因为我们不知道) view.Rule.AAAIsZhaDan); var candidates = new List<(PlayOutCardPdkF play, int score)>(); // 不要(不是先出时可选) if (!isFirstPlay) candidates.Add((Pass(view), ScorePass(view))); if (tips != null) { foreach (var tip in tips) { var play = MakePlay(tip, view.MyPos); candidates.Add((play, ScorePlay(play, hand.Length - tip.Count, view))); } } if (candidates.Count == 0) return Pass(view); candidates.Sort((a, b) => b.score.CompareTo(a.score)); var best = candidates[0]; var desc = best.play.Type == CardType1.None ? "pass" : $"{best.play.Type}:{best.play.Ids?.Length ?? 0}cards"; System.Console.WriteLine($"[Bot pos{view.MyPos}] hand:{hand.Length} L:{GetLeftCount(view)} R:{GetRightCount(view)} => {desc}"); return best.play; } // ---- 辅助方法 ---- private static PlayOutCardPdkF MakePlay(List cards, int pos) { return new PlayOutCardPdkF { Pos = (byte)pos, GameNum = cards[0].GameNum, Ids = cards.Select(x => x.ID).ToArray(), Type = cards.Count == 1 ? CardType1.DanZhang : cards.Count == 2 ? CardType1.DuiZi : cards.Count == 4 && cards.All(c => c.GameNum == cards[0].GameNum) ? CardType1.ZhaDan : CardType1.DanZhang // 精确牌型由CheckPack/GetOutCard修正 }; } private static PlayOutCardPdkF Pass(PdkBotView view) { return new PlayOutCardPdkF { Pos = (byte)view.MyPos, Type = CardType1.None }; } private static TCardInfoPdkF[] GetMaxPlayerHand(PdkBotView view) { // 我们不知道上家具体手牌,返回null让GetTipCard忽略maxHand限制 return null; } // ---- 评分引擎 ---- private int ScorePlay(PlayOutCardPdkF play, int remainingAfter, PdkBotView view) { int score = 0; int leftCnt = GetLeftCount(view); int rightCnt = GetRightCount(view); // 1. 直接赢 if (remainingAfter == 0) return 10000; // 2. 对手威胁 if (leftCnt <= 3 || rightCnt <= 3) score += 300; if (leftCnt <= 2 || rightCnt <= 2) score += 500; // 3. 牌效率:剩余越少越好 score += (20 - remainingAfter) * 15; // 4. 炸弹:留到关键时刻 bool isBomb = play.Type == CardType1.ZhaDan || (play.Ids != null && play.Ids.Length == 4 && view.MyHand.Count(x => x.GameNum == play.GameNum && x.GameState == 1) == 4); if (isBomb) { if (leftCnt <= 5 || rightCnt <= 5) score += 400; // 对手快赢了 else if (remainingAfter <= 4) score += 200; // 自己快赢了 else score -= 80; // 先留着 } // 5. 出更多牌更好 int cardsOut = view.MyHand.Length - remainingAfter; score += cardsOut * 8; // 6. 保持先手权 if (play.Type != CardType1.None) score += 60; // 7. 黑桃3首局必出 if (view.Rule.HeiTao3BiChu && view.MaxPlayCard.GameNum <= 0) if (play.Ids != null && play.Ids.Any(x => x == 3)) score += 150; return score; } private int ScorePass(PdkBotView view) { int score = -80; // 对手快赢时不要很危险 if (GetLeftCount(view) <= 3 || GetRightCount(view) <= 3) score -= 400; return score; } private int GetLeftCount(PdkBotView view) { int next = view.MyPos % view.ShenYuCard.Length; return view.ShenYuCard[next]; } private int GetRightCount(PdkBotView view) { int prev = (view.MyPos - 2 + view.ShenYuCard.Length) % view.ShenYuCard.Length; return view.ShenYuCard[prev]; } } }