docs: 全面更新 README + DSL规范

README.md:
- 测试数: 45→58, 玩法: 4→5 (新增南昌)
- 番型: 16→39 种完整清单
- 新增: 精牌类型(fixed/random), 计分倍数, 5种玩法覆盖表
- 新增: 压测结果表, 引擎代码规模

dsl-specification.md:
- scoring 段新增 self_draw/discard_win/dealer 倍数字段
- 已知限制更新: 花牌(完全支持), wildcard type(fixed+random)
- 快速清单新增 wildcard.type + scoring倍数 检查项
This commit is contained in:
xiaoou
2026-07-04 22:02:47 +08:00
parent 42b032339c
commit 359e43ce23
2 changed files with 133 additions and 69 deletions

177
README.md
View File

@ -1,92 +1,145 @@
# Card Game Engine — 棋牌规则引擎
# Card Game Engine — YAML DSL 驱动的麻将规则引擎
YAML DSL 驱动的棋牌规则引擎。一份 DSL 配置文件 = 一种玩法,
引擎加载后自动运行,不需要写玩法特定代码。
一份 YAML 配置文件 = 一种麻将玩法。引擎加载后自动运行,零玩法特定代码。
## 核心理念
**DSL 定义规则,引擎暴露能力。** 玩法变体之间的差异全部通过 DSL 字段表达:
番型表、精牌类型、计分倍数、操作列表、回合结束条件——引擎不判断玩法名,
只读 DSL 参数。
## 项目结构
```
card-game-engine/
├── docs/
│ ├── architecture-plan.md # 架构设计文档
── demo-implementation-plan.md # Demo 实施计划
├── RuleEngine/ # C# 规则引擎核心
── Core/ # MahjongTile 编码 / Deck 牌堆 / GameState 状态
│ ├── Patterns/ # MeldsSolver 胡牌判断 + 番型识别
│ ├── Phase/ # PhaseMachine 回合状态机
│ ├── Scoring/ # ScoreEngine 计分引擎
│ ├── Dsl/ # DslLoader YAML 加载 + CapabilityRegistry
── AI/ # RandomMahjongAI 随机AI验证用
├── RuleEngine.Tests/ # 单元测试 (45个)
├── dsl-examples/ # 玩法 DSL 配置 (YAML)
│ ├── xuezhandaodi.yaml # 四川麻将血战到底 (108张)
│ ├── guangdong_jipinghu.yaml # 广东麻将鸡平胡 (136张 + 花牌)
│ ├── guobiao.yaml # 国标麻将 (144张81番种)
── wuhan.yaml # 武汉麻将 (136张,红中癞子 + 258将)
└── Demo/ # 控制台 Demo
│ ├── dsl-specification.md # DSL 完整字段参考 (AI 可读)
── fan-recognizer-inventory.md # 39 种番型识别器清单
│ ├── macos-build-deploy.md # macOS 构建指南
── architecture-plan.md # 架构设计历史
├── RuleEngine/ # C# 引擎核心 (~3000 行)
│ ├── Core/ # 牌编码 / 牌堆 / 状态 / 癞子注册表
│ ├── Patterns/ # 胡牌判断 (MeldsSolver) + 番型识别
│ ├── Phase/ # 回合状态机 + 合法操作
── Scoring/ # 计分引擎 + 花牌/癞子 bonus
│ ├── Dsl/ # YAML 加载 + 双层合规检查
│ └── AI/ # 随机 AI + 人类玩家 CLI
├── RuleEngine.Tests/ # 58 个单元测试
├── dsl-examples/ # 5 种麻将玩法 DSL
│ ├── xuezhandaodi.yaml # 四川血战到底 (108 张)
── wuhan.yaml # 武汉麻将 (136 张, 红中癞子, 258 将)
│ ├── guangdong_jipinghu.yaml # 广东鸡平胡 (144 张, 花牌)
│ ├── guobiao.yaml # 国标麻将 (144 张, 81 番种, 8 番起胡)
│ └── nanchang.yaml # 南昌麻将 (136 张, 骰子翻牌定精, 庄×2)
└── Demo/ # 控制台 Demo
```
## 核心设计
- **规则引擎 ≠ 游戏引擎**纯逻辑库C#),不依赖图形框架。输入状态 → 输出合法操作
- **扑克/麻将引擎分离**:各自精专,共享基础层
- **int 编码**4 bytes/tile性能优先
- **加载时能力检查**DSL 声明 `requires`,引擎 CapabilityRegistry 自动验证
## 已实现能力
### 胡牌判断 (MeldsSolver)
| 能力 | 状态 | 说明 |
|------|------|------|
| 标准胡牌 (4面子+1对) | ✅ | 回溯搜索,含 wildcard 缺口填充 |
| 七对 | ✅ | 纯对子形态,wildcard 配对支持 |
| 十三幺 | ✅ | 13种幺九+1重复 |
| 全不靠 | ✅ | 14牌从16位置模板选wildcard 补位 |
| 一色双龙会 | ✅ | 同色1-9各至少2张wildcard 支持 |
| 鬼牌/癞子 | ✅ | 缺口填充式回溯 O(w×2^w) |
| 258将检查 | ✅ | 武汉麻将专用 |
### 胡牌判断
| 能力 | 说明 |
|------|------|
| 标准胡牌 (4 面子 + 1 对) | 回溯搜索,支持癞子缺口填充 O(w×2^w) |
| 七对 | 纯对子形态,癞子配对 |
| 十三幺 | 13 种幺九 + 1 重复 |
| 全不靠 | 147/258/369 + 字牌 16 位模板 |
| 一色双龙会 | 同色 1-9 各至少 2 张 |
| 258 将检查 | 武汉麻将专用DSL `pair_must_be_258` |
### 番型识别 (IdentifyFans)
自动识别 16 种结构性番型:清一色混一色字一色对对胡、暗七对、带幺九、混幺九、缺一门、平胡、断幺九、全大、全中、全小、大于五、小于五、全双、碰碰和
### 番型识别 (39 种)
结构性 (24): 清一色/混一色/字一色/对对胡/碰碰胡/碰碰和/暗七对/七对/将一色/
带幺九/全带幺/混幺九/缺一门/平胡/平和/断幺九/无字/全大/全中/全小/全双/
大于五/小于五/大四喜/大三元/小四喜/小三元/一色四同顺/五门齐
特殊牌型 (4): 十三幺/全不靠/一色双龙会/连七对
状态/事件 (9): 全求人/门前清/杠上开花/海底捞月/抢杠胡/天胡/地胡/癞子胡
兜底 (1): 鸡胡
### 回合控制
| 能力 | 状态 |
| 能力 | 说明 |
|------|------|
| 摸→打→碰杠胡回合 | |
| 优先级仲裁 (胡>杠>碰>吃) | |
| 血战到底 (胡后不结束) | |
| 查叫/查花猪 | |
| 花牌 (摸到即补) | ✅ |
| 摸→打→碰杠胡回合 | DSL 控制操作列表 |
| 优先级仲裁 (胡>杠>碰>吃) | DSL `priority_policy` |
| 血战到底 (胡后继续) | DSL `parallel_elimination` |
| 查叫/查花猪 | 血战专用 |
| 花牌 (摸到即补, 开局补花) | while 循环递归补花 |
| 8 番起胡 | DSL `win_min_fan`, 非硬编码 |
### DSL 热切换
4 种麻将变体 100% 覆盖率,`--dsl` 参数切换
### 精牌 (Wildcard)
| 类型 | 说明 |
|------|------|
| 固定精 (fixed) | DSL 指定牌名 (红中/发财/白板) 为癞子 |
| 翻牌定精 (random) | 发牌后翻一张牌X+1 正精、X+2 副精 (9→1 循环) |
## Demo 用法
### 计分
| 能力 | 说明 |
|------|------|
| 番型叠加策略 | add / max_level / add_maxDSL 配置 |
| 番型互斥 | excludes / conflicts 图,引擎自动应用 |
| 计分倍数 | self_draw / discard_win / dealer 倍数DSL 可配 |
| 花牌计分 | 每张 +1 番 + 座位配对额外分 |
| 癞子 bonus | 胡牌手牌中每张癞子加分 |
## 5 种玩法 DSL 覆盖率
```
血战 武汉 广东 国标 南昌
声明式 DSL 9 3 9 11 9
内置算法 7 7 7 6 9
引擎能力 (capability) 7 7 7 10 7
番型识别 14 14 14 22 14
─────────────────────────────────────────────
覆盖完整性 ✓ ✓ ✓ ✓ ✓
```
> 覆盖完整性 = 所有 DSL 配置的番型/条件/hook 均被引擎消费。
> `ValidateRuleCompliance()` 双层检查确保零假阳性。
## 用法
```bash
# 交互模式逐局展示
# 交互模式 (逐局展示)
dotnet run --project Demo
dotnet run --project Demo -- --dsl wuhan
dotnet run --project Demo -- --dsl guobiao
dotnet run --project Demo -- --dsl guangdong_jipinghu
dotnet run --project Demo -- --dsl nanchang
# 人类玩家模式
dotnet run --project Demo -- --human --dsl wuhan
# 压测模式
dotnet run --project Demo -- --auto --count 1000
# 运行测试
dotnet test # 全部 45 个
# 测试
dotnet test # 58 个测试
dotnet test --filter "听牌" # 听牌检测相关
dotnet test --filter "鬼牌" # 癞子相关
```
## 测试覆盖
## 压测结果
- **45 个单元测试** 全部通过
- 覆盖:牌编码、牌库构建、标准胡牌、七对、十三幺、全不靠、一色双龙会、
wildcard 补位、258将检查、番型识别清一色/混一色/字一色/对对胡/
带幺九/缺一门/平胡/断幺九/全大/全小/全双)
- 1000 局压测 0 报错
| 玩法 | 胡牌率 | 性能 | 零和 |
|------|--------|------|------|
| 血战到底 | ~10% | 7ms/局 | ✓ |
| 武汉麻将 | ~40% | 8ms/局 | ✓ |
| 广东鸡平胡 | ~20% | 7ms/局 | ✓ |
| 国标麻将 | ~15% | 7ms/局 | ✓ |
| 南昌麻将 | ~60% | 7ms/局 | ✓ |
## 开源参考
1000 局 × 5 种玩法压测 0 错误,全部零和结算。
- q_algorithm (C# 胡牌算法库)
- majiang_algorithm (Java 麻将引擎 + AI)
- MahjongKit (Python 牌谱分析)
## 引擎代码规模
| 文件 | 行数 | 职责 |
|------|------|------|
| MahjongRoom.cs | 866 | 牌局编排 |
| MeldsSolver.cs | 808 | 胡牌 + 番型 |
| DslLoader.cs | 299 | DSL 加载 |
| PhaseMachine.cs | 260 | 回合状态机 |
| ScoreEngine.cs | 174 | 计分引擎 |
| 其余 7 个文件 | ~581 | 牌编码 / 状态 / AI |
| **总计** | **2988** | — |
> 引擎 95% 通用逻辑5% 变体特殊逻辑(已通过 DSL 参数化)。
> 零 `if (gameName == "武汉")` 式的玩法名判断。

View File

@ -181,14 +181,22 @@ phases:
### scoring (必需)
```yaml
scoring:
mode: fan_table # 当前仅支持"fan_table"
max_cap: 100 # 番数上限
pre_hooks: # 可选,结算前执行的hook
- name: check_hua_zhu # 检查花猪(仅血战)
mode: fan_table # 当前仅支持"fan_table"
max_cap: 999 # 番数上限, 默认无限
self_draw_multiplier: 1 # 自摸: 每家付 baseFan × N (默认 1)
discard_win_multiplier: 3 # 点炮: 放炮者付 baseFan × N (默认 3)
dealer_multiplier: 1 # 庄家输赢 × N (默认 1, 南昌=2)
pre_hooks: # 可选, 结算前执行的 hook
- name: check_hua_zhu # 检查花猪 (仅血战)
condition: deck_exhausted
- name: check_ting # 检查听牌(仅血战)
- name: check_ting # 检查听牌 (仅血战)
condition: deck_exhausted AND not hua_zhu
```
**计分倍数规则**
- `self_draw_multiplier`: 自摸胡牌时每位非赢家支付 `baseFan × N`,赢家收入总和
- `discard_win_multiplier`: 点炮胡牌时放炮者支付 `baseFan × N`,赢家收入
- `dealer_multiplier`: 庄家赢 → 每家付倍数;闲家赢 → 仅庄家付倍数
- 不设置时使用默认值,现有玩法向后兼容
### pre_hooks (可选)
```yaml
@ -260,10 +268,11 @@ flower_rules:
3. **scoring.mode**: 仅支持 `fan_table`。不支持 `multiply_score`
4. **牌库生成器**: 仅支持 `generator: mahjong`。不支持其他牌库类型。
5. **玩家数**: 仅支持 4 人。不支持 2 人/3 人。
6. **花牌计分**: `flower_rules.scoring` 的各字段都支持,但 `replace_tiles` 仅在广东鸡平胡(BEFORE_GAME_START)和国标(留空=on_draw replace)下正确
6. **花牌计分**: 完全支持。初始花牌 (`deal_cards_with_flowers`) 和游戏中摸到花牌都正确补牌while 循环处理递归花牌)
7. **pre_hooks**: 仅 `wildcard_count`/`check_hua_zhu`/`check_ting` 被引擎识别。其他名称会在合规检查中警告。
8. **特殊花色**: 不区分风圈/箭刻/门风。番型 `圈风刻`/`门风刻`/`箭刻` 不在识别列表中
8. **wildcard type**: 支持 `fixed`(指定牌为癞子)和 `random`(骰子翻牌定精)
9. **番型组合: 一色三同顺/一色三节高/三色三同顺/花龙/组合龙/推不倒/无番和**: 不在识别列表中。
10. **精的冲关/德国计分**: 引擎不支持南昌麻将的冲关精数×2^N和德国无精胡牌加分复杂计分规则。
---
@ -284,6 +293,8 @@ flower_rules:
- [ ] `fan_types``condition` 在支持列表中(或为空)
- [ ] `phases` 的 actions 在支持列表中
- [ ] `wildcard_rules.behavior` = "substitute"
- [ ] `wildcard_rules.type` = "fixed" 或 "random"
- [ ] `scoring.mode` = "fan_table"
- [ ] `scoring` 倍数 (self_draw/discard_win/dealer) 设置正确
- [ ] `pre_hooks` 仅含 known hooks
- [ ] 加载时合规检查零警告