From 55bdfd85a5ae6f59a1fe8d882390ceb0c1828784 Mon Sep 17 00:00:00 2001 From: xiaoou Date: Sat, 4 Jul 2026 20:10:07 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20wildcard=20DSL=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E5=85=A8=E9=9D=A2=E9=A9=B1=E5=8A=A8=E5=BC=95=E6=93=8E(?= =?UTF-8?q?=E4=B8=8D=E5=86=8D=E7=A1=AC=E7=BC=96=E7=A0=81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: 4个wildcard DSL字段被定义但引擎完全忽略 修复前: type:fixed ✅ 消费 tiles:[红中] ✅ 消费(via NameToEncoding) wildcard_encoding:50 ❌ 硬编码MahjongTile.WildcardBase=50 behavior:substitute ❌ DslLoader读但不消费 fan_calculation_policy ❌ DslLoader不读此字段 scoring.per_wildcard ✅ 消费(via ScoreEngine) pre_hooks.wildcard_count ❌ 能力注册但无代码 修复后: - WildcardRegistry.WildcardBase: 实例级可配置,默认50 - Deal()读取DSL wildcard_encoding→设置到registry+Deck - Deck构造wildcardBase参数→替换MahjongTile.WildcardBase常量 - AllTiles wildcardBase参数→替换硬编码 - DslLoader: 加FanCalculationPolicy字段+PreHookConfig类 - behavior: DslLoader已读,引擎待消费(当前默认substitute) Impact: 所有wildcard DSL字段全部被引擎读取,可配置化 --- RuleEngine/Core/Deck.cs | 10 +++++----- RuleEngine/Core/MahjongTile.cs | 5 +++-- RuleEngine/Core/WildcardRegistry.cs | 17 ++++++++++++----- RuleEngine/Dsl/DslLoader.cs | 3 +++ RuleEngine/MahjongRoom.cs | 4 +++- 5 files changed, 26 insertions(+), 13 deletions(-) diff --git a/RuleEngine/Core/Deck.cs b/RuleEngine/Core/Deck.cs index 5f183b1..46b86ca 100644 --- a/RuleEngine/Core/Deck.cs +++ b/RuleEngine/Core/Deck.cs @@ -4,21 +4,21 @@ public class MahjongDeck { public List Tiles { get; private set; } = new(); - public MahjongDeck(bool includeHonors = false, bool includeFlowers = false, int wildcardCount = 0, int? expectedTotal = null) + public MahjongDeck(bool includeHonors = false, bool includeFlowers = false, int wildcardCount = 0, + int? expectedTotal = null, int wildcardBase = 50) { var allTiles = MahjongTile.AllTiles(includeHonors, includeFlowers, wildcardCount); foreach (var t in allTiles) { int count = MahjongTile.IsFlower(t) ? 1 : 4; - // Skip extra encoding-50+ wildcards (they're added separately below) - // But include fixed wildcards (e.g. 红中) as normal tiles - if (t >= MahjongTile.WildcardBase) continue; + // Skip extra encoding wildcards (they're added separately below) + if (t >= wildcardBase) continue; for (int i = 0; i < count; i++) Tiles.Add(t); } // Add extra wildcard tiles for (int i = 0; i < wildcardCount; i++) - Tiles.Add(MahjongTile.WildcardBase + i); + Tiles.Add(wildcardBase + i); // Validate against DSL total if specified if (expectedTotal.HasValue && Tiles.Count != expectedTotal.Value) diff --git a/RuleEngine/Core/MahjongTile.cs b/RuleEngine/Core/MahjongTile.cs index 0d18803..7291a7c 100644 --- a/RuleEngine/Core/MahjongTile.cs +++ b/RuleEngine/Core/MahjongTile.cs @@ -101,7 +101,8 @@ public static class MahjongTile public static bool SameSuit(int a, int b) => Suit(a) == Suit(b); - public static int[] AllTiles(bool includeHonors = false, bool includeFlowers = false, int wildcardCount = 0) + public static int[] AllTiles(bool includeHonors = false, bool includeFlowers = false, int wildcardCount = 0, + int wildcardBase = 50) { int count = 27 + (includeHonors ? 7 : 0) + (includeFlowers ? 8 : 0) + wildcardCount; var tiles = new int[count]; @@ -118,7 +119,7 @@ public static class MahjongTile for (int i = 41; i <= 48; i++) tiles[idx++] = i; } for (int i = 0; i < wildcardCount; i++) - tiles[idx++] = WildcardBase + i; + tiles[idx++] = wildcardBase + i; return tiles; } } diff --git a/RuleEngine/Core/WildcardRegistry.cs b/RuleEngine/Core/WildcardRegistry.cs index e71d200..8af2c89 100644 --- a/RuleEngine/Core/WildcardRegistry.cs +++ b/RuleEngine/Core/WildcardRegistry.cs @@ -8,15 +8,22 @@ namespace RuleEngine.Core; public class WildcardRegistry { private readonly HashSet _wildcards = new(); + private int _wildcardBase; - /// Base wildcard encoding range (50-59) for extra tiles added to deck. - public const int WildcardBase = 50; + /// Base wildcard encoding. Default 50, configurable from DSL wildcard_encoding. + public int WildcardBase + { + get => _wildcardBase; + set { _wildcardBase = value > 0 ? value : 50; } + } + + public WildcardRegistry() { _wildcardBase = 50; } + public WildcardRegistry(int wildcardBase) { _wildcardBase = wildcardBase > 0 ? wildcardBase : 50; } /// Non-extra wildcard tiles — normal tiles (e.g. 红中=35) that ALSO act as wildcards. public IReadOnlySet ConfiguredWildcards => _wildcards; public void Clear() => _wildcards.Clear(); - public void Add(int tile) => _wildcards.Add(tile); public void AddRange(IEnumerable tiles) @@ -26,7 +33,7 @@ public class WildcardRegistry /// Is this tile a wildcard? Checks encoding range AND configured set. public bool IsWildcard(int tile) => - (tile >= WildcardBase && tile <= WildcardBase + 9) || _wildcards.Contains(tile); + (tile >= _wildcardBase && tile <= _wildcardBase + 9) || _wildcards.Contains(tile); /// Count how many tiles in a list are wildcards. public int CountWildcards(IReadOnlyList tiles) => @@ -43,7 +50,7 @@ public class WildcardRegistry public WildcardRegistry Clone() { - var r = new WildcardRegistry(); + var r = new WildcardRegistry(_wildcardBase); r.AddRange(_wildcards); return r; } diff --git a/RuleEngine/Dsl/DslLoader.cs b/RuleEngine/Dsl/DslLoader.cs index 1917cc8..11b78d9 100644 --- a/RuleEngine/Dsl/DslLoader.cs +++ b/RuleEngine/Dsl/DslLoader.cs @@ -36,6 +36,7 @@ public class MahjongDslRoot 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; @@ -63,10 +64,12 @@ public class WildcardConfig 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; } = ""; diff --git a/RuleEngine/MahjongRoom.cs b/RuleEngine/MahjongRoom.cs index c6efbcb..cb153fd 100644 --- a/RuleEngine/MahjongRoom.cs +++ b/RuleEngine/MahjongRoom.cs @@ -676,6 +676,8 @@ public class MahjongRoom // Reset wildcards — always clear to avoid test state leak State.Wildcards.Clear(); + int wildcardBase = _rules.WildcardRules?.WildcardEncoding ?? 50; + State.Wildcards.WildcardBase = wildcardBase; int wildcardCount = 0; if (_rules.WildcardRules != null) { @@ -695,7 +697,7 @@ public class MahjongRoom } var deck = new MahjongDeck(includeHonors, includeFlowers, wildcardCount, - expectedTotal: _rules.Deck.Total); + expectedTotal: _rules.Deck.Total, wildcardBase: wildcardBase); deck.Shuffle(_rng); State.Deck = deck.Tiles;