feat: 番型识别DSL驱动化 + 全不靠/一色双龙会测试完善

IdentifyFans: 从4种扩展到17种结构性番型自动识别
- 清一色/混一色/字一色/对对胡/暗七对/带幺九/混幺九
- 缺一门/平胡/断幺九/全大/全中/全小/大于五/小于五/全双/碰碰和
- FanConfig 新增 Condition 字段预留game-event条件

TryAllOrphans: 修正容差 +2(16选14),允许非幺九数牌
TryDoubleDragon: 增加honor/越界保护

测试: 33→45,新增12个(全不靠×3/一色双龙会×2/番型×7)
100局压测: 0出错
This commit is contained in:
xiaoou
2026-07-04 10:46:09 +08:00
parent 96246d2fec
commit ffe90b6d3d
4 changed files with 328 additions and 51 deletions

View File

@ -213,16 +213,140 @@ public class MeldsSolverTests
}
// === 全不靠 ===
// 全不靠需要从16张模板(147×3=9 + 7字)中选14张
// 当前算法要求9数位全占需搭配wildcard。纯手牌场景留待后续完善
// [Fact(Skip = "全不靠需wildcard补位纯14牌无wildcard场景待完善")]
// 全不靠: 14 tiles chosen from 16-position template (9 numbered gaps + 7 honors).
// Two positions may be empty per rule — no wildcards needed.
[Fact]
public void _7字牌_7数牌_ShouldWin()
public void _14牌0wildcard_ShouldWin()
{
// 仅验证算法不崩溃
// 7 honors + 万147 + 条258 + 筒3 = 14 tiles from 16-position pool
var hand = new List<int> { 1, 4, 7, 12, 15, 18, 23, 31, 32, 33, 34, 35, 36, 37 };
var result = _solver.CheckWin(hand, wildcardCount: 0);
Assert.True(result.IsWin, "全不靠(14牌从16位置模板选)应判定为胡牌");
}
[Fact]
public void _3wildcard补位_ShouldWin()
{
// 5 honors + 万147 + 条258 = 11 tiles + 3 wildcards → fill 5 missing → 14 total
var hand = new List<int> { 1, 4, 7, 12, 15, 18, 31, 32, 33, 34, 35 };
var result = _solver.CheckWin(hand, wildcardCount: 3);
Assert.True(result.IsWin, "全不靠(11牌+3wildcard补位)应判定为胡牌");
}
[Fact]
public void _0wildcard不完整_ShouldNotWin()
{
// Only 6 honors + 万147 = 9 tiles, 5 positions missing, 0 wildcard → fail
var hand = new List<int> { 1, 4, 7, 31, 32, 33, 34, 35, 36 };
var result = _solver.CheckWin(hand, wildcardCount: 0);
Assert.False(result.IsWin, "全不靠(9牌+0wildcard不完整)不应判定为胡牌");
}
// === 一色双龙会 (需 wildcard: 9 ranks × 2 = 18 positions, 14 tiles + wildcards) ===
[Fact]
public void _4wildcard_ShouldWin()
{
// 5 ranks × 2 = 10 tiles + 4 wildcards for remaining 4 ranks = 14 total
var hand = new List<int> { 1, 1, 2, 2, 3, 3, 4, 4, 5, 5 };
var result = _solver.CheckWin(hand, wildcardCount: 4);
Assert.True(result.IsWin, "一色双龙会(10牌+4wildcard)应判定为胡牌");
}
[Fact]
public void _不足wildcard_ShouldNotWin()
{
// Same hand, only 2 wildcards → ranks 6-9 need 2 each → 6 missing, only 2 available
var hand = new List<int> { 1, 1, 2, 2, 3, 3, 4, 4, 5, 5 };
var result = _solver.CheckWin(hand, wildcardCount: 2);
Assert.False(result.IsWin, "一色双龙会不足wildcard不应判定为胡牌");
}
// === 新番型识别测试 ===
[Fact]
public void _ShouldIdentify()
{
// 万牌 + 字牌对将: 111万 234万 567万 东东东 发发
var hand = new List<int> { 1, 1, 1, 2, 3, 4, 5, 6, 7, 31, 31, 31, 36, 36 };
var result = _solver.CheckWin(hand);
// 标准回溯和七对都失败后全不靠也因缺位而null预期不崩
Assert.True(result == null || !result.IsWin);
Assert.True(result.IsWin);
var fans = _solver.IdentifyFans(result!);
Assert.Contains("混一色", fans);
}
[Fact]
public void _ShouldIdentify()
{
// All honors: 111 333 555 777 99 in encoding
var hand = new List<int> { 31, 31, 31, 33, 33, 33, 35, 35, 35, 37, 37, 37, 32, 32 };
var result = _solver.CheckWin(hand);
Assert.True(result.IsWin);
var fans = _solver.IdentifyFans(result!);
Assert.Contains("字一色", fans);
}
[Fact]
public void _ShouldIdentify()
{
// Only 万 and 条, no 筒: 123万 456万 789万 111条 99条
var hand = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 11, 11, 19, 19 };
var result = _solver.CheckWin(hand);
Assert.True(result.IsWin);
var fans = _solver.IdentifyFans(result!);
Assert.Contains("缺一门", fans);
}
[Fact]
public void _全顺子_ShouldIdentify()
{
// All shunzi: 123万 456万 789万 234条 55条
var hand = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 15, 15 };
var result = _solver.CheckWin(hand);
Assert.True(result.IsWin);
var fans = _solver.IdentifyFans(result!);
Assert.Contains("平胡", fans);
}
[Fact]
public void _7到9_ShouldIdentify()
{
// All ranks 7-9: 777万 888万 999万 777条 99条
var hand = new List<int> { 7, 7, 7, 8, 8, 8, 9, 9, 9, 17, 17, 17, 19, 19 };
var result = _solver.CheckWin(hand);
Assert.True(result.IsWin);
var fans = _solver.IdentifyFans(result!);
Assert.Contains("全大", fans);
}
[Fact]
public void _1到3_ShouldIdentify()
{
// All ranks 1-3: 111万 222万 333万 111条 33条
var hand = new List<int> { 1, 1, 1, 2, 2, 2, 3, 3, 3, 11, 11, 11, 13, 13 };
var result = _solver.CheckWin(hand);
Assert.True(result.IsWin);
var fans = _solver.IdentifyFans(result!);
Assert.Contains("全小", fans);
}
[Fact]
public void _偶数_ShouldIdentify()
{
// All even ranks: 222万 444万 666万 222条 88条
var hand = new List<int> { 2, 2, 2, 4, 4, 4, 6, 6, 6, 12, 12, 12, 18, 18 };
var result = _solver.CheckWin(hand);
Assert.True(result.IsWin);
var fans = _solver.IdentifyFans(result!);
Assert.Contains("全双", fans);
}
[Fact]
public void _ShouldIdentify()
{
// No terminals: 234万 456万 678万 234条 55条 (ranks 2-8 only)
var hand = new List<int> { 2, 3, 4, 4, 5, 6, 6, 7, 8, 12, 13, 14, 15, 15 };
var result = _solver.CheckWin(hand);
Assert.True(result.IsWin);
var fans = _solver.IdentifyFans(result!);
Assert.Contains("断幺九", fans);
}
}