namespace RuleEngine.Dsl; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; public class CapabilityRegistry { private readonly HashSet _capabilities = new(); public void Register(string id) => _capabilities.Add(id); public bool Has(string id) => _capabilities.Contains(id); public void Check(IEnumerable required) { var missing = required.Where(r => !_capabilities.Contains(r)).ToList(); if (missing.Count > 0) { string list = string.Join("\n ", missing.Select(m => $"❌ {m} — 引擎尚未实现")); throw new InvalidOperationException( $"DSL 需要的以下算法能力引擎尚未实现:\n {list}\n\n" + $"请添加对应实现后注册 Capability。\n" + $"当前引擎能力: {string.Join(", ", _capabilities)}"); } } } // === DSL POCO types === public class MahjongDslRoot { public GameInfo Game { get; set; } = new(); public List Requires { get; set; } = new(); public DeckConfig Deck { get; set; } = new(); public DealConfig Deal { get; set; } = new(); public WildcardConfig? WildcardRules { get; set; } public WinConditionConfig? WinCondition { get; set; } public WinRuleConfig? WinRule { get; set; } public List PreHooks { get; set; } = new(); public List FanTypes { get; set; } = new(); public string FanStacking { get; set; } = "add"; public int MaxFan { get; set; } = int.MaxValue; public List Phases { get; set; } = new(); public ScoringDslConfig Scoring { get; set; } = new(); public int WinMinFan { get; set; } = 0; public FlowerDslConfig? FlowerRules { get; set; } } public class GameInfo { public string Name { get; set; } = ""; public string Type { get; set; } = ""; } public class DeckConfig { public string Generator { get; set; } = "mahjong"; public int Total { get; set; } public bool IncludeHonors { get; set; } public bool IncludeFlowers { get; set; } public int WildcardCount { get; set; } public string? WildcardTile { get; set; } } public class DealConfig { public int CardsPerPlayer { get; set; } = 13; public int DealerExtra { get; set; } = 1; } public class WildcardConfig { public string Type { get; set; } = ""; public string Behavior { get; set; } = ""; public int WildcardEncoding { get; set; } = 50; public List? Tiles { get; set; } public WildcardScoringConfig? Scoring { get; set; } public string FanCalculationPolicy { get; set; } = "optimal"; } public class WildcardScoringConfig { public int PerWildcardInWin { get; set; } } public class WinConditionConfig { public bool PairMustBe258 { get; set; } } public class WinRuleConfig { public bool JiHuSelfDrawOnly { get; set; } } public class PreHookConfig { public string Name { get; set; } = ""; public string Description { get; set; } = ""; } public class FanTypeConfig { public string Name { get; set; } = ""; public int BaseFan { get; set; } public int Level { get; set; } public List? Excludes { get; set; } public List? Conflicts { get; set; } public string? Condition { get; set; } } public class PhaseDslConfig { public string Name { get; set; } = ""; public string Type { get; set; } = ""; public string? Action { get; set; } public string? Next { get; set; } public SubPhasesConfig? SubPhases { get; set; } public List? EndConditions { get; set; } public bool ParallelElimination { get; set; } public string? OnEliminate { get; set; } } public class SubPhasesConfig { public DrawSubPhase? Draw { get; set; } public SelfActionSubPhase? SelfAction { get; set; } public OthersReactionSubPhase? OthersReaction { get; set; } } public class DrawSubPhase { public string Type { get; set; } = "auto"; public string Action { get; set; } = "draw_card"; } public class SelfActionSubPhase { public List Options { get; set; } = new(); } public class OthersReactionSubPhase { public List Options { get; set; } = new(); public string PriorityPolicy { get; set; } = "highest_wins"; public string? OnWin { get; set; } } public class ActionOptionDsl { public string Action { get; set; } = ""; public int Priority { get; set; } public string? Condition { get; set; } } public class EndConditionDsl { public string Type { get; set; } = ""; public string Action { get; set; } = ""; } public class ScoringDslConfig { public string Mode { get; set; } = "fan_table"; public int MaxCap { get; set; } = int.MaxValue; } public class FlowerDslConfig { public string OnDraw { get; set; } = ""; } public class DslLoader { private readonly CapabilityRegistry _caps; private readonly IDeserializer _deserializer; public DslLoader(CapabilityRegistry caps) { _caps = caps; _deserializer = new DeserializerBuilder() .WithNamingConvention(UnderscoredNamingConvention.Instance) .IgnoreUnmatchedProperties() .Build(); } public MahjongDslRoot Load(string path) { if (!File.Exists(path)) throw new FileNotFoundException($"DSL file not found: {path}"); var yaml = File.ReadAllText(path); return LoadString(yaml, path); } public MahjongDslRoot LoadString(string yaml, string source = "inline") { var dsl = _deserializer.Deserialize(yaml) ?? throw new InvalidOperationException($"Failed to parse DSL: {source}"); // Check capabilities _caps.Check(dsl.Requires); return dsl; } }