Files
hjha-server/docs/pdk-bot.md
xiaoou 61948049b7 docs: 统一文档 pdk-bot.md + 清理冗余
合并 3 个分散文档为单一 docs/pdk-bot.md:
1. 概述与架构 — 外挂设计/信息隔离/策略可插拔
2. 算法说明 — PdkBot启发式 + IsmctsBot蒙特卡洛 + C路径展望
3. 引擎整合 — 对 PdkGameMain 的 8 处最小改动清单
4. 未来优化 — 短期/中期/长期路线图

删除旧文件: bot-architecture.md bot-algorithms.md ismcts-design.md
清理运行时产物: output.txt gamepack/
2026-07-07 13:34:08 +08:00

229 lines
7.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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模拟策略** `GetTipCard` 取第一个方案最小牌快速推进对局不需要最优——大量采样后统计收敛
#### 包庄决策
```
DecideBaoZhuang(view):
模拟 600 局完整对局 → 统计我第一个出完的概率 P(win)
P(win) ≥ 45% → 包庄
```
**为什么 45% 而不是 50%** 包庄是零和博弈中的多方决策你不包但别人包了你也要扣双倍阈值略微向攻方倾斜以弥补防守的机会成本
#### 性能
| 模拟次数 | 延迟 | 胜率估计精度 |
|---------|------|-------------|
| 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
- 规则引擎PdkCardAlgorithmPdkRule
- 发牌逻辑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 论文: <https://arxiv.org/abs/2106.06135>
---
## 5. 运行
```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
```