From 712116bca8d96a40188ec116af208da0d5bd3541 Mon Sep 17 00:00:00 2001 From: xiaoou Date: Sat, 4 Jul 2026 23:06:12 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20fan=5Fstacking=E9=AA=8C=E8=AF=81=20+=20O?= =?UTF-8?q?nEliminate=E4=BC=A0=E6=92=AD=20+=20TurnOrder=E8=AF=BBDSL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 子代理审查(DSL+Core)发现26个问题,本轮修复3个: 1. fan_stacking值验证: 非法值→警告(之前静默以add计算) 2. OnEliminate传播: BuildPhases补映射(PhaseConfig已有属性) 3. TurnOrder读DSL: 之前硬编码counter_clockwise,现读YAML REVIEW_REPORT.md: 子代理审查完整报告(保留作为参考) --- REVIEW_REPORT.md | 170 ++++++++++++++++++++++++++++++++++++ RuleEngine/Dsl/DslLoader.cs | 5 ++ RuleEngine/MahjongRoom.cs | 3 +- 3 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 REVIEW_REPORT.md diff --git a/REVIEW_REPORT.md b/REVIEW_REPORT.md new file mode 100644 index 0000000..9a9c6c8 --- /dev/null +++ b/REVIEW_REPORT.md @@ -0,0 +1,170 @@ +# DSL YAML + DslLoader + Core 代码审查报告 + +审查范围: dsl-examples/*.yaml (5文件) + DslLoader.cs (313行) + Core/WildcardRegistry.cs + Core/MahjongTile.cs + Core/GameState.cs + Core/Deck.cs +审查日期: 2026-07-04 + +--- + +## 一、YAML字段不一致 (4 issues) + +### 1.1 DeckConfig.WildcardCount/WildcardTile — POCO属性未在任何YAML中使用 +- **位置**: DslLoader.cs:168-169 (POCO), 5个YAML文件 deck 段 +- **问题**: `DeckConfig` 定义了 `WildcardCount` 和 `WildcardTile` 两个属性,但所有5个YAML的 deck 段只使用 `generator, include_honors, include_flowers, total` 四个字段。wildcard_count 实际配置在 wildcard_rules 段下,由 `WildcardConfig` 吸收。 +- **严重度**: 中 — 字段永远不会被赋值,容易混淆使用者。MahjongRoom.cs:292 读取此字段永远为0。 + +### 1.2 PhaseDslConfig.TurnOrder — DSL有值但BuildPhases硬编码覆盖 +- **位置**: DslLoader.cs:215 (POCO), MahjongRoom.cs:102 +- **问题**: 所有YAML在 play phase 中配置 `turn_order: counter_clockwise`,但 `BuildPhases()` 中 `TurnOrder = "counter_clockwise"` 硬编码,未使用 `p.TurnOrder`。 +- **严重度**: 中 — 若有人写 `turn_order: clockwise` 会被静默忽略。 + +### 1.3 PhaseDslConfig.OnEliminate — 未传播到PhaseConfig +- **位置**: DslLoader.cs:213 (POCO), PhaseMachine.cs:20 (PhaseConfig 有 OnEliminate), MahjongRoom.cs:94-118 (BuildPhases) +- **问题**: `phase.on_eliminate` 在 xuezhandaodi.yaml:69 配置为 `hu_paid`,但 BuildPhases 未将 `p.OnEliminate` 复制到 PhaseConfig。PhaseConfig 有 OnEliminate 属性,但从未被赋值。 +- **严重度**: 高 — 血战到底的"胡牌后继续"核心逻辑可能失效。 + +### 1.4 PhaseDslConfig.FuFlag — 未传播到PhaseConfig +- **位置**: DslLoader.cs:214 (FuFlag), xuezhandaodi.yaml:74-78 +- **问题**: xuezhandaodi 配置了完整的 `fu_flag` (过水规则),但 BuildPhases 不复制 FuFlag 到 PhaseConfig,PhaseConfig 也没有 FuFlag 属性。引擎使用硬编码逻辑处理过水 (MahjongRoom.cs:270,369,397),DSL 配置值未参与。 +- **严重度**: 中 — 过水逻辑可用但无法通过 DSL 定制。 + +--- + +## 二、DSL字段未消费 (6 issues) + +### 2.1 WildcardConfig.FanCalculationPolicy — 引擎未使用 +- **位置**: DslLoader.cs:180 (POCO), wuhan.yaml:31 / nanchang.yaml:33 (YAML= `optimal`) +- **问题**: `FanCalculationPolicy` 字段存在于 POCO 和 YAML,但在 ScoreEngine 和 MeldsSolver 中完全未被读取。引擎在计算癞子番型时无此策略分支。 +- **严重度**: 低 — 当前仅 optimal 一种策略,但若有人配置其他值不会报错也不会生效。 + +### 2.2 FlowerDslConfig.ReplaceTiles — 引擎未使用 +- **位置**: DslLoader.cs:258 (POCO), guangdong_jipinghu.yaml:28 (`BEFORE_GAME_START`) +- **问题**: 广东鸡平胡配置 `replace_tiles: BEFORE_GAME_START`,但 ScoreEngine 和 MahjongRoom 均不读取此字段。花牌替换逻辑在 MahjongRoom.StepTurn() 中硬编码为"摸到花牌立即补牌"。 +- **严重度**: 中 — 若 BEFORE_GAME_START 表示"开局前补花"语义,则当前行为不符。 + +### 2.3 PreHookConfig.Condition — 引擎未评估 +- **位置**: DslLoader.cs:193 (POCO), xuezhandaodi.yaml:89-90 (`deck_exhausted AND not hua_zhu`) +- **问题**: `PreHookConfig.Condition` 在 YAML 中有复杂条件表达式,但引擎代码中 PreHook 仅按 name 触发,Condition 字符串从未被解析/评估。 +- **严重度**: 中 — 条件判断实际在引擎中硬编码,DSL condition 仅为文档参考。 + +### 2.4 FanTypeConfig.Condition — 引擎未评估 +- **位置**: DslLoader.cs:202 (POCO), wuhan.yaml:49 / nanchang.yaml:54 (`hand_contains_wildcard`) +- **问题**: `FanTypeConfig.Condition` 被复制到 `FanConfig.Condition` (MahjongRoom.cs:88) 但 MeldsSolver.IdentifyFans() (607-794行) 完全不检查此字段。癞子胡的"手牌含癞子"判断是硬编码在 757 行,并非通过 DSL condition。 +- **严重度**: 低 — 引擎自动识别癞子胡,condition 冗余但无害。 + +### 2.5 DrawSubPhase.OnDrawFlower — 部分消费 +- **位置**: DslLoader.cs:234 (POCO), guobiao.yaml:97 / guangdong_jipinghu.yaml:75 (`replace`) +- **问题**: `draw.on_draw_flower` 在 YAML 中配置为 `replace`,但引擎在 StepTurn 中硬编码处理花牌补牌逻辑,不读取此配置值。 +- **严重度**: 低 + +### 2.6 DrawSubPhase.OnEmptyDeck — 部分消费 +- **位置**: DslLoader.cs:235 (POCO), xuezhandaodi.yaml:54 (`check_ting`) +- **问题**: `draw.on_empty_deck` 在 xuezhandaodi 配置为 `check_ting`,其他为 `exhausted`。引擎代码在 StepTurn 中硬编码检查牌墙耗尽逻辑,不按 DSL 值分支。 +- **严重度**: 低 + +--- + +## 三、编码重复魔法数字 (7 issues) + +### 3.1 WildcardBase=50 重复定义 +- **位置**: MahjongTile.cs:11 (const), WildcardRegistry.cs:23-24 (default), DslLoader.cs:176 (default), Deck.cs:8 (default param) +- **问题**: 值 `50` 出现在4个位置。MahjongTile.cs 定义了常量 `WildcardBase = 50`,但 WildcardRegistry 和 DslLoader 各自硬编码默认值。 +- **严重度**: 中 — 若需改为60,必须同时修改4处。 + +### 3.2 Wildcard范围 = 9 重复 +- **位置**: WildcardRegistry.cs:39 (`_wildcardBase + 9`), MahjongTile.cs:14,18 (`WildcardBase + 9`) +- **问题**: 癞子编码范围 [50, 59] 的长度 9 重复定义。无常量 `WildcardRange`。 +- **严重度**: 低 + +### 3.3 Suit常量未提取 +- **位置**: MahjongTile.cs:64-70, WildcardRegistry.cs:67,76,78, Deck.cs (间接) +- **问题**: suit 值 (0=万, 1=条, 2=筒, 3=字, 4=花, 5=宝牌) 在 MahjongTile.Suit() 和 WildcardRegistry.ComputeNextTiles() 等处多次硬编码,无枚举或命名常量。 +- **严重度**: 低 + +### 3.4 编码边界值重复 +- **位置**: MahjongTile.cs:40-42,64-69, TileToIndex 等 +- **问题**: 编码边界 1,9,11,19,21,29,31,37,41,48 在 Suit/Rank/ToString/TileToIndex 中反复硬编码。 +- **严重度**: 低 — Encoder/Decoder 集中在一个类,但缺乏常量名可读性差。 + +### 3.5 牌复制数 4 硬编码 +- **位置**: Deck.cs:13 `int count = MahjongTile.IsFlower(t) ? 1 : 4;` +- **问题**: 每张非花牌4张的规则硬编码。MahjongTile.AllTiles() 中也隐式依赖此数。 +- **严重度**: 低 + +### 3.6 MeldsSolver 编码索引范围 +- **位置**: MeldsSolver.cs:127-134, 140-154 (counts array size 34, index offset 9/18/27) +- **问题**: counts 数组大小为 34 (27 数字牌 + 7 字牌),偏移量 9,18,27 硬编码,与 MahjongTile 的编码系统存在隐含耦合但无共享常量。 + +### 3.7 DefaultFanValue 硬编码番值 +- **位置**: PhaseMachine.cs:178-184 +- **问题**: `DefaultFanValue` 为特定番型硬编码 fallback 值。MeldsSolver.IdentifyFans line 786-792 也有一个超长的硬编码番型列表用作过滤。这两个列表是同一知识的两个拷贝。 +- **严重度**: 中 — 添加新番型需同步修改两处。 + +--- + +## 四、合规检查遗漏 (10 issues) + +### 4.1 fan_stacking 值未验证 +- **位置**: DslLoader.cs:57-131 (ValidateRuleCompliance) +- **问题**: `fan_stacking` 合法值应为 `add`, `add_max`, `max_level`。guangdong_jipinghu 使用 `max_level` 且 ScoreEngine.cs:109-117 有相应逻辑,但合规检查未验证此值。若输入 `fan_stacking: multiply` 不会报错。 +- **严重度**: 高 — 非法值会导致 ComputeTotalFans 落入 default 分支静默以 add 模式计算。 + +### 4.2 max_fan 值未验证 +- **位置**: DslLoader.cs:147 (MaxFan=MaxValue), YAML中 max_fan +- **问题**: `max_fan` 应为正数,但合规检查未验证。负数会被 Math.Min 截断但无警告。 +- **严重度**: 低 + +### 4.3 win_min_fan 与 fan_stacking 一致性 +- **位置**: guobiao.yaml:41 `win_min_fan: 8` + `fan_stacking: add_max` +- **问题**: 国标需要起胡8番,合规检查未验证 win_min_fan > 0 且 fan_stacking 支持此语义。 +- **严重度**: 中 + +### 4.4 Phase 类型值未验证 +- **位置**: DslLoader.cs:206 (PhaseDslConfig.Type) +- **问题**: `phase.type` 合法值应为 `auto` / `mahjong_turn`,合规检查未验证。 +- **严重度**: 低 + +### 4.5 EndCondition 类型值未验证 +- **位置**: DslLoader.cs:245 (EndConditionDsl) +- **问题**: `end_conditions[].type` 在 YAML 中为 `last_one_standing` / `deck_exhausted` / `player_wins`,合规检查未验证此枚举。 +- **严重度**: 中 — 非法类型静默跳过。 + +### 4.6 Action 名未验证 +- **位置**: DslLoader.cs:244 (ActionOptionDsl.Action) +- **问题**: YAML 中 actions 包含 `discard, an_kong, bu_kong, win, pung, ming_kong, chi, pass`。合规检查未验证这些动作名。 +- **严重度**: 低 — 非法 action 会在 PhaseMachine 中被忽略。 + +### 4.7 PriorityPolicy 值未验证 +- **位置**: DslLoader.cs:241 (`"highest_wins"`) +- **问题**: `priority_policy` 目前仅支持 `highest_wins`,合规检查未验证。 +- **严重度**: 低 + +### 4.8 FuFlag 配置完整性未检查 +- **位置**: DslLoader.cs:217-223 (FuFlagConfig), xuezhandaodi.yaml:74-78 +- **问题**: fu_flag 有 4 个子字段 (type/set_on/clear_on/effect),合规检查未验证这些值是否合法。 +- **严重度**: 中 + +### 4.9 番型 Excludes/Conflicts 引用完整性 +- **位置**: DslLoader.cs:200-201 +- **问题**: YAML 中 `excludes` 和 `conflicts` 引用的番型名可能指向不存在的番型 (如 wuhan.yaml:39 `excludes: [缺一门, 无字]` 但武汉麻将 fan_types 中无 缺一门/无字)。合规检查未做引用完整性校验。 +- **严重度**: 中 — 引用了不存在的番型名不会报错,排除逻辑静默失效。 + +### 4.10 flower_rules 一致性 +- **位置**: guangdong_jipinghu.yaml:28 `replace_tiles: BEFORE_GAME_START` +- **问题**: `replace_tiles` 值 `BEFORE_GAME_START` 未在合规检查中验证。 +- **严重度**: 低 + +--- + +## 五、总结统计 + +| 类别 | High | Medium | Low | 合计 | +|------|------|--------|-----|------| +| YAML字段不一致 | 1 | 2 | 0 | 3 | +| DSL未消费 | 0 | 4 | 2 | 6 | +| 编码重复魔法数字 | 0 | 2 | 5 | 7 | +| 合规检查遗漏 | 1 | 6 | 3 | 10 | +| **总计** | **2** | **14** | **10** | **26** | + +**最高优先级修复**: +1. 🔴 **PhaseDslConfig.OnEliminate 未传播** (1.3) — 血战到底核心逻辑可能受影响 +2. 🔴 **fan_stacking 值未验证** (4.1) — 非法值静默以 add 模式计算 \ No newline at end of file diff --git a/RuleEngine/Dsl/DslLoader.cs b/RuleEngine/Dsl/DslLoader.cs index ffcd287..c066f9e 100644 --- a/RuleEngine/Dsl/DslLoader.cs +++ b/RuleEngine/Dsl/DslLoader.cs @@ -109,6 +109,11 @@ public class CapabilityRegistry warnings.Add($"pre_hook「{h.Name}」引擎未实现→{h.Description}"); } + // fan_stacking 值验证 + var validStackings = new HashSet { "add", "add_max", "max_level" }; + if (!validStackings.Contains(rules.FanStacking)) + warnings.Add($"fan_stacking={rules.FanStacking} 非法→仅支持 add/add_max/max_level→已按add计算"); + // Scoring mode if (rules.Scoring.Mode != "fan_table") warnings.Add($"scoring.mode={rules.Scoring.Mode} 引擎仅支持fan_table→忽略"); diff --git a/RuleEngine/MahjongRoom.cs b/RuleEngine/MahjongRoom.cs index 1bb89f9..282bc42 100644 --- a/RuleEngine/MahjongRoom.cs +++ b/RuleEngine/MahjongRoom.cs @@ -99,8 +99,9 @@ public class MahjongRoom Type = p.Type, Action = p.Action ?? "", Next = p.Next, - TurnOrder = "counter_clockwise", + TurnOrder = p.TurnOrder ?? "counter_clockwise", ParallelElimination = p.ParallelElimination, + OnEliminate = p.OnEliminate, SelfActions = p.SubPhases?.SelfAction?.Options?.Select(o => new ActionOption { Action = o.Action, Priority = o.Priority, Condition = o.Condition