Files
card-game-engine/Demo/Program.cs
xiaoou 4302ec2fd5 feat: 集成测试 + 手动出牌模式 + discart pool幽灵牌修复
集成测试 (10个):
- 发牌: 庄14闲13, 牌墙55
- 回合流转: 庄家第一轮不摸牌, 非庄家摸牌后出牌
- 数据完整性: 10回合后手牌+副露+牌墙+弃牌=108
- 对局完整性: 4种DSL全部不出错完成
- 构造听牌: 完整手牌第一轮自摸

手动出牌 (--human):
- HumanMahjongPlayer: 显示手牌/可选操作/h=胡/p=碰/k=杠/1-N=出牌
- MahjongRoom: humanPlayer参数

修复: DiscardPool碰/胡后不移除→幽灵牌→总牌数溢出
2026-07-04 11:45:54 +08:00

216 lines
7.7 KiB
C#

using RuleEngine;
using RuleEngine.Core;
using RuleEngine.Dsl;
// Parse arguments
bool autoMode = args.Contains("--auto");
bool humanMode = args.Contains("--human");
string dslPath = "dsl-examples/xuezhandaodi.yaml";
int dslIdx = Array.IndexOf(args, "--dsl");
if (dslIdx >= 0 && dslIdx + 1 < args.Length)
dslPath = $"dsl-examples/{args[dslIdx + 1]}.yaml";
int count = 1;
int countIdx = Array.IndexOf(args, "--count");
if (countIdx >= 0 && countIdx + 1 < args.Length)
int.TryParse(args[countIdx + 1], out count);
// Init engine
var caps = new CapabilityRegistry();
caps.Register("meldsolver.standard_win");
caps.Register("meldsolver.seven_pairs");
caps.Register("meldsolver.thirteen_orphans");
caps.Register("meldsolver.all_orphans");
caps.Register("meldsolver.double_dragon");
caps.Register("meldsolver.wildcard");
caps.Register("deck.flower_cards");
caps.Register("phase.mahjong_turn");
caps.Register("phase.parallel_elimination");
caps.Register("phase.priority_arbitration");
caps.Register("scoring.fan_exclusion");
caps.Register("scoring.pre_hooks");
caps.Register("deck.generator_mahjong");
var loader = new DslLoader(caps);
MahjongDslRoot rules;
try
{
rules = loader.Load(dslPath);
}
catch (Exception ex)
{
Console.WriteLine($"❌ DSL 加载失败: {ex.Message}");
return 1;
}
if (autoMode)
{
// Batch mode: run N rounds silently
Console.WriteLine($"=== 麻将规则引擎 Demo — {rules.Game.Name} === (自动模式 {count}局)");
int wins = 0, errors = 0;
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
for (int i = 0; i < count; i++)
{
try
{
var room = new MahjongRoom(rules, new[] { "AI-东", "AI-南", "AI-西", "AI-北" }, autoMode: true,
humanPlayer: humanMode ? "AI-东" : null);
room.Run();
if (room.State.HuPlayers.Count > 0)
wins++;
var elapsed = stopwatch.ElapsedMilliseconds;
if ((i + 1) % 100 == 0 || i == count - 1)
Console.WriteLine($"[局 {i + 1}/{count}] ✅ {(room.State.HuPlayers.Count > 0 ? room.State.HuPlayers[0] + "" : "")} | 总耗时 {(elapsed / 1000.0):F1}s");
}
catch (Exception ex)
{
errors++;
Console.WriteLine($"[局 {i + 1}/{count}] ❌ 错误: {ex.Message}");
}
}
stopwatch.Stop();
Console.WriteLine("\n========================================");
Console.WriteLine($"统计: 总对局 {count} | 胡牌率 {(double)wins / count:P1} | 出错 {errors} | 平均 {(double)stopwatch.ElapsedMilliseconds / count:F0}ms/局");
}
else
{
// Interactive mode: step-by-step with key-press advancement
Console.WriteLine($"=== 麻将规则引擎 Demo — {rules.Game.Name} === (交互模式,按 Enter 继续下一步)");
Console.WriteLine();
var room = new MahjongRoom(rules, new[] { "AI-东", "AI-南", "AI-西", "AI-北" }, autoMode: false,
humanPlayer: humanMode ? "AI-东" : null);
room.Deal();
// Show dealt hands
RenderDeal(room);
Console.Write("\n[按 Enter 开始对局]");
Console.ReadLine();
// Step through each turn
int turnNumber = 0;
while (!room.IsFinished)
{
turnNumber++;
var events = room.StepTurn();
// Skip deal phase transition
if (room.State.Phase == "deal") continue;
Console.WriteLine($"\n--- 第 {turnNumber} 回合 | 牌墙剩余 {room.State.Deck.Count} 张 ---");
// Show events for this turn
foreach (var evt in events)
{
switch (evt.Type)
{
case "draw":
Console.WriteLine($" [{evt.Player}] 摸牌: {MahjongTile.ToString(evt.Tile ?? 0)}");
break;
case "discard":
Console.WriteLine($" [{evt.Player}] 出牌: {MahjongTile.ToString(evt.Tile ?? 0)}");
break;
case "pung":
Console.WriteLine($" → {evt.Player}: 碰!{MahjongTile.ToString(evt.Tile ?? 0)}");
break;
case "ming_kong":
Console.WriteLine($" → {evt.Player}: 明杠!{MahjongTile.ToString(evt.Tile ?? 0)}");
break;
case "win":
Console.WriteLine($" ✅ {evt.Player}: 胡!{evt.Description}");
break;
case "deck_exhausted":
Console.WriteLine($" ⚠ 牌墙耗尽!");
break;
case "hua_zhu":
Console.WriteLine($" ⚠ {evt.Player}: 花猪(三色齐全)");
break;
case "hua_zhu_ok":
break;
case "ting_checked":
Console.WriteLine($" ✓ {evt.Player}: 听牌");
break;
case "ting_failed":
Console.WriteLine($" ✗ {evt.Player}: 未听牌");
break;
case "settle":
Console.WriteLine($" 💰 结算");
break;
}
}
// Show current hands
if (!room.IsFinished)
{
Console.WriteLine();
Console.WriteLine(" 当前手牌:");
foreach (var p in room.State.PlayerOrder)
{
if (!room.State.AlivePlayers.Contains(p)) continue;
var hand = room.State.Hands[p];
Console.WriteLine($" {p}: {string.Join(" ", hand.OrderBy(t => t).Select(MahjongTile.ToString))} ({hand.Count}张)");
}
Console.Write("\n[按 Enter 继续]");
Console.ReadLine();
}
}
// Final render
Console.WriteLine("\n══════════════════════════════════════");
RenderFinal(room);
}
return 0;
void RenderDeal(MahjongRoom room)
{
var state = room.State;
Console.WriteLine("═══ 发牌 ═══");
foreach (var p in state.PlayerOrder)
Console.WriteLine($" {p}{(p == state.Dealer ? " ()" : "")}: {state.Hands[p].Count}张");
Console.WriteLine($" 牌墙: {state.Deck.Count} 张");
Console.WriteLine($" (手牌已洗牌,按 Enter 查看牌局)");
}
void RenderFinal(MahjongRoom room)
{
var state = room.State;
Console.WriteLine("[最终状态]");
Console.WriteLine($" 游戏结束,牌墙剩余 {state.Deck.Count} 张");
Console.WriteLine();
Console.WriteLine(" 最终状态:");
foreach (var p in state.PlayerOrder)
{
var hand = state.Hands[p];
var sorted = string.Join(", ", hand.OrderBy(t => t).Select(MahjongTile.ToString));
var exposedStr = "";
if (state.Exposed.TryGetValue(p, out var melds) && melds.Count > 0)
{
var meldDescs = melds.Select(m =>
$"{m.Type}[{string.Join(",", m.Tiles.Select(MahjongTile.ToString))}]");
exposedStr = $" | 已碰/杠: {string.Join(" ", meldDescs)}";
}
Console.WriteLine($" {p}{(p == state.Dealer ? "()" : "")}: {sorted} ({hand.Count}张){exposedStr}");
}
if (state.HuPlayers.Count > 0)
{
Console.WriteLine();
Console.WriteLine($" 胡牌: {string.Join(", ", state.HuPlayers)}");
}
Console.WriteLine();
Console.WriteLine("[最终结算]");
foreach (var (p, score) in state.Scores)
Console.WriteLine($" {p}: {(score >= 0 ? "+" : "")}{score}分");
int total = state.Scores.Values.Sum();
Console.WriteLine($" 总分: {(total >= 0 ? "+" : "")}{total} {(total == 0 ? "" : " ")}");
Console.WriteLine("══════════════════════════════════════");
}