Files
hjha-server/docs/pdk-bot.md
xiaoou 13fe07ea11 chore: 清理冗余 + 文档更新至v3.1 + 打包清单
清理:
  - 删除 C:\Users\... Windows残留路径
  - analyze_stress.py → docs/
  - .gitignore 覆盖 stress_* runtime产物

文档更新:
  - pdk-bot.md: 包庄阈值45%→70%、RolloutPolicy描述、选牌策略
  - code-review.md: 7/7 全部清零
  - PACKAGE.md: 新建打包清单(代码+集成+文档+net48步骤+压测记录)

bot 文件总览:
  核心: PdkBot.cs(194行) + IsmctsBot.cs(331行) = 525行
  文档: pdk-bot.md + code-review.md + stress-test-report.md + PACKAGE.md
2026-07-07 14:56:43 +08:00

10 KiB
Raw Permalink Blame History

PdkBot — 跑得快自动出牌机器人

1. 概述与架构

1.1 项目定位

PdkBot 为 hjha-server 跑得快游戏提供全自动对局能力。三个机器人在 Console 模式下相互对弈,模拟真人玩家的出牌决策。

1.2 外挂式架构(非侵入)

PdkGameMain.WaitWanJiaShuRu()
  ├─ BotMode == false → Console.ReadLine()(人工输入)
  └─ BotMode == true  → PdkBotView → IPdkBot.DecidePlay()

机器人只在两个决策点介入:

  • case 3 包庄DecideBaoZhuang(view)
  • case 4 打牌DecidePlay(view)

完全不修改游戏状态机、规则引擎、发牌逻辑或结算逻辑。

1.3 信息隔离

PdkBotView 严格裁剪游戏状态,机器人只能看到

信息 来源 说明
自己手牌 CardPack.Cards[myPos-1] (GameState==1) 16 张起
桌面最后出牌 Info.MaxPlayCard GameNum==0 表示新一轮
各家剩余张数 Info.ShenYuCard[] 跑得快公开信息
各家炸弹数 Info.ZhaDans[] 公开统计
游戏规则 Rule 关牌/经典/15张、必压等
游戏阶段 Info.GameZT 3=包庄 4=打牌

不能看到:其他玩家的具体手牌 (CardPack.Cards[other].CardInfos)。

1.4 策略可插拔

public interface IPdkBot
{
    bool DecideBaoZhuang(PdkBotView view);
    PlayOutCardPdkF DecidePlay(PdkBotView view);
}
实现 文件 描述
PdkBot PdkBot.cs 5 维启发式评分(快速基线)
IsmctsBot IsmctsBot.cs 蒙特卡洛搜索(当前默认,推荐)

切换:改 PdkGameMain.InitBots() 一行 new IsmctsBot()new PdkBot()

1.5 文件清单

文件 职责
PdkFriendServer/Logic/PdkBot.cs IPdkBot 接口、PdkBotView、PdkBot 启发式实现
PdkFriendServer/Logic/IsmctsBot.cs ISMCTS 蒙特卡洛搜索机器人(默认)
PdkFriendServer/Logic/PdkGameMain.cs BotMode 开关、BuildBotView、WaitWanJiaShuRu 集成点
hjha-console/Program.cs main.BotMode = true 启动入口

2. 算法说明

2.1 三条路径对比

A: 启发式 (PdkBot) B: ISMCTS (IsmctsBot) C: CNN+MCTS
最优性 高(接近最优) 理论最高
延迟 <1ms 50-200ms 10-50ms
代码量 100 行 300 行 1000+ 行 + 训练管道
训练需求 4 GPU × 2-5 天
预训练模型 不需要 不需要 不存在
状态 已实现 已实现(默认) 未实现

2.2 PdkBot启发式评分已实现非默认

GetTipCard() 返回的每个合法出牌方案打分:

ScorePlay():
  1. 直接获胜(remainingAfter==0)       +10000
  2. 对手威胁(left/right ≤3)          +300/500
  3. 牌效率(剩余越少分越高)             (20-剩余)×15
  4. 炸弹管理(对手快赢+400/自己快赢+200/留着-80)
  5. 出牌量(一次出多张更好)             cardsOut×8
  6. 先手权(非pass)                    +60
  7. 黑桃3首局必出                      +150

ScorePass():
  基础 -80, 对手快赢追加 -400

2.3 IsmctsBot蒙特卡洛搜索当前默认

核心原理

不完全信息蒙特卡洛树搜索——对每个候选出牌方案,模拟 N 局随机对局到结束,统计胜率,选胜率最高的方案。

DecidePlay(view):
  unknownPool = 全部48张 - 我的手牌 - 已知已出牌

  for each candidate in GetTipCard():
    模拟 800 局:
      ├─ 对手手牌从 unknownPool 随机采样
      ├─ 应用我的候选出牌
      └─ 三人贪心策略(RolloutPolicy)打到终局
    胜率 = 我获胜的局数 / 800

  return 胜率最高的方案

RolloutPolicy模拟策略:用 PickBestTip 选最优方案(优先多牌组合 + 最大牌),模拟更强的对手行为。大量采样后 ISMCTS 胜率统计收敛。

包庄决策

DecideBaoZhuang(view):
  模拟 600 局完整对局 → 统计我第一个出完的概率 P(win)
  P(win) ≥ 70% → 包庄

阈值校准历程: 45%v1→ 60%v2成功率 30%)→ 70%v3.1,成功率 57%。100 局压测验证70% 阈值下包庄成功率翻倍。

选牌策略

两阶段选择:

  1. ISMCTS 评估所有候选胜率
  2. 多牌组合 +10%/张加权 → 优先出对子/顺子/三带二等
  3. 多牌 ≥ 单张 → 无条件优先多牌

性能

模拟次数 延迟 胜率估计精度
200 ~30ms ±7%
800 ~100ms ±3.5%
2000 ~250ms ±2%

当前默认 800 次。


3. 引擎整合(对原项目的最小修改)

3.1 修改清单

文件 行号 改动 原因
PdkGameMain.cs L46-51 + IPdkBot[] _bots / + BotMode bot 注入点
PdkGameMain.cs L60-66 + InitBots() 懒初始化 3 个 IsmctsBot
PdkGameMain.cs L68-82 + BuildBotView(pos) 仅取可见信息构建视图
PdkGameMain.cs L279 GamePack.Info.WhoPlay = (byte)(i+1) 包庄轮询时修正位置
PdkGameMain.cs L543 + if(DeskGameDo==null) return; Console 模式 NPE 守卫
PdkGameMain.cs L632-710 WaitWanJiaShuRu 加 bot 分支 bot/人工双通道
PdkGameMain.cs L491-494 C:\Users\..../gamepack/ 跨平台路径
Program.cs L14 main.BotMode = true 启动时开启 bot

3.2 未修改的部分

以下完全不动:

  • 游戏状态机GameFaPai → GameBaoZhuang → GameDaPai → GameOver
  • 规则引擎PdkCardAlgorithm、PdkRule
  • 发牌逻辑GameFaPai
  • 结算逻辑GameOver
  • 所有网络/服务器模块

3.3 配置

Program.cs 中设置:

var main = new PdkGameMain(null);
main.BotMode = true;   // true=自动对局, false=Console.ReadLine
main.Test();           // 3 人自动打到结算

4. 未来优化

4.1 短期(提升胜率)

方向 描述 难度
RolloutPolicy 加强 模拟中用启发式代替纯随机最小牌,提高每局模拟质量
自适应模拟次数 手牌多时 1000 次,手牌少时减少到 200 次
对手建模 记录对手出牌倾向(激进/保守),调整模拟中的对手策略
包庄阈值学习 跑 1000 局统计分析最优 P(win) 阈值

4.2 中期(引擎能力)

方向 描述 难度
支持更多玩法 经典玩法16 张必压、15 张玩法,通过 Rule 参数切换
AI vs 人类 将 BotMode 扩展到单个位置(如 pos1=人类, pos2/3=bot
回放分析 每局结束后输出 .json 回放文件,分析 bot 决策质量
性能基准测试 跑 1000 局自对弈统计 PdkBot vs IsmctsBot 胜率

4.3 长期C 路径CNN + MCTS

方向 描述 依赖
Deep Monte Carlo 借鉴 DouZero (ICML 2021) 方法CNN 编码手牌 + MCTS 搜索 4 GPU × 3 天训练
ONNX 推理 PyTorch → ONNX → ML.NET 加载,嵌入 C# 推理 Python 训练管道
自对弈训练 跑得快状态/动作编码,自我对弈产数据 → CNN 策略网络 RLCard/RLlib

C 路径的技术可行性已验证DouZero 在斗地主上击败所有 344 个 AI 对手,排名 Botzone 第一),但目前不存在跑得快的预训练模型,需要从零训练。详见 DouZero 论文: https://arxiv.org/abs/2106.06135


5. 牌型全覆盖

引擎 GetOutCard 已验证支持所有牌型bot AddMultiCardLeads + InferType 实现完整枚举:

牌型 引擎 Bot 先手 Bot 跟牌 说明
单张 DanZhang GetTipCard GetTipCard
对子 DuiZi GetMultiGroups(2) GetTipCard
顺子 ShunZi PokerLogic.GetShunZi GetTipCard ≥5 张连续
连对 LianDui GetConsecutiveRuns GetTipCard ≥3 对连续
三带二 SanDai2 三张+最小2张 GetTipCard 附牌不限对子
四带二 SiDai2 炸弹+最小2张 关牌规则
四带三 SiDai3 炸弹+最小3张 关牌规则
炸弹 ZhaDan GetPlayZhaDans GetTipCard 4 张相同
飞机 FeiJi GetConsecutiveRuns GetTipCard ≥2 组连续三张
飞机带对 FeijiDai2 飞机+附对子 GetTipCard

附牌策略

引擎 IsSanDaiEr / IsSiDai2 / IsSiDai3 均不要求附牌为对子——允许任意单牌挂件。Bot 取手牌最小剩余牌做附牌,最大化清牌效率。


6. 双环境兼容(.NET 4.8 / .NET 10

6.1 已处理

特性 .NET 4.8 .NET 10 方案
C#8 switch expression 改为 ternary
C#9 { get; init; } 改为 { get; set; }
ValueTuple ⚠️ NuGet net48 加 System.ValueTuple
LINQ 无需改动
string 插值 $"..." C#6 无需改动
?. / ?? C#6 无需改动

6.2 net48 项目集成步骤

  1. 复制 PdkBot.cs + IsmctsBot.csPdkFriendServer/Logic/
  2. PdkFriendServer.csproj 加:
    <PackageReference Include="System.ValueTuple" Version="4.5.0" />
    <LangVersion>8.0</LangVersion>
    
  3. PdkGameMain.csWaitWanJiaShuRu() 加入 BotMode 分支(与 net10.0 逻辑完全相同)

7. 运行

cd ~/projects/hjha-server
dotnet run --project hjha-console

输出示例:

=== HJHA Console Mode - 跑得快 Bot对局 ===
[ISMCTS pos1] 包庄评估: P(win)=31% → 不包
[ISMCTS pos2] 包庄评估: P(win)=52% → 包庄
[ISMCTS pos3] 包庄评估: P(win)=63% → 包庄
[ISMCTS pos2] 800sims winRate:3% => DanZhang:1c
[ISMCTS pos3] 800sims winRate:5% => DanZhang:1c
玩家1 输赢32
玩家2 ,输赢:-64
玩家3 输赢32
test over