feat: CardTracker — 出牌记录+对手手牌推断+炸弹检测
CardTracker.cs (新文件, 400行): - 48位bitset追踪已出牌(按ID) - 对手pass约束记录: pass对子9→该对手无对子≥9 - BombsOutside(): 外面还剩几个炸弹 - BombRanks(): 可能藏炸弹的rank列表 - CanAnyoneBeat(play): 判断是否可能被压制 - SampleOpponentHands(): 约束采样替代随机分配 - 支持6种牌型的压制判断: 单张/对子/炸弹/顺子/连对/飞机 IsmctsBot.cs 整合: - CardTracker实例,每次DecidePlay/DecideBaoZhuang调用Update() - 炸弹感知加权: 外面没炸弹→我方炸弹+25%, 快赢时+15% - 外面有炸弹且快赢→保守出牌(-5%多牌分) - SimulateToEnd改用约束采样(回退随机补齐)
This commit is contained in:
@ -18,32 +18,34 @@ namespace PdkFriendServer.Logic
|
||||
{
|
||||
private readonly int _simulations;
|
||||
private readonly Random _rng;
|
||||
private const double BaoThreshold = 0.70; // 100局校准: 60%→30%, 提至70%
|
||||
private const double MultiBonusPerCard = 0.10; // 多牌加权: 每多1张+10%
|
||||
private const int FastSims = 40; // 快速评估模拟次数
|
||||
private readonly CardTracker _tracker = new CardTracker();
|
||||
private const double BaoThreshold = 0.70;
|
||||
private const double MultiBonusPerCard = 0.10;
|
||||
private const int FastSims = 40;
|
||||
|
||||
public IsmctsBot(int simulations = 800)
|
||||
{
|
||||
_simulations = simulations;
|
||||
_rng = new Random();
|
||||
}
|
||||
public IsmctsBot(int simulations = 800)
|
||||
{
|
||||
_simulations = simulations;
|
||||
_rng = new Random();
|
||||
}
|
||||
|
||||
// ---- 自适应模拟次数 ----
|
||||
private int AdaptiveSims(int handSize)
|
||||
{
|
||||
if (handSize <= 3) return 2000; // 决胜时刻
|
||||
if (handSize <= 6) return 1500;
|
||||
if (handSize <= 10) return 1200;
|
||||
if (handSize <= 13) return 1000;
|
||||
return 800; // 开局快速
|
||||
}
|
||||
// ---- 自适应模拟次数 ----
|
||||
private int AdaptiveSims(int handSize)
|
||||
{
|
||||
if (handSize <= 3) return 2000;
|
||||
if (handSize <= 6) return 1500;
|
||||
if (handSize <= 10) return 1200;
|
||||
if (handSize <= 13) return 1000;
|
||||
return 800;
|
||||
}
|
||||
|
||||
// ---- 包庄决策 ----
|
||||
|
||||
public bool DecideBaoZhuang(PdkBotView view)
|
||||
{
|
||||
// 最小步数规划:手牌结构好不好
|
||||
int minPlays = HandOptimizer.MinPlays(view.MyHand);
|
||||
public bool DecideBaoZhuang(PdkBotView view)
|
||||
{
|
||||
_tracker.Update(view);
|
||||
|
||||
int minPlays = HandOptimizer.MinPlays(view.MyHand);
|
||||
double handEfficiency = view.MyHand.Length > 0
|
||||
? (double)(view.MyHand.Length - minPlays) / view.MyHand.Length : 0;
|
||||
|
||||
@ -77,8 +79,9 @@ namespace PdkFriendServer.Logic
|
||||
// ---- 出牌决策 ----
|
||||
|
||||
public PlayOutCardPdkF DecidePlay(PdkBotView view)
|
||||
{
|
||||
var hand = view.MyHand;
|
||||
{
|
||||
_tracker.Update(view);
|
||||
var hand = view.MyHand;
|
||||
if (hand.Length == 0) return Pass(view);
|
||||
|
||||
bool isFirstPlay = view.MaxPlayCard.GameNum <= 0;
|
||||
@ -147,7 +150,33 @@ namespace PdkFriendServer.Logic
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 两阶段选择:多牌优先
|
||||
// 5. 炸弹感知加权
|
||||
int bombsOutside = _tracker.BombsOutside(hand);
|
||||
var bombRanks = _tracker.BombRanks(hand);
|
||||
|
||||
for (int i = 0; i < scoredCandidates.Count; i++)
|
||||
{
|
||||
var (p, wr, am, m) = scoredCandidates[i];
|
||||
bool isBomb = p.Ids != null && p.Ids.Length == 4
|
||||
&& hand.Count(c => c.GameNum == p.GameNum && c.GameState == 1) == 4;
|
||||
|
||||
if (isBomb)
|
||||
{
|
||||
// 我方炸弹
|
||||
if (bombsOutside == 0) wr += 0.25; // 外面没炸弹 → 我方炸弹无敌
|
||||
else if (am <= 3) wr += 0.15; // 快赢了,果断炸
|
||||
else wr -= 0.05; // 先留着
|
||||
}
|
||||
else if (bombsOutside > 0 && am <= 3)
|
||||
{
|
||||
// 外面有炸弹但我快赢了 → 出牌保守,少加多牌分
|
||||
if (m) wr -= 0.05;
|
||||
}
|
||||
|
||||
scoredCandidates[i] = (p, wr, am, m);
|
||||
}
|
||||
|
||||
// 6. 两阶段选择:多牌优先
|
||||
scoredCandidates.Sort((a, b) => b.winRate.CompareTo(a.winRate));
|
||||
var best = scoredCandidates[0];
|
||||
|
||||
@ -189,9 +218,23 @@ namespace PdkFriendServer.Logic
|
||||
|
||||
var allCards = GenerateDeck();
|
||||
var unknown = allCards.Where(c => !myHand.Any(h => h.ID == c.ID)).ToList();
|
||||
Shuffle(unknown);
|
||||
hands[nextPos - 1].AddRange(unknown.Take(view.ShenYuCard[nextPos - 1]));
|
||||
hands[oppPos - 1].AddRange(unknown.Skip(view.ShenYuCard[nextPos - 1]).Take(view.ShenYuCard[oppPos - 1]));
|
||||
|
||||
// 使用 CardTracker 进行约束采样
|
||||
var sampled = _tracker.SampleOpponentHands(myHand, view, _rng);
|
||||
hands[nextPos - 1].AddRange(sampled[0]);
|
||||
hands[oppPos - 1].AddRange(sampled[1]);
|
||||
|
||||
// 如果约束采样没填满,用随机采样补齐
|
||||
var usedIds = new HashSet<byte>(myHand.Select(c => c.ID));
|
||||
foreach (var c in hands[nextPos - 1]) usedIds.Add(c.ID);
|
||||
foreach (var c in hands[oppPos - 1]) usedIds.Add(c.ID);
|
||||
|
||||
var remaining = unknown.Where(c => !usedIds.Contains(c.ID)).ToList();
|
||||
Shuffle(remaining);
|
||||
int n1Need = view.ShenYuCard[nextPos - 1] - hands[nextPos - 1].Count;
|
||||
int n2Need = view.ShenYuCard[oppPos - 1] - hands[oppPos - 1].Count;
|
||||
if (n1Need > 0) hands[nextPos - 1].AddRange(remaining.Take(n1Need));
|
||||
if (n2Need > 0) hands[oppPos - 1].AddRange(remaining.Skip(n1Need).Take(n2Need));
|
||||
|
||||
int current, maxPlayer;
|
||||
PlayOutCardPdkF maxPlay;
|
||||
|
||||
Reference in New Issue
Block a user