diff --git a/PdkFriendServer/Logic/PdkGameMain.cs b/PdkFriendServer/Logic/PdkGameMain.cs
index 2cf2cc4f..da513ebb 100644
--- a/PdkFriendServer/Logic/PdkGameMain.cs
+++ b/PdkFriendServer/Logic/PdkGameMain.cs
@@ -61,7 +61,7 @@ namespace PdkFriendServer.Logic
{
_bots = new IPdkBot[3];
for (int i = 0; i < 3; i++)
- _bots[i] = new SOIsmctsBot();
+ _bots[i] = new IsmctsBot();
}
/// 构建当前玩家的可见视图——只看自己手牌+公开信息
diff --git a/PdkFriendServer/Logic/SOIsmctsBot.cs b/PdkFriendServer/Logic/SOIsmctsBot.cs
index ac443bd1..ffb937cf 100644
--- a/PdkFriendServer/Logic/SOIsmctsBot.cs
+++ b/PdkFriendServer/Logic/SOIsmctsBot.cs
@@ -9,12 +9,12 @@ using GameMessage.PaoDeKuaiF;
namespace PdkFriendServer.Logic
{
///
- /// SO-ISMCTS v2 — 优化版,静态牌池+数组交换替代对象分配。
+ /// SO-ISMCTS v4 — 引擎级 rollout,支持全部牌型。
///
- /// 性能关键:
- /// - 48张牌静态数组,模拟时只交换索引不创建新对象
- /// - 预生成N份对手手牌分布,分摊 determinization 成本
- /// - 贪心 rollout 直接操作数组切片
+ /// 核心改进:用 PdkCardAlgorithm.GetTipCard 替代字节级贪心模拟,
+ /// 支持单张/对子/三张/三带二/顺子/连对/飞机/炸弹/四带二/飞机带对。
+ ///
+ /// Rollout 策略:优先清牌效率(多牌优先),但保留大牌控制权。
///
public class SOIsmctsBot : IPdkBot
{
@@ -22,17 +22,17 @@ namespace PdkFriendServer.Logic
private readonly Random _rng;
private readonly CardTracker _tracker = new CardTracker();
- // 静态牌池:52张牌(同 IsmctsBot.GenerateDeck),只创建一次
+ // 静态牌池:52张牌,只创建一次,ID→card 快速查找
private static readonly TCardInfoPdkF[] FullDeck;
static SOIsmctsBot()
{
- FullDeck = new TCardInfoPdkF[52];
+ FullDeck = new TCardInfoPdkF[53]; // 1-indexed
int id = 1;
foreach (int f in new[] { 1, 2, 3, 4 })
for (int n = 1; n <= 13; n++)
- FullDeck[id - 1] = new TCardInfoPdkF
+ FullDeck[id] = new TCardInfoPdkF
{
- ID = (byte)(id++),
+ ID = (byte)id,
Flower = (byte)f,
GameNum = (byte)(n == 1 ? 14 : n == 2 ? 16 : n),
GameState = 1
@@ -54,10 +54,10 @@ namespace PdkFriendServer.Logic
int wins = 0;
for (int s = 0; s < 400; s++)
- if (SimulateToEnd(view.MyHand, view, null)) wins++;
+ if (Rollout(view.MyHand, view, null)) wins++;
double wr = (double)wins / 400;
- Console.WriteLine($"[SOv2 pos{view.MyPos}] 包庄: min={minPlays} wr={wr:P0} → {(wr >= threshold ? "包" : "不")}");
+ Console.WriteLine($"[SOv4 pos{view.MyPos}] 包庄: min={minPlays} wr={wr:P0} → {(wr >= threshold ? "包" : "不")}");
return wr >= threshold;
}
@@ -67,20 +67,18 @@ namespace PdkFriendServer.Logic
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}步");
+ Console.WriteLine($"[SOv4 pos{view.MyPos}] LOCK {lockSeq.Count}步");
return new PlayOutCardPdkF { Pos = (byte)view.MyPos, GameNum = lockSeq[0].GameNum, Ids = lockSeq[0].Ids, Type = CardType1.None };
}
}
@@ -89,223 +87,176 @@ namespace PdkFriendServer.Logic
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;
+ // 预生成对手手牌 (TCardInfoPdkF)
+ var knownIds = new HashSet();
+ foreach (var c in hand) knownIds.Add(c.ID);
+ var poolCards = FullDeck.Where(c => c.ID > 0 && !knownIds.Contains(c.ID)).ToList();
+
+ int preGen = Math.Min(200, _simBudget);
+ var preHands1 = new TCardInfoPdkF[preGen][];
+ var preHands2 = new TCardInfoPdkF[preGen][];
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);
+ Shuffle(poolCards, _rng);
+ preHands1[g] = poolCards.Take(o1c).ToArray();
+ preHands2[g] = poolCards.Skip(o1c).Take(o2c).ToArray();
}
- int genIdx = 0;
- int totalSims = 0;
+ int genIdx = 0, totalSims = 0;
for (int iter = 0; iter < _simBudget; iter++)
{
int ci = _rng.Next(candidates.Count);
- var play = candidates[ci];
+ var (play, isMulti) = 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);
- }
+ // 用预生成的对手手牌
+ genIdx = (genIdx + 1) % preGen;
+ bool win = Rollout(hand, view, play, preHands1[genIdx], preHands2[genIdx]);
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}");
+ Console.WriteLine($"[SOv4 pos{view.MyPos}] {totalSims}s best={bestIdx}/{candidates.Count} wr={root.WinRate(bestIdx):P0}");
return candidates[bestIdx].play;
}
- // ---- 快速模拟(用数组而非对象) ----
+ // ---- Rollout: 引擎级全牌型模拟 ----
- private bool SimPass(TCardInfoPdkF[] myHand, PdkBotView view, byte[] o1Ids, byte[] o2Ids)
+ ///
+ /// Rollout 一局到终局。firstPlay=null 时不改状态直接从当前桌面开始。
+ ///
+ private bool Rollout(TCardInfoPdkF[] myHand, PdkBotView view,
+ PlayOutCardPdkF? myPlay = null,
+ TCardInfoPdkF[] o1Hand = null, TCardInfoPdkF[] o2Hand = null)
{
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);
- }
-
- // 快速模拟核心:清牌效率优先。
- // 领牌:优先出多牌组合(3张>2张>1张),用最小的 rank 保留大牌
- // 跟牌:出最小的能压制的牌,保留大牌
- 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;
- bool isMe = (current == 2);
-
- if (isNewRound)
+ var my = myHand.Where(c => c.GameState == 1).ToList();
+ if (myPlay.HasValue && myPlay.Value.Type != CardType1.None && myPlay.Value.Ids != null)
{
- if (cur.Count == 0) return false;
+ foreach (var id in myPlay.Value.Ids) my.RemoveAll(c => c.ID == id);
+ if (my.Count == 0) return true;
+ }
- // 清牌效率排序: 三张 > 对子 > 单张。始终用最小的 rank
- var groups = cur.GroupBy(g => g).OrderBy(g => g.Key).ToList();
-
- // 三张(清 3 张)
- var triples = groups.Where(g => g.Count() >= 3).ToList();
- if (triples.Count > 0)
- {
- var t = triples[0];
- var toRemove = t.Take(3).ToList();
- foreach (var g in toRemove) cur.Remove(g);
- return FastSim(h1, h2, my, (current + 1) % 3, t.Key, CardType1.SanZhang, current, depth + 1);
- }
-
- // 对子(清 2 张)
- var pairs = groups.Where(g => g.Count() >= 2).ToList();
- if (pairs.Count > 0)
- {
- var p = pairs[0];
- var toRemove = p.Take(2).ToList();
- foreach (var g in toRemove) cur.Remove(g);
- return FastSim(h1, h2, my, (current + 1) % 3, p.Key, CardType1.DuiZi, current, depth + 1);
- }
-
- // 单张:出最小的
- byte play = groups[0].Key;
- cur.Remove(play);
- return FastSim(h1, h2, my, (current + 1) % 3, play, CardType1.DanZhang, current, depth + 1);
+ // 构建或取样对手手牌
+ List o1, o2;
+ if (o1Hand != null)
+ {
+ o1 = new List(o1Hand);
+ o2 = new List(o2Hand);
}
else
{
- // 跟牌:有牌能压 → 出最小的压制牌
- if (maxType == CardType1.DuiZi || maxType == CardType1.SanZhang)
- {
- int need = maxType == CardType1.SanZhang ? 3 : 2;
- var groups = cur.GroupBy(g => g)
- .Where(g => g.Count() >= need && g.Key > maxGameNum)
- .OrderBy(g => g.Key).ToList();
- if (groups.Count > 0)
- {
- var g = groups[0];
- var toRemove = g.Take(need).ToList();
- foreach (var x in toRemove) cur.Remove(x);
- return FastSim(h1, h2, my, (current + 1) % 3, g.Key, maxType, current, depth + 1);
- }
- }
-
- // 单张跟牌:出最小的可压牌
- var bigger = cur.Where(g => g > maxGameNum).OrderBy(g => g).ToList();
- 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);
+ var known = new HashSet();
+ foreach (var c in myHand) known.Add(c.ID);
+ var pool = FullDeck.Where(c => c.ID > 0 && !known.Contains(c.ID)).ToList();
+ Shuffle(pool, _rng);
+ int o1c = view.ShenYuCard[myPos % 3];
+ o1 = pool.Take(o1c).ToList();
+ o2 = pool.Skip(o1c).Take(view.ShenYuCard[(myPos + 1) % 3]).ToList();
}
+
+ // 初始状态
+ PlayOutCardPdkF maxPlay;
+ int maxPlayer, current;
+
+ if (myPlay.HasValue && myPlay.Value.Type != CardType1.None)
+ {
+ maxPlay = ClonePlay(myPlay.Value);
+ maxPlayer = myPos - 1;
+ current = myPos % 3;
+ }
+ else
+ {
+ maxPlay = ClonePlay(view.MaxPlayCard);
+ maxPlayer = maxPlay.Pos > 0 ? maxPlay.Pos - 1 : myPos - 1;
+ current = myPos % 3;
+ }
+
+ return SimLoop(my, o1, o2, current, maxPlay, maxPlayer, 0);
}
- // ---- 旧的 SimulateToEnd (保留兼容) ----
-
- private bool SimulateToEnd(TCardInfoPdkF[] myHand, PdkBotView view, PlayOutCardPdkF? fp)
+ private bool SimLoop(List my, List o1, List o2,
+ int current, PlayOutCardPdkF maxPlay, int maxPlayer, int depth)
{
- 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];
+ if (depth > 50) return my.Count <= Math.Min(o1.Count, o2.Count);
+ if (o1.Count == 0) return false;
+ if (o2.Count == 0) return false;
+ if (my.Count == 0) return true;
- var knownIds = new HashSet();
- foreach (var c in myHand) knownIds.Add(c.ID);
+ var cur = current == 0 ? o1 : current == 1 ? o2 : my;
+ bool isNewRound = maxPlayer == current || maxPlay.GameNum <= 0;
+ int next = (current + 1) % 3;
- 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();
+ // 用引擎获取合法出牌
+ var tips = PdkCardAlgorithm.GetTipCard(
+ isNewRound ? new PlayOutCardPdkF() : maxPlay,
+ cur.ToArray(), null, false);
- byte maxGn = 0;
- int maxP = 0;
- int cur = myPos % 3;
-
- if (fp.HasValue && fp.Value.Type != CardType1.None)
+ if (tips != null && tips.Count > 0)
{
- 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;
+ // Rollout 策略:多牌优先 + 最小 rank 保留大牌
+ // isNewRound 时 GetTipCard 只返回单张 → 需要补充多牌先手
+ List> allPlays = new List>(tips);
+ if (isNewRound)
+ {
+ var arr = cur.ToArray();
+ // 对子 (2张同rank)
+ foreach (var g in arr.GroupBy(c => c.GameNum).Where(g => g.Count() >= 2))
+ allPlays.Add(g.Take(2).ToList());
+ // 三张
+ foreach (var g in arr.GroupBy(c => c.GameNum).Where(g => g.Count() >= 3))
+ allPlays.Add(g.Take(3).ToList());
+ // 顺子 (≥5张连续rank)
+ var ranks = arr.Select(c => (int)c.GameNum).Where(r => r >= 3 && r <= 14).Distinct().OrderBy(r => r).ToList();
+ for (int len = Math.Min(ranks.Count, 12); len >= 5; len--)
+ for (int si = 0; si <= ranks.Count - len; si++)
+ {
+ bool isConsecutive = true;
+ for (int i = 1; i < len; i++)
+ if (ranks[si + i] != ranks[si + i - 1] + 1)
+ { isConsecutive = false; break; }
+ if (!isConsecutive) continue;
+ var strCards = ranks.Skip(si).Take(len)
+ .SelectMany(r => arr.Where(c => c.GameNum == r).Take(1)).ToList();
+ if (strCards.Count == len) allPlays.Add(strCards);
+ }
+ }
+
+ // 选择:优先多牌(×10权重),同长度选最小rank
+ var best = allPlays
+ .OrderByDescending(t => t.Count * 10)
+ .ThenBy(t => t[0].GameNum)
+ .First();
+
+ foreach (var c in best) cur.RemoveAll(x => x.ID == c.ID);
+
+ var newMax = new PlayOutCardPdkF
+ {
+ GameNum = best[0].GameNum,
+ Type = InferType(best),
+ Ids = best.Select(c => c.ID).ToArray()
+ };
+
+ return SimLoop(my, o1, o2, next, newMax, current, depth + 1);
}
- return FastSim(o1, o2, myList, cur, maxGn, CardType1.DanZhang, maxP, 0);
+ // pass
+ int newMaxPlayer = isNewRound ? current : maxPlayer;
+ return SimLoop(my, o1, o2, next, maxPlay, newMaxPlayer, depth + 1);
}
- // ---- 辅助 ----
+ // ---- 候选构建 ----
private List<(PlayOutCardPdkF play, bool isMulti)> BuildCandidates(
TCardInfoPdkF[] hand, PdkBotView view, bool isFirstPlay)
@@ -314,7 +265,7 @@ namespace PdkFriendServer.Logic
var candidates = new List<(PlayOutCardPdkF, bool)>();
if (tips != null)
foreach (var t in tips)
- candidates.Add((MakePlay(t, view.MyPos), t.Count > 1));
+ candidates.Add((new PlayOutCardPdkF { Pos = (byte)view.MyPos, GameNum = t[0].GameNum, Ids = t.Select(c => c.ID).ToArray(), Type = InferType(t) }, t.Count > 1));
if (isFirstPlay)
AddMultiCardLeads(hand, view.MyPos, candidates);
if (!isFirstPlay)
@@ -322,23 +273,28 @@ namespace PdkFriendServer.Logic
return candidates;
}
- // 复用 IsmctsBot 的多牌枚举(对子/三张/顺子/飞机/炸弹/连对)
private static void AddMultiCardLeads(TCardInfoPdkF[] hand, int pos,
List<(PlayOutCardPdkF, bool)> candidates)
{
- var make = (List cards) => candidates.Add((
+ void add(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 b in bombs) add(b);
foreach (var g in hand.GroupBy(c => c.GameNum).Where(g => g.Count() >= 2))
- make(g.Take(2).ToList());
+ add(g.Take(2).ToList());
foreach (var g in hand.GroupBy(c => c.GameNum).Where(g => g.Count() >= 3))
- make(g.Take(3).ToList());
+ {
+ var t = g.Take(3).ToList();
+ var rest = hand.Where(c => c.GameNum != g.Key).Take(2).ToList();
+ add(rest.Count == 2 ? t.Concat(rest).ToList() : t);
+ }
var straights = PokerLogic.GetShunZi(hand);
- if (straights != null) foreach (var s in straights) make(s);
+ if (straights != null) foreach (var s in straights) add(s);
}
+ // ---- 辅助 ----
+
private static CardType1 InferType(List cards)
{
int n = cards.Count;
@@ -346,86 +302,36 @@ namespace PdkFriendServer.Logic
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;
+ if (cnts[0] >= 3) return n == 3 ? CardType1.SanZhang : CardType1.SanDai2;
+ if (cnts[0] == 2 && n % 2 == 0) return n == 4 ? CardType1.LianDui : CardType1.LianDui;
+ return n >= 5 ? CardType1.ShunZi : CardType1.DanZhang;
}
- 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 PlayOutCardPdkF ClonePlay(PlayOutCardPdkF s)
+ => new PlayOutCardPdkF { Pos = s.Pos, GameNum = s.GameNum, Type = s.Type, Ids = s.Ids?.ToArray() };
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]);
- }
+ { 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;
-
+ private readonly int[] _wins, _visits;
+ public SONode(int n) { _wins = new int[n]; _visits = new int[n]; }
+ public void Visit(int i, bool w) { if (i >= 0 && i < _visits.Length) { _visits[i]++; if (w) _wins[i]++; } }
+ public double WinRate(int i) => i >= 0 && i < _visits.Length && _visits[i] > 0 ? (double)_wins[i] / _visits[i] : 0;
+ public int VisitCount(int i) => i >= 0 && i < _visits.Length ? _visits[i] : 0;
public int BestChild(int n)
{
- int best = -1;
- double bestRate = -1;
+ 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; }
- }
+ { double r = WinRate(i); if (r > bestRate) { bestRate = r; best = i; } }
return best;
}
}
diff --git a/exe/PdkFriendServer/PdkFriendServer.dll b/exe/PdkFriendServer/PdkFriendServer.dll
index c5e54891..79572a14 100644
Binary files a/exe/PdkFriendServer/PdkFriendServer.dll and b/exe/PdkFriendServer/PdkFriendServer.dll differ
diff --git a/exe/PdkFriendServer/PdkFriendServer.pdb b/exe/PdkFriendServer/PdkFriendServer.pdb
index 77616822..5e3632c5 100644
Binary files a/exe/PdkFriendServer/PdkFriendServer.pdb and b/exe/PdkFriendServer/PdkFriendServer.pdb differ
diff --git a/exe/hjha-console/PdkFriendServer.dll b/exe/hjha-console/PdkFriendServer.dll
index c5e54891..79572a14 100644
Binary files a/exe/hjha-console/PdkFriendServer.dll and b/exe/hjha-console/PdkFriendServer.dll differ
diff --git a/exe/hjha-console/PdkFriendServer.pdb b/exe/hjha-console/PdkFriendServer.pdb
index 77616822..5e3632c5 100644
Binary files a/exe/hjha-console/PdkFriendServer.pdb and b/exe/hjha-console/PdkFriendServer.pdb differ
diff --git a/exe/hjha-console/hjha-console.dll b/exe/hjha-console/hjha-console.dll
index bc5eaa0d..664b4eac 100644
Binary files a/exe/hjha-console/hjha-console.dll and b/exe/hjha-console/hjha-console.dll differ
diff --git a/exe/hjha-console/hjha-console.pdb b/exe/hjha-console/hjha-console.pdb
index bf0b43e9..7a6b3051 100644
Binary files a/exe/hjha-console/hjha-console.pdb and b/exe/hjha-console/hjha-console.pdb differ