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} 能力注册: {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}"); } else { Console.WriteLine($" ✅ 规则合规检查通过"); } 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($"\n✅ 仅验证模式 — 未运行牌局\n 注意: 能力注册≠完整规则覆盖, 需逐条验证玩法规则"); 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; } } // 听牌检测: 每回合后显示各玩家听牌状态 var tingInfo = new List(); foreach (var p in room.State.AlivePlayers) { var tingTiles = room.GetTingTiles(p); if (tingTiles.Count > 0) { var tileNames = tingTiles.Select(MahjongTile.ToString); tingInfo.Add($"{p}: 🀄听 {tingTiles.Count}张({string.Join(" ", tileNames)})"); } else tingInfo.Add($"{p}: 未听"); } Console.WriteLine($" 听牌: {string.Join(" | ", tingInfo)}"); // Show current hands if (!room.IsFinished) { 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} 张"); // Show wildcard info if configured var wc = state.Wildcards; if (wc.ConfiguredWildcards.Count > 0) { var wcStr = string.Join(" ", wc.ConfiguredWildcards.Select(t => MahjongTile.ToString(t, wc))); if (wc.RevealedTile.HasValue) Console.WriteLine($" 🀫 精: {wcStr} (翻牌: {MahjongTile.ToString(wc.RevealedTile.Value)})"); else Console.WriteLine($" 🀫 宝牌: {wcStr}"); } 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("══════════════════════════════════════"); }