RuleEngine 核心类: - MahjongTile.cs — int编码 (1-29万条筒, 31-37字, 41-48花, 50-59宝牌) - MeldsSolver.cs — 标准回溯 + wildcard缺口填充 + 七对/十三幺/全不靠 - PhaseMachine.cs — 回合机(摸打碰杠胡 + 优先级仲裁) - ScoreEngine.cs — 番型计分 + 互斥图 - DslLoader.cs — YAML DSL加载 + 能力检查 4个DSL: 四川血战/广东鸡平胡/国标麻将/武汉麻将 控制台Demo: 交互模式 + 自动模式(--auto) 测试: 33个测试用例, 32个通过
131 lines
4.8 KiB
C#
131 lines
4.8 KiB
C#
using RuleEngine;
|
||
using RuleEngine.Core;
|
||
using RuleEngine.Dsl;
|
||
|
||
// Parse arguments
|
||
bool autoMode = args.Contains("--auto");
|
||
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;
|
||
}
|
||
|
||
Console.WriteLine($"=== 麻将规则引擎 Demo — {rules.Game.Name} === ({(autoMode ? "自动模式" : "交互模式")})");
|
||
Console.WriteLine();
|
||
|
||
if (autoMode)
|
||
{
|
||
// Batch mode: run N rounds
|
||
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);
|
||
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: single game with full output
|
||
var room = new MahjongRoom(rules, new[] { "AI-东", "AI-南", "AI-西", "AI-北" }, autoMode: false);
|
||
room.Run();
|
||
|
||
RenderGame(room);
|
||
}
|
||
|
||
return 0;
|
||
|
||
void RenderGame(MahjongRoom room)
|
||
{
|
||
var state = room.State;
|
||
|
||
Console.WriteLine("══════════════════════════════════════");
|
||
Console.WriteLine("[发牌]");
|
||
foreach (var p in state.PlayerOrder)
|
||
Console.WriteLine($" {p}{(p == state.Dealer ? "(庄)" : "")}: {string.Join(", ", state.Hands[p].OrderBy(t => t).Select(MahjongTile.ToString))} ({state.Hands[p].Count}张)");
|
||
Console.WriteLine($" 牌墙剩余: {state.Deck.Count} 张");
|
||
|
||
// Replay events
|
||
foreach (var evt in state.RecentEvents)
|
||
{
|
||
if (evt.Type == "win")
|
||
Console.WriteLine($" ✅ {evt.Player}: 自摸!番型: {evt.Description}");
|
||
else if (evt.Type == "discard")
|
||
Console.WriteLine($" [{evt.Player}] 出牌: {MahjongTile.ToString(evt.Tile ?? 0)}");
|
||
else if (evt.Type == "pung")
|
||
Console.WriteLine($" → {evt.Player}: 碰!");
|
||
else if (evt.Type == "draw")
|
||
Console.WriteLine($" [{evt.Player}] 摸牌: {MahjongTile.ToString(evt.Tile ?? 0)}");
|
||
else if (evt.Type == "deck_exhausted")
|
||
Console.WriteLine($" 牌墙耗尽!");
|
||
else if (evt.Type == "hua_zhu")
|
||
Console.WriteLine($" ⚠️ {evt.Player}: 花猪");
|
||
else if (evt.Type == "ting_checked")
|
||
Console.WriteLine($" ✓ {evt.Player}: 听牌");
|
||
else if (evt.Type == "ting_failed")
|
||
Console.WriteLine($" ❌ {evt.Player}: 未听牌");
|
||
else if (evt.Type == "settle")
|
||
Console.WriteLine($" 💰 结算");
|
||
}
|
||
|
||
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("══════════════════════════════════════");
|
||
}
|