fix: ISMCTS包庄决策——600次模拟估算P(win), 阈值45%
替换占位逻辑(大牌≥6张)为真正的蒙特卡洛评估: - EstimateWinProbability(): 模拟N局完整对局, 统计我第一个出完的概率 - 阈值45%: 包庄收益翻倍, 略微倾斜于有利局势 - 日志: [ISMCTS posX] 包庄评估: P(win)=XX% → 包庄/不包 原理: 包庄不改变牌局流程(不像斗地主叫地主), 只翻倍输赢。 因此决策简化为 P(win|我的手牌) ≥ 阈值 → 包庄。
This commit is contained in:
@ -26,10 +26,86 @@ namespace PdkFriendServer.Logic
|
||||
|
||||
public bool DecideBaoZhuang(PdkBotView view)
|
||||
{
|
||||
// 包庄决策:用快速模拟评估——手牌质量高就包
|
||||
// 简化:手牌中 >10 的大牌(J/Q/K/A/2)多就包
|
||||
int bigCards = view.MyHand.Count(c => c.GameNum >= 11);
|
||||
return bigCards >= 6; // ≥6 张大牌 → 有一定控制力
|
||||
// ISMCTS 包庄决策:模拟 N 局,估算自己第一个出完的概率
|
||||
// 包庄不改变牌局流程,只翻倍输赢:P(win)*2*gain - P(lose)*2*loss
|
||||
// 阈值 45%——因为包庄成功收益翻倍,略微倾斜有利于局势占优
|
||||
double winRate = EstimateWinProbability(view);
|
||||
bool shouldBao = winRate >= 0.45;
|
||||
|
||||
Console.WriteLine($"[ISMCTS pos{view.MyPos}] 包庄评估: P(win)={winRate:P0} → {(shouldBao ? "包庄" : "不包")}");
|
||||
return shouldBao;
|
||||
}
|
||||
|
||||
/// <summary>模拟 N 局随机对局,统计我第一个出完的概率</summary>
|
||||
private double EstimateWinProbability(PdkBotView view)
|
||||
{
|
||||
int myPos = view.MyPos;
|
||||
var hand = view.MyHand;
|
||||
int sims = Math.Min(_simulations, 600); // 包庄模拟用 600 次,比出牌少
|
||||
|
||||
int wins = 0;
|
||||
for (int s = 0; s < sims; s++)
|
||||
{
|
||||
if (SimulateWin(hand, view))
|
||||
wins++;
|
||||
}
|
||||
return (double)wins / sims;
|
||||
}
|
||||
|
||||
/// <summary>模拟一局只看胜负——不指定第一步出什么</summary>
|
||||
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);
|
||||
|
||||
int nextCnt = view.ShenYuCard[nextPos - 1];
|
||||
int oppCnt = view.ShenYuCard[oppPos - 1];
|
||||
hands[nextPos - 1].AddRange(unknown.Take(nextCnt));
|
||||
hands[oppPos - 1].AddRange(unknown.Skip(nextCnt).Take(oppCnt));
|
||||
|
||||
// 模拟到有人出完
|
||||
int current = myPos;
|
||||
var maxPlay = new PlayOutCardPdkF(); // GameNum=0 → 新一轮
|
||||
int maxPlayer = 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)
|
||||
{
|
||||
var pick = tips[0];
|
||||
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;
|
||||
foreach (var id in maxPlay.Ids)
|
||||
curHand.RemoveAll(c => c.ID == id);
|
||||
}
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user