清理: - 删除 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
10 KiB
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% 阈值下包庄成功率翻倍。
选牌策略
两阶段选择:
- ISMCTS 评估所有候选胜率
- 多牌组合 +10%/张加权 → 优先出对子/顺子/三带二等
- 多牌 ≥ 单张 → 无条件优先多牌
性能
| 模拟次数 | 延迟 | 胜率估计精度 |
|---|---|---|
| 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 项目集成步骤
- 复制
PdkBot.cs+IsmctsBot.cs→PdkFriendServer/Logic/ PdkFriendServer.csproj加:<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <LangVersion>8.0</LangVersion>PdkGameMain.cs的WaitWanJiaShuRu()加入 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!