using System;
using System.Collections.Generic;
using System.Linq;
using GameFix.PaoDeKuaiF;
using GameFix.Poker;
using GameMessage;
using GameMessage.PaoDeKuaiF;
namespace PdkFriendServer.Logic
{
///
/// SO-ISMCTS v2 — 优化版,静态牌池+数组交换替代对象分配。
///
/// 性能关键:
/// - 48张牌静态数组,模拟时只交换索引不创建新对象
/// - 预生成N份对手手牌分布,分摊 determinization 成本
/// - 贪心 rollout 直接操作数组切片
///
public class SOIsmctsBot : IPdkBot
{
private readonly int _simBudget;
private readonly Random _rng;
private readonly CardTracker _tracker = new CardTracker();
// 静态牌池:52张牌(同 IsmctsBot.GenerateDeck),只创建一次
private static readonly TCardInfoPdkF[] FullDeck;
static SOIsmctsBot()
{
FullDeck = new TCardInfoPdkF[52];
int id = 1;
foreach (int f in new[] { 1, 2, 3, 4 })
for (int n = 1; n <= 13; n++)
FullDeck[id - 1] = new TCardInfoPdkF
{
ID = (byte)(id++),
Flower = (byte)f,
GameNum = (byte)(n == 1 ? 14 : n == 2 ? 16 : n),
GameState = 1
};
}
public SOIsmctsBot(int simBudget = 800)
{
_simBudget = simBudget;
_rng = new Random();
}
public bool DecideBaoZhuang(PdkBotView view)
{
_tracker.Update(view);
int minPlays = HandOptimizer.MinPlays(view.MyHand);
if (minPlays >= 7) return false;
double threshold = minPlays <= 3 ? 0.50 : minPlays <= 4 ? 0.60 : minPlays <= 5 ? 0.65 : 0.70;
int wins = 0;
for (int s = 0; s < 400; s++)
if (SimulateToEnd(view.MyHand, view, null)) wins++;
double wr = (double)wins / 400;
Console.WriteLine($"[SOv2 pos{view.MyPos}] 包庄: min={minPlays} wr={wr:P0} → {(wr >= threshold ? "包" : "不")}");
return wr >= threshold;
}
public PlayOutCardPdkF DecidePlay(PdkBotView view)
{
_tracker.Update(view);
var hand = view.MyHand;
if (hand.Length == 0) return Pass(view);
// 终局精确求解
if (hand.Length <= 5 && _tracker.UnknownCount <= 15)
{
var eg = EndgameSolver.Solve(hand, view, _tracker, _rng);
if (eg.HasValue) return eg.Value;
}
// 锁手检测
if (hand.Length <= 8)
{
var lockSeq = LockDetector.FindLockSequence(hand, _tracker, view.MyPos);
if (lockSeq != null)
{
Console.WriteLine($"[SOv2 pos{view.MyPos}] LOCK {lockSeq.Count}步");
return new PlayOutCardPdkF { Pos = (byte)view.MyPos, GameNum = lockSeq[0].GameNum, Ids = lockSeq[0].Ids, Type = CardType1.None };
}
}
bool isFirstPlay = view.MaxPlayCard.GameNum <= 0;
var candidates = BuildCandidates(hand, view, isFirstPlay);
if (candidates.Count == 1) return candidates[0].play;
// SO-ISMCTS 主循环:单树 + 预生成手牌
var root = new SONode(candidates.Count);
int myPos = view.MyPos;
// 预生成 N 份对手手牌分布
int preGen = Math.Min(300, _simBudget);
var hands1 = new byte[preGen][];
var hands2 = new byte[preGen][];
// 建立已知牌ID集合
var knownIds = new HashSet();
foreach (var c in hand) knownIds.Add(c.ID);
int o1c = view.ShenYuCard[myPos % 3];
int o2c = view.ShenYuCard[(myPos + 1) % 3];
var pool = new byte[52 - knownIds.Count];
int pi = 0;
foreach (var c in FullDeck)
if (!knownIds.Contains(c.ID))
pool[pi++] = c.ID;
for (int g = 0; g < preGen; g++)
{
Shuffle(pool, _rng);
hands1[g] = new byte[o1c];
hands2[g] = new byte[o2c];
Array.Copy(pool, 0, hands1[g], 0, o1c);
Array.Copy(pool, o1c, hands2[g], 0, o2c);
}
int genIdx = 0;
int totalSims = 0;
for (int iter = 0; iter < _simBudget; iter++)
{
int ci = _rng.Next(candidates.Count);
var play = candidates[ci];
// 取一份预生成手牌
var o1 = hands1[genIdx % preGen];
var o2 = hands2[genIdx % preGen];
genIdx++;
bool win;
if (play.play.Type == CardType1.None) // pass
{
win = SimPass(hand, view, o1, o2);
}
else
{
var myRem = hand.Where(c => c.GameState == 1 && !play.play.Ids.Contains(c.ID)).ToArray();
if (myRem.Length == 0) win = true;
else win = SimAfterPlay(myRem, view, o1, o2, play.play.GameNum);
}
root.Visit(ci, win);
totalSims++;
if (totalSims % 150 == 0)
{
int converged = root.BestChild(candidates.Count);
if (converged >= 0 && root.VisitCount(converged) > _simBudget / candidates.Count / 2 && root.WinRate(converged) > 0.85)
break;
}
}
int bestIdx = root.BestChild(candidates.Count);
if (bestIdx < 0) bestIdx = 0;
Console.WriteLine($"[SOv2 pos{view.MyPos}] {totalSims}s best={bestIdx} wr={root.WinRate(bestIdx):P0}");
return candidates[bestIdx].play;
}
// ---- 快速模拟(用数组而非对象) ----
private bool SimPass(TCardInfoPdkF[] myHand, PdkBotView view, byte[] o1Ids, byte[] o2Ids)
{
int myPos = view.MyPos;
var maxPlay = view.MaxPlayCard;
int maxPlayer = maxPlay.Pos > 0 ? maxPlay.Pos - 1 : myPos - 1;
int current = myPos % 3;
// 从 ID 数组构建手牌 GameNum 数组
var mg = new List(myHand.Where(c => c.GameState == 1).Select(c => c.GameNum));
var o1g = new List(IdsToGameNums(o1Ids));
var o2g = new List(IdsToGameNums(o2Ids));
return FastSim(o1g, o2g, mg, current, maxPlay.GameNum, maxPlay.Type, maxPlayer, 0);
}
private bool SimAfterPlay(TCardInfoPdkF[] myRem, PdkBotView view, byte[] o1Ids, byte[] o2Ids, byte myGameNum)
{
int myPos = view.MyPos;
int current = myPos % 3;
var mg = new List(myRem.Where(c => c.GameState == 1).Select(c => c.GameNum));
var o1g = new List(IdsToGameNums(o1Ids));
var o2g = new List(IdsToGameNums(o2Ids));
return FastSim(o1g, o2g, mg, current, myGameNum, CardType1.DanZhang, myPos - 1, 0);
}
// 快速模拟核心:全部用 byte[]。支持单张+对子。
private bool FastSim(List h1, List h2, List my,
int current, byte maxGameNum, CardType1 maxType, int maxPlayer, int depth)
{
if (depth > 30) return my.Count <= Math.Min(h1.Count, h2.Count);
if (h1.Count == 0) return false;
if (h2.Count == 0) return false;
if (my.Count == 0) return true;
var cur = current == 0 ? h1 : current == 1 ? h2 : my;
bool isNewRound = maxPlayer == current || maxGameNum == 0;
if (isNewRound)
{
if (cur.Count == 0) return false;
// 找对子(贪心:出最大的对子)
var groups = cur.GroupBy(g => g).Where(g => g.Count() >= 2).OrderByDescending(g => g.Key).ToList();
if (groups.Count > 0)
{
var pair = groups[0];
var toRemove = pair.Take(2).ToList();
foreach (var g in toRemove) cur.Remove(g);
return FastSim(h1, h2, my, (current + 1) % 3, pair.Key, CardType1.DuiZi, current, depth + 1);
}
// 单张
var sorted = cur.OrderByDescending(g => g).ToList();
byte play = sorted[0];
cur.Remove(play);
return FastSim(h1, h2, my, (current + 1) % 3, play, CardType1.DanZhang, current, depth + 1);
}
else
{
// 跟牌:找能压制的同类型牌
var bigger = cur.Where(g => g > maxGameNum).OrderBy(g => g).ToList();
if (maxType == CardType1.DuiZi)
{
// 对子:找 ≥2 张且大于 maxGameNum
var pairGroups = cur.GroupBy(g => g).Where(g => g.Count() >= 2 && g.Key > maxGameNum).OrderBy(g => g.Key).ToList();
if (pairGroups.Count > 0)
{
var pair = pairGroups[0];
var toRemove = pair.Take(2).ToList();
foreach (var g in toRemove) cur.Remove(g);
return FastSim(h1, h2, my, (current + 1) % 3, pair.Key, CardType1.DuiZi, current, depth + 1);
}
}
if (bigger.Count > 0)
{
byte play = bigger[0];
cur.Remove(play);
return FastSim(h1, h2, my, (current + 1) % 3, play, CardType1.DanZhang, current, depth + 1);
}
// pass
return FastSim(h1, h2, my, (current + 1) % 3, maxGameNum, maxType, isNewRound ? current : maxPlayer, depth + 1);
}
}
// ---- 旧的 SimulateToEnd (保留兼容) ----
private bool SimulateToEnd(TCardInfoPdkF[] myHand, PdkBotView view, PlayOutCardPdkF? fp)
{
int myPos = view.MyPos;
var myList = myHand.Where(c => c.GameState == 1).Select(c => c.GameNum).ToList();
int o1c = view.ShenYuCard[myPos % 3];
int o2c = view.ShenYuCard[(myPos + 1) % 3];
var knownIds = new HashSet();
foreach (var c in myHand) knownIds.Add(c.ID);
var pool = FullDeck.Where(c => !knownIds.Contains(c.ID)).Select(c => c.GameNum).ToList();
Shuffle(pool, _rng);
var o1 = pool.Take(o1c).ToList();
var o2 = pool.Skip(o1c).Take(o2c).ToList();
byte maxGn = 0;
int maxP = 0;
int cur = myPos % 3;
if (fp.HasValue && fp.Value.Type != CardType1.None)
{
maxGn = fp.Value.GameNum;
maxP = myPos - 1;
foreach (var id in fp.Value.Ids ?? Array.Empty())
myList.RemoveAll(g => g == GameNumOf(id));
}
else if (fp.HasValue) // pass
{
maxGn = view.MaxPlayCard.GameNum;
maxP = view.MaxPlayCard.Pos > 0 ? view.MaxPlayCard.Pos - 1 : myPos - 1;
}
return FastSim(o1, o2, myList, cur, maxGn, CardType1.DanZhang, maxP, 0);
}
// ---- 辅助 ----
private List<(PlayOutCardPdkF play, bool isMulti)> BuildCandidates(
TCardInfoPdkF[] hand, PdkBotView view, bool isFirstPlay)
{
var tips = PdkCardAlgorithm.GetTipCard(view.MaxPlayCard, hand, null, view.Rule.AAAIsZhaDan);
var candidates = new List<(PlayOutCardPdkF, bool)>();
if (tips != null)
foreach (var t in tips)
candidates.Add((MakePlay(t, view.MyPos), t.Count > 1));
if (isFirstPlay)
AddMultiCardLeads(hand, view.MyPos, candidates);
if (!isFirstPlay)
candidates.Add((Pass(view), false));
return candidates;
}
// 复用 IsmctsBot 的多牌枚举(对子/三张/顺子/飞机/炸弹/连对)
private static void AddMultiCardLeads(TCardInfoPdkF[] hand, int pos,
List<(PlayOutCardPdkF, bool)> candidates)
{
var make = (List cards) => candidates.Add((
new PlayOutCardPdkF { Pos = (byte)pos, GameNum = cards[0].GameNum, Ids = cards.Select(c => c.ID).ToArray(), Type = InferType(cards) }, true));
if (PokerLogic.GetPlayZhaDans(hand, out var bombs) && bombs != null)
foreach (var b in bombs) make(b);
foreach (var g in hand.GroupBy(c => c.GameNum).Where(g => g.Count() >= 2))
make(g.Take(2).ToList());
foreach (var g in hand.GroupBy(c => c.GameNum).Where(g => g.Count() >= 3))
make(g.Take(3).ToList());
var straights = PokerLogic.GetShunZi(hand);
if (straights != null) foreach (var s in straights) make(s);
}
private static CardType1 InferType(List cards)
{
int n = cards.Count;
if (n == 1) return CardType1.DanZhang;
if (n == 2) return CardType1.DuiZi;
var cnts = cards.GroupBy(c => c.GameNum).Select(g => g.Count()).OrderByDescending(x => x).ToList();
if (cnts[0] == 4) { if (n == 4) return CardType1.ZhaDan; if (n == 6) return CardType1.SiDai2; if (n == 7) return CardType1.SiDai3; }
if (cnts[0] >= 3) return CardType1.SanZhang;
if (cnts[0] == 2 && n % 2 == 0) return CardType1.LianDui;
return CardType1.ShunZi;
}
private static PlayOutCardPdkF MakePlay(List cards, int pos)
=> new PlayOutCardPdkF { Pos = (byte)pos, GameNum = cards[0].GameNum, Ids = cards.Select(x => x.ID).ToArray(), Type = CardType1.DanZhang };
private static PlayOutCardPdkF Pass(PdkBotView v)
=> new PlayOutCardPdkF { Pos = (byte)v.MyPos, Type = CardType1.None };
private static List IdsToGameNums(byte[] ids)
{
var result = new List();
foreach (var id in ids)
{
var card = FullDeck.FirstOrDefault(c => c.ID == id);
if (card.GameNum > 0) result.Add(card.GameNum);
}
return result;
}
private static byte GameNumOf(byte id)
{
var card = FullDeck.FirstOrDefault(c => c.ID == id);
return card.GameNum;
}
private static void Shuffle(T[] arr, Random rng)
{
for (int i = arr.Length - 1; i > 0; i--)
{
int j = rng.Next(i + 1);
(arr[i], arr[j]) = (arr[j], arr[i]);
}
}
private static void Shuffle(List list, Random rng)
{
for (int i = list.Count - 1; i > 0; i--)
{
int j = rng.Next(i + 1);
(list[i], list[j]) = (list[j], list[i]);
}
}
}
internal class SONode
{
private readonly int[] _wins;
private readonly int[] _visits;
public SONode(int numChildren)
{
_wins = new int[numChildren];
_visits = new int[numChildren];
}
public void Visit(int idx, bool win)
{
if (idx < 0 || idx >= _visits.Length) return;
_visits[idx]++;
if (win) _wins[idx]++;
}
public double WinRate(int idx)
=> idx >= 0 && idx < _visits.Length && _visits[idx] > 0 ? (double)_wins[idx] / _visits[idx] : 0;
public int VisitCount(int idx)
=> idx >= 0 && idx < _visits.Length ? _visits[idx] : 0;
public int BestChild(int n)
{
int best = -1;
double bestRate = -1;
for (int i = 0; i < Math.Min(n, _visits.Length); i++)
{
double rate = WinRate(i);
if (rate > bestRate) { bestRate = rate; best = i; }
}
return best;
}
}
}