fix: 癞子=136张内的牌(红中),不是额外+4张 + 宝牌HUD显示
- MahjongTile.ConfigureWildcards: 支持固定wildcard(如红中35) - IsWildcard: 同时检查encoding 50+和配置的wildcard - ToString: wildcard牌显示🀫中/🀫发等 - Deck: 固定wildcard作为普通牌加入(红中在136内) - Deal: fixed类型wildcard不额外加牌 - HUD: 显示 '🀫 宝牌(癞子): 🀫中' - Wuhan DSL: 移除wildcardCount:4 - 测试: Wuhan牌库136张
This commit is contained in:
@ -55,8 +55,8 @@ public class MahjongRoomIntegrationTests
|
|||||||
room.Deal();
|
room.Deal();
|
||||||
|
|
||||||
int totalTiles = room.State.Hands.Sum(h => h.Value.Count) + room.State.Deck.Count;
|
int totalTiles = room.State.Hands.Sum(h => h.Value.Count) + room.State.Deck.Count;
|
||||||
// 136 tiles + 4 wildcards = 140
|
// 136 tiles total (108 numbered + 28 honors, 红中 is wildcard but part of the 136)
|
||||||
Assert.Equal(140, totalTiles);
|
Assert.Equal(136, totalTiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Turn flow ===
|
// === Turn flow ===
|
||||||
|
|||||||
@ -33,6 +33,11 @@ public class HumanMahjongPlayer
|
|||||||
Console.WriteLine($"=== {Name} 的回合 ===");
|
Console.WriteLine($"=== {Name} 的回合 ===");
|
||||||
Console.WriteLine($" 牌墙: {state.Deck.Count}张 | 弃牌堆: {state.DiscardPool.Count}张");
|
Console.WriteLine($" 牌墙: {state.Deck.Count}张 | 弃牌堆: {state.DiscardPool.Count}张");
|
||||||
|
|
||||||
|
// Show wildcard info
|
||||||
|
var wcTiles = MahjongTile.ConfiguredWildcards;
|
||||||
|
if (wcTiles.Count > 0)
|
||||||
|
Console.WriteLine($" 🀫 宝牌(癞子): {string.Join(" ", wcTiles.Select(MahjongTile.ToString))}");
|
||||||
|
|
||||||
// Show exposed melds
|
// Show exposed melds
|
||||||
if (state.Exposed.TryGetValue(Name, out var exposed) && exposed.Count > 0)
|
if (state.Exposed.TryGetValue(Name, out var exposed) && exposed.Count > 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -10,12 +10,13 @@ public class MahjongDeck
|
|||||||
foreach (var t in allTiles)
|
foreach (var t in allTiles)
|
||||||
{
|
{
|
||||||
int count = MahjongTile.IsFlower(t) ? 1 : 4;
|
int count = MahjongTile.IsFlower(t) ? 1 : 4;
|
||||||
// Wildcards are handled by AllTiles already
|
// Skip extra encoding-50+ wildcards (they're added separately below)
|
||||||
if (MahjongTile.IsWildcard(t)) continue;
|
// But include fixed wildcards (e.g. 红中) as normal tiles
|
||||||
|
if (t >= MahjongTile.WildcardBase) continue;
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
Tiles.Add(t);
|
Tiles.Add(t);
|
||||||
}
|
}
|
||||||
// Add wildcards separately
|
// Add extra wildcard tiles
|
||||||
for (int i = 0; i < wildcardCount; i++)
|
for (int i = 0; i < wildcardCount; i++)
|
||||||
Tiles.Add(MahjongTile.WildcardBase + i);
|
Tiles.Add(MahjongTile.WildcardBase + i);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,19 @@ public static class MahjongTile
|
|||||||
// Encoding: 万1-9=1-9, 条1-9=11-19, 筒1-9=21-29
|
// Encoding: 万1-9=1-9, 条1-9=11-19, 筒1-9=21-29
|
||||||
// 字: 东=31,南=32,西=33,北=34,中=35,发=36,白=37
|
// 字: 东=31,南=32,西=33,北=34,中=35,发=36,白=37
|
||||||
// 花: 春=41..菊=48
|
// 花: 春=41..菊=48
|
||||||
// 宝牌: 50-59
|
// 宝牌: 50-59 (extra wildcards added to deck)
|
||||||
|
|
||||||
|
private static readonly HashSet<int> _wildcards = new();
|
||||||
|
|
||||||
|
/// <summary>Configure which normal tiles (e.g. 红中=35) also act as wildcards.</summary>
|
||||||
|
public static void ConfigureWildcards(IEnumerable<int> tiles)
|
||||||
|
{
|
||||||
|
_wildcards.Clear();
|
||||||
|
foreach (var t in tiles) _wildcards.Add(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Get the set of normal tiles configured as wildcards.</summary>
|
||||||
|
public static IReadOnlySet<int> ConfiguredWildcards => _wildcards;
|
||||||
|
|
||||||
public static int Encode(string suit, int rank)
|
public static int Encode(string suit, int rank)
|
||||||
{
|
{
|
||||||
@ -24,6 +36,7 @@ public static class MahjongTile
|
|||||||
if (tile >= 1 && tile <= 9) return $"{tile}万";
|
if (tile >= 1 && tile <= 9) return $"{tile}万";
|
||||||
if (tile >= 11 && tile <= 19) return $"{tile - 10}条";
|
if (tile >= 11 && tile <= 19) return $"{tile - 10}条";
|
||||||
if (tile >= 21 && tile <= 29) return $"{tile - 20}筒";
|
if (tile >= 21 && tile <= 29) return $"{tile - 20}筒";
|
||||||
|
if (_wildcards.Contains(tile)) return $"🀫{TileName(tile)}";
|
||||||
return tile switch
|
return tile switch
|
||||||
{
|
{
|
||||||
31 => "东", 32 => "南", 33 => "西", 34 => "北",
|
31 => "东", 32 => "南", 33 => "西", 34 => "北",
|
||||||
@ -35,6 +48,13 @@ public static class MahjongTile
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string TileName(int tile) => tile switch
|
||||||
|
{
|
||||||
|
31 => "东", 32 => "南", 33 => "西", 34 => "北",
|
||||||
|
35 => "中", 36 => "发", 37 => "白",
|
||||||
|
_ => $"?{tile}"
|
||||||
|
};
|
||||||
|
|
||||||
/// 0=万, 1=条, 2=筒, 3=字, 4=花, 5=宝牌
|
/// 0=万, 1=条, 2=筒, 3=字, 4=花, 5=宝牌
|
||||||
public static int Suit(int tile)
|
public static int Suit(int tile)
|
||||||
{
|
{
|
||||||
@ -64,7 +84,7 @@ public static class MahjongTile
|
|||||||
|
|
||||||
public static bool IsHonor(int tile) => tile >= 31 && tile <= 37;
|
public static bool IsHonor(int tile) => tile >= 31 && tile <= 37;
|
||||||
public static bool IsFlower(int tile) => tile >= 41 && tile <= 48;
|
public static bool IsFlower(int tile) => tile >= 41 && tile <= 48;
|
||||||
public static bool IsWildcard(int tile) => tile >= 50 && tile <= 59;
|
public static bool IsWildcard(int tile) => tile >= 50 && tile <= 59 || _wildcards.Contains(tile);
|
||||||
public static bool IsNumbered(int tile) => tile >= 1 && tile <= 29;
|
public static bool IsNumbered(int tile) => tile >= 1 && tile <= 29;
|
||||||
public static bool IsTerminal(int tile)
|
public static bool IsTerminal(int tile)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -438,7 +438,25 @@ public class MahjongRoom
|
|||||||
int perPlayer = _rules.Deal.CardsPerPlayer;
|
int perPlayer = _rules.Deal.CardsPerPlayer;
|
||||||
bool includeFlowers = _rules.Deck.IncludeFlowers;
|
bool includeFlowers = _rules.Deck.IncludeFlowers;
|
||||||
bool includeHonors = _rules.Deck.IncludeHonors;
|
bool includeHonors = _rules.Deck.IncludeHonors;
|
||||||
int wildcardCount = _rules.WildcardRules != null ? _rules.Deck.WildcardCount : 0;
|
|
||||||
|
// Configure fixed wildcards (e.g. Wuhan 红中)
|
||||||
|
int wildcardCount = 0;
|
||||||
|
if (_rules.WildcardRules != null)
|
||||||
|
{
|
||||||
|
if (_rules.WildcardRules.Type == "fixed" && _rules.WildcardRules.Tiles != null)
|
||||||
|
{
|
||||||
|
// Fixed wildcards: specified tiles ARE the wildcards, not extra tiles
|
||||||
|
var wcTiles = _rules.WildcardRules.Tiles
|
||||||
|
.Select(t => t switch { "红中" => 35, "发财" => 36, "白板" => 37, _ => -1 })
|
||||||
|
.Where(t => t > 0).ToArray();
|
||||||
|
MahjongTile.ConfigureWildcards(wcTiles);
|
||||||
|
wildcardCount = 0; // No extra tiles
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
wildcardCount = _rules.Deck.WildcardCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var deck = new MahjongDeck(includeHonors, includeFlowers, wildcardCount);
|
var deck = new MahjongDeck(includeHonors, includeFlowers, wildcardCount);
|
||||||
deck.Shuffle(_rng);
|
deck.Shuffle(_rng);
|
||||||
@ -479,8 +497,6 @@ public class MahjongRoom
|
|||||||
|
|
||||||
private void CheckTing()
|
private void CheckTing()
|
||||||
{
|
{
|
||||||
bool hasWildcard = _rules.WildcardRules != null;
|
|
||||||
int wildcardCount = hasWildcard ? _rules.Deck.WildcardCount : 0;
|
|
||||||
bool require258 = _rules.WinCondition?.PairMustBe258 ?? false;
|
bool require258 = _rules.WinCondition?.PairMustBe258 ?? false;
|
||||||
|
|
||||||
// All possible tiles that could be drawn (numbered 1-9 across 3 suits + 7 honors)
|
// All possible tiles that could be drawn (numbered 1-9 across 3 suits + 7 honors)
|
||||||
@ -500,8 +516,10 @@ public class MahjongRoom
|
|||||||
int alreadyHave = hand.Count(t => t == tile);
|
int alreadyHave = hand.Count(t => t == tile);
|
||||||
if (alreadyHave >= 4) continue; // can't draw more of this tile
|
if (alreadyHave >= 4) continue; // can't draw more of this tile
|
||||||
|
|
||||||
|
var handWithTile = new List<int>(hand) { tile };
|
||||||
|
int wc = CountWildcardsInHand(handWithTile);
|
||||||
var r = _solver.CheckWin(hand, newTile: tile,
|
var r = _solver.CheckWin(hand, newTile: tile,
|
||||||
wildcardCount: wildcardCount, require258Pair: require258);
|
wildcardCount: wc, require258Pair: require258);
|
||||||
if (r != null && r.IsWin) { ting = true; break; }
|
if (r != null && r.IsWin) { ting = true; break; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,8 +17,6 @@ deck:
|
|||||||
generator: mahjong
|
generator: mahjong
|
||||||
include_honors: true
|
include_honors: true
|
||||||
include_flowers: false
|
include_flowers: false
|
||||||
wildcard_tile: 红中
|
|
||||||
wildcard_count: 4
|
|
||||||
total: 136
|
total: 136
|
||||||
|
|
||||||
deal:
|
deal:
|
||||||
|
|||||||
Reference in New Issue
Block a user