# 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 策略可插拔 ```csharp 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` 中设置: ```csharp 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 论文: --- ## 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.cs` → `PdkFriendServer/Logic/` 2. `PdkFriendServer.csproj` 加: ```xml 8.0 ``` 3. `PdkGameMain.cs` 的 `WaitWanJiaShuRu()` 加入 BotMode 分支(与 net10.0 逻辑完全相同) --- ## 7. 运行 ```bash 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! ```