From 2ef7d57877dc7a586450915b1f779181ed7aa3e5 Mon Sep 17 00:00:00 2001 From: xiaoou Date: Sat, 4 Jul 2026 22:49:19 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=BF=9E=E4=B8=83=E5=AF=B9=E8=AF=86?= =?UTF-8?q?=E5=88=AB=E5=99=A8=20+=20258=E6=A3=80=E6=9F=A5gap=20+=20.First(?= =?UTF-8?q?)=20null=E9=98=B2=E5=BE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 连七对: IsConsecutiveSameSuitSevenPairs() 检测同色连续7对 TrySevenPairs成功后检查→ 连七对 vs 暗七对/七对 2. 258 check gap: 十三幺/全不靠/一色双龙会路径加 require258Pair 检查 武汉麻将中这些牌型无258将概念→258要求下直接拒绝 3. .First() → .FirstOrDefault() + null return: ExecuteAnKong / ExecuteBuKong 防御性编程 审计JSON: remaining_issues 清空 (所有已知问题已修复) --- RuleEngine/MahjongRoom.cs | 6 ++++-- RuleEngine/Patterns/MeldsSolver.cs | 22 +++++++++++++++++++++- dsl-review-result.json | 18 ++---------------- 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/RuleEngine/MahjongRoom.cs b/RuleEngine/MahjongRoom.cs index 61abe67..1bb89f9 100644 --- a/RuleEngine/MahjongRoom.cs +++ b/RuleEngine/MahjongRoom.cs @@ -568,7 +568,8 @@ public class MahjongRoom { var hand = State.Hands[player]; var group = hand.GroupBy(t => t) - .First(g => g.Count() >= 4 && !State.Wildcards.IsWildcard(g.Key)); + .FirstOrDefault(g => g.Count() >= 4 && !State.Wildcards.IsWildcard(g.Key)); + if (group == null) return; // defensive: PhaseMachine should prevent this int tile = group.Key; for (int i = 0; i < 4; i++) State.Hands[player].Remove(tile); State.Exposed[player].Add(new Meld @@ -607,7 +608,8 @@ public class MahjongRoom private void ExecuteBuKong(string player) { var exposed = State.Exposed[player]; - var pung = exposed.First(m => m.Type == "pung" && State.Hands[player].Contains(m.Tiles[0])); + var pung = exposed.FirstOrDefault(m => m.Type == "pung" && State.Hands[player].Contains(m.Tiles[0])); + if (pung == null) return; // defensive: PhaseMachine should prevent this int tile = pung.Tiles[0]; State.Hands[player].Remove(tile); pung.Type = "kong_bu"; diff --git a/RuleEngine/Patterns/MeldsSolver.cs b/RuleEngine/Patterns/MeldsSolver.cs index e31a71a..f20c525 100644 --- a/RuleEngine/Patterns/MeldsSolver.cs +++ b/RuleEngine/Patterns/MeldsSolver.cs @@ -59,7 +59,10 @@ public class MeldsSolver } } } - sevenPairs.Fans = new List { FanName("暗七对", "七对") }; + sevenPairs.Fans = new List { + IsConsecutiveSameSuitSevenPairs(sevenPairs.Melds) ? "连七对" + : FanName("暗七对", "七对") + }; sevenPairs.Fans = ApplyFanExclusions( sevenPairs.Fans.Concat(IdentifyFans(sevenPairs)).Distinct().ToList()); return sevenPairs; @@ -69,6 +72,7 @@ public class MeldsSolver var thirteen = TryThirteenOrphans(tiles, wildcardCount); if (thirteen != null) { + if (require258Pair) return new MeldsResult { IsWin = false }; // 十三幺无258将概念 thirteen.Fans = new List { "十三幺" }; thirteen.Fans = ApplyFanExclusions( thirteen.Fans.Concat(IdentifyFans(thirteen)).Distinct().ToList()); @@ -79,6 +83,7 @@ public class MeldsSolver var allOrphans = TryAllOrphans(tiles, wildcardCount); if (allOrphans != null) { + if (require258Pair) return new MeldsResult { IsWin = false }; // 全不靠无将牌概念 allOrphans.Fans = new List { "全不靠" }; allOrphans.Fans = ApplyFanExclusions( allOrphans.Fans.Concat(IdentifyFans(allOrphans)).Distinct().ToList()); @@ -89,6 +94,7 @@ public class MeldsSolver var doubleDragon = TryDoubleDragon(tiles, wildcardCount); if (doubleDragon != null) { + if (require258Pair) return new MeldsResult { IsWin = false }; // 一色双龙会无258将概念 doubleDragon.Fans = new List { "一色双龙会" }; doubleDragon.Fans = ApplyFanExclusions( doubleDragon.Fans.Concat(IdentifyFans(doubleDragon)).Distinct().ToList()); @@ -386,6 +392,20 @@ public class MeldsSolver return null; } + /// Check if 7 pairs form 连七对: same suit, consecutive ranks 1-7/2-8/3-9. + private static bool IsConsecutiveSameSuitSevenPairs(List melds) + { + if (melds.Count != 7) return false; + var tiles = melds.Select(m => m.Tiles[0]).ToList(); + // All must be numbered (no honors) and same suit + if (!tiles.All(MahjongTile.IsNumbered)) return false; + int suit = MahjongTile.Suit(tiles[0]); + if (!tiles.All(t => MahjongTile.Suit(t) == suit)) return false; + // Ranks must form consecutive 1-7, 2-8, or 3-9 + var ranks = tiles.Select(MahjongTile.Rank).OrderBy(r => r).ToList(); + return ranks.SequenceEqual(Enumerable.Range(ranks[0], 7)) && ranks[0] <= 3; + } + // === 十三幺 === private MeldsResult? TryThirteenOrphans(List tiles, int wildcardCount) { diff --git a/dsl-review-result.json b/dsl-review-result.json index 94e73c1..f9eaac4 100644 --- a/dsl-review-result.json +++ b/dsl-review-result.json @@ -26,20 +26,6 @@ "鸡胡识别缺失 → allZeroValue检查 + DSL配置鸡胡" ] }, - "remaining_issues": [ - { - "severity": "med", - "file": "MeldsSolver.cs", - "issue": "约10个番型识别器缺失: 将一色,大四喜,大三元,小四喜,小三元,一色四同顺,连七对,五门齐,全带幺,全求人", - "impact": "这些番型被引擎标记为well-known(可通过filter), 但IdentifyFans中部分无自动识别逻辑。DSL配置后FanValue可正常查找(通过_fanConfig), 但引擎需在这些模式出现时自动添加番型名。", - "status": "部分已实现(将一色/大四喜/大三元/小四喜/小三元/一色四同顺/五门齐/全求人已有识别逻辑), 全带幺通过FanName(\"带幺九\",\"全带幺\")处理, 连七对通过七对路径+名称匹配" - }, - { - "severity": "med", - "file": "MeldsSolver.cs:45-65", - "issue": "258将检查仅对七对路径实施, 标准回溯路径另有检查。但十三幺/全不靠/一色双龙会绕过258检查", - "impact": "武汉麻将的258要求在这些特殊牌型中不生效。但实际游戏中这些牌型几乎不会出现258争议。" - } - ], - "note": "本文件为历史审计快照。所有5个HIGH已修复。剩余MED问题影响极小, 实际游戏中几乎不触发。" + "remaining_issues": [], + "note": "本文件为历史审计快照。所有已知问题已修复。连七对识别器、258检查gap、.First() null防御已在本轮补充修复。" } \ No newline at end of file