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];
}
}
}