Files
card-game-engine/RuleEngine/Core/GameState.cs
xiaoou 3f91d7c3f8 feat: 四归一/圈风/门风/单钓将识别器 + GameState状态跟踪
新增4个番型识别器:
- 四归一: 4张同牌跨面子使用(不在单个kezi/kong) — 纯结构检测
- 圈风: 当前风圈对应的风刻(31=东/32=南/33=西/34=北)
- 门风: 玩家座位风位对应的风刻(庄=东)
- 单钓将: 自摸时摸到的牌完成了将牌的单吊对子

GameState扩展:
- WindRound(当前风圈) + SeatWinds(玩家→风向) + IsSingleWait flag
- Deal()分配座位风位(庄家=东,按逆时针)
- StepTurn自摸时检测单钓将(摸牌←→对子匹配)

效果: 国标--check 6→0 (圈风/门风/单钓将/四归一全部消除)
所有玩法合规检查通过(南昌/血战仅base_fan=0标记警告)
2026-07-04 23:31:31 +08:00

97 lines
4.2 KiB
C#

namespace RuleEngine.Core;
using RuleEngine;
public class GameEvent
{
public string Type { get; set; } = "";
public string Player { get; set; } = "";
public string? Description { get; set; }
public int? Tile { get; set; }
}
public class MahjongGameState
{
public string Phase { get; set; } = "";
public Dictionary<string, List<int>> Hands { get; set; } = new();
public Dictionary<string, List<Meld>> Exposed { get; set; } = new();
public Dictionary<string, List<int>> FlowerPool { get; set; } = new();
public List<int> Deck { get; set; } = new();
public List<int> DiscardPool { get; set; } = new();
public Dictionary<string, List<int>> DiscardHistory { get; set; } = new();
public int? LastDiscard { get; set; }
public string? LastDiscardPlayer { get; set; }
public string CurrentPlayer { get; set; } = "";
public List<string> PlayerOrder { get; set; } = new();
public string Dealer { get; set; } = "";
public int RoundNumber { get; set; }
public Dictionary<string, int> Scores { get; set; } = new();
public List<string> HuPlayers { get; set; } = new();
public List<string> AlivePlayers { get; set; } = new();
public Dictionary<string, bool> FuFlags { get; set; } = new();
public bool IsDeckExhausted { get; set; }
public List<GameEvent> RecentEvents { get; set; } = new();
public int FlowerReplaced { get; set; }
public int? LastDrawnTile { get; set; }
public WildcardRegistry Wildcards { get; set; } = new();
// Event flags for event-based fan types (杠上开花/海底捞月/抢杠胡/天胡/地胡)
public bool IsKongDraw { get; set; } // 杠后摸牌 — set after kong draw, cleared on next discard
public bool IsLastTile { get; set; } // 海底 — set when deck has 1 tile before draw
public bool IsRobbedKong { get; set; } // 抢杠 — set when someone adds to pung (bu_kong)
public bool IsFirstTurn { get; set; } // 第一轮 — set for dealer after deal, non-dealer after first draw
public string? DealerOnFirstTurn { get; set; } // 庄家首轮(用于天胡判断)
public bool IsSingleWait { get; set; } // 单钓将: 自摸时摸到的牌完成了将牌的对子
// 圈风/门风: 用于圈风刻/门风刻番型
public int WindRound { get; set; } = 31; // 当前风圈: 31=东/32=南/33=西/34=北
public Dictionary<string, int> SeatWinds { get; set; } = new(); // 玩家→座位风位
public MahjongGameState Clone()
{
return new MahjongGameState
{
Phase = Phase,
Hands = Hands.ToDictionary(kv => kv.Key, kv => new List<int>(kv.Value)),
Exposed = Exposed.ToDictionary(kv => kv.Key, kv => kv.Value.Select(m => m.Clone()).ToList()),
FlowerPool = FlowerPool.ToDictionary(kv => kv.Key, kv => new List<int>(kv.Value)),
Deck = new List<int>(Deck),
DiscardPool = new List<int>(DiscardPool),
LastDiscard = LastDiscard,
LastDiscardPlayer = LastDiscardPlayer,
CurrentPlayer = CurrentPlayer,
PlayerOrder = new List<string>(PlayerOrder),
Dealer = Dealer,
RoundNumber = RoundNumber,
Scores = new Dictionary<string, int>(Scores),
HuPlayers = new List<string>(HuPlayers),
AlivePlayers = new List<string>(AlivePlayers),
FuFlags = new Dictionary<string, bool>(FuFlags),
IsDeckExhausted = IsDeckExhausted,
RecentEvents = new List<GameEvent>(RecentEvents),
FlowerReplaced = FlowerReplaced,
LastDrawnTile = LastDrawnTile,
Wildcards = Wildcards.Clone(),
IsKongDraw = IsKongDraw,
IsLastTile = IsLastTile,
IsRobbedKong = IsRobbedKong,
IsFirstTurn = IsFirstTurn,
DealerOnFirstTurn = DealerOnFirstTurn,
IsSingleWait = IsSingleWait,
WindRound = WindRound,
SeatWinds = new Dictionary<string, int>(SeatWinds)
};
}
public void AddEvent(string type, string player, int? tile = null, string? desc = null)
{
RecentEvents.Add(new GameEvent
{
Type = type,
Player = player,
Tile = tile,
Description = desc
});
}
}