用法: dotnet run --project Demo -- --check --dsl new_variant 输出: 能力覆盖率 + 合规检查 + 仅验证模式退出 文档: README + dsl-specification 更新创建流程
275 lines
10 KiB
C#
275 lines
10 KiB
C#
using RuleEngine;
|
|
using RuleEngine.Core;
|
|
using RuleEngine.Dsl;
|
|
|
|
// Parse arguments
|
|
bool autoMode = args.Contains("--auto");
|
|
bool humanMode = args.Contains("--human");
|
|
bool checkOnly = args.Contains("--check");
|
|
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);
|
|
var (r, m, pct) = caps.ComputeCoverage(rules.Requires);
|
|
string status = pct == 100 ? "✅" : "⚠";
|
|
Console.WriteLine($"{status} DSL 适配率: {m}/{r} ({pct}%) — {rules.Game.Name}");
|
|
|
|
// Rule-level compliance check (finer granularity than capability check)
|
|
var warnings = caps.ValidateRuleCompliance(rules);
|
|
if (warnings.Count > 0)
|
|
{
|
|
Console.WriteLine($" ⚠ {warnings.Count} 条规则引擎无法完全执行:");
|
|
foreach (var w in warnings)
|
|
Console.WriteLine($" • {w}");
|
|
}
|
|
|
|
if (pct < 100)
|
|
{
|
|
var missing = rules.Requires.Where(req => !caps.Has(req));
|
|
foreach (var miss in missing)
|
|
Console.WriteLine($" ❌ {miss} — 引擎尚未实现,相关规则将被跳过");
|
|
}
|
|
Console.WriteLine();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"❌ DSL 加载失败: {ex.Message}");
|
|
return 1;
|
|
}
|
|
|
|
if (checkOnly)
|
|
{
|
|
Console.WriteLine($"✅ 仅验证模式 — 未运行牌局");
|
|
return 0;
|
|
}
|
|
|
|
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)
|
|
{
|
|
// In human mode, show only opponent hands (human HUD shows their own)
|
|
Console.WriteLine();
|
|
if (humanMode)
|
|
{
|
|
Console.WriteLine(" 对手状态:");
|
|
foreach (var p in room.State.PlayerOrder)
|
|
{
|
|
if (p == "AI-东") continue; // skip human
|
|
if (!room.State.AlivePlayers.Contains(p)) continue;
|
|
var hand = room.State.Hands[p];
|
|
var exposedStr = "";
|
|
if (room.State.Exposed.TryGetValue(p, out var melds) && melds.Count > 0)
|
|
{
|
|
var descs = melds.Select(m =>
|
|
$"{m.Type}[{string.Join(",", m.Tiles.Where(t => t != -1).Select(MahjongTile.ToString))}]");
|
|
exposedStr = $" | 已{string.Join(" ", descs)}";
|
|
}
|
|
var discardStr = "";
|
|
if (room.State.DiscardHistory.TryGetValue(p, out var discards) && discards.Count > 0)
|
|
{
|
|
var recent = discards.TakeLast(6).Select(MahjongTile.ToString);
|
|
discardStr = $" | 出: {string.Join(" ", recent)}";
|
|
}
|
|
Console.WriteLine($" {p}: {hand.Count}张手牌{exposedStr}{discardStr}");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
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}张)");
|
|
}
|
|
}
|
|
|
|
// In human mode, don't wait for Enter — human interactions already pause
|
|
if (!humanMode)
|
|
{
|
|
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("══════════════════════════════════════");
|
|
}
|