fix: 连七对识别器 + 258检查gap + .First() null防御

1. 连七对: IsConsecutiveSameSuitSevenPairs() 检测同色连续7对
   TrySevenPairs成功后检查→ 连七对 vs 暗七对/七对
2. 258 check gap: 十三幺/全不靠/一色双龙会路径加 require258Pair 检查
   武汉麻将中这些牌型无258将概念→258要求下直接拒绝
3. .First() → .FirstOrDefault() + null return:
   ExecuteAnKong / ExecuteBuKong 防御性编程

审计JSON: remaining_issues 清空 (所有已知问题已修复)
This commit is contained in:
xiaoou
2026-07-04 22:49:19 +08:00
parent c2c326b07a
commit 2ef7d57877
3 changed files with 27 additions and 19 deletions

View File

@ -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";

View File

@ -59,7 +59,10 @@ public class MeldsSolver
}
}
}
sevenPairs.Fans = new List<string> { FanName("暗七对", "七对") };
sevenPairs.Fans = new List<string> {
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<string> { "十三幺" };
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<string> { "全不靠" };
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<string> { "一色双龙会" };
doubleDragon.Fans = ApplyFanExclusions(
doubleDragon.Fans.Concat(IdentifyFans(doubleDragon)).Distinct().ToList());
@ -386,6 +392,20 @@ public class MeldsSolver
return null;
}
/// <summary>Check if 7 pairs form 连七对: same suit, consecutive ranks 1-7/2-8/3-9.</summary>
private static bool IsConsecutiveSameSuitSevenPairs(List<Meld> 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<int> tiles, int wildcardCount)
{

View File

@ -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防御已在本轮补充修复。"
}