清理: - 删除 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
284 lines
10 KiB
Markdown
284 lines
10 KiB
Markdown
# 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 论文: <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.cs` → `PdkFriendServer/Logic/`
|
||
2. `PdkFriendServer.csproj` 加:
|
||
```xml
|
||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||
<LangVersion>8.0</LangVersion>
|
||
```
|
||
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!
|
||
```
|