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;