diff --git a/RuleEngine/Core/GameState.cs b/RuleEngine/Core/GameState.cs index ae78cf3..fd2ca63 100644 --- a/RuleEngine/Core/GameState.cs +++ b/RuleEngine/Core/GameState.cs @@ -41,6 +41,11 @@ public class MahjongGameState 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 SeatWinds { get; set; } = new(); // 玩家→座位风位 public MahjongGameState Clone() { @@ -71,7 +76,10 @@ public class MahjongGameState IsLastTile = IsLastTile, IsRobbedKong = IsRobbedKong, IsFirstTurn = IsFirstTurn, - DealerOnFirstTurn = DealerOnFirstTurn + DealerOnFirstTurn = DealerOnFirstTurn, + IsSingleWait = IsSingleWait, + WindRound = WindRound, + SeatWinds = new Dictionary(SeatWinds) }; } diff --git a/RuleEngine/Dsl/DslLoader.cs b/RuleEngine/Dsl/DslLoader.cs index fe799b0..1393bb2 100644 --- a/RuleEngine/Dsl/DslLoader.cs +++ b/RuleEngine/Dsl/DslLoader.cs @@ -68,7 +68,7 @@ public class CapabilityRegistry "将一色","大四喜","大三元","小四喜","小三元","三风","双箭刻", "一色四同顺","一色三同顺","一般高","五门齐","全求人","门前清", "鸡胡","癞子胡", - "杠上开花","海底捞月","抢杠胡","天胡","地胡", + "圈风","门风","单钓将","四归一","杠上开花","海底捞月","抢杠胡","天胡","地胡", "将","无字" // base_fan:0/1 markers }; foreach (var f in rules.FanTypes) diff --git a/RuleEngine/MahjongRoom.cs b/RuleEngine/MahjongRoom.cs index 1d90e40..bdb9dc3 100644 --- a/RuleEngine/MahjongRoom.cs +++ b/RuleEngine/MahjongRoom.cs @@ -239,7 +239,23 @@ public class MahjongRoom var result = _solver.CheckWin(fullHand, wildcardCount: wildInHand, require258Pair: r258); if (result != null) + { + // 单钓将: drawn tile completed a pair from a singleton + if (State.LastDrawnTile.HasValue && result.PairTiles.Count == 2) + { + int drawn = State.LastDrawnTile.Value; + if (result.PairTiles.Contains(drawn)) + { + int other = result.PairTiles[0] == drawn ? result.PairTiles[1] : result.PairTiles[0]; + // Before drawing the winning tile, hand had 1 of the other pair tile + var handBefore = new List(fullHand); + handBefore.Remove(drawn); + if (handBefore.Count(t => t == other) == 1) + State.IsSingleWait = true; + } + } result.Fans = _solver.IdentifyFans(result, State); // event-based fans + } State.HuPlayers.Add(player); State.AlivePlayers.Remove(player); State.AddEvent("win", player, null, @@ -758,6 +774,15 @@ public class MahjongRoom State.AddEvent("deal_done", "", null, "发牌完成"); + // 圈风/门风: assign seat winds based on player order relative to dealer + int[] winds = { 31, 32, 33, 34 }; // 东南西北 + int dealerIdx = State.PlayerOrder.IndexOf(State.Dealer); + for (int i = 0; i < State.PlayerOrder.Count; i++) + { + int windIdx = (i - dealerIdx + 4) % 4; + State.SeatWinds[State.PlayerOrder[i]] = winds[windIdx]; + } + // Random wildcard (骰子翻牌定精): flip one tile from remaining deck to determine 精 if (_rules.WildcardRules?.Type == "random" && State.Deck.Count > 0) { diff --git a/RuleEngine/Patterns/MeldsSolver.cs b/RuleEngine/Patterns/MeldsSolver.cs index 96a9260..7b07e05 100644 --- a/RuleEngine/Patterns/MeldsSolver.cs +++ b/RuleEngine/Patterns/MeldsSolver.cs @@ -774,8 +774,25 @@ public class MeldsSolver fans.Add("天胡"); else if (state.IsFirstTurn) fans.Add("地胡"); + + // 圈风刻: kezi matching current wind round (e.g. 东风圈→有东刻子) + if (keziTiles.Any(t => t == state.WindRound)) + fans.Add("圈风"); + // 门风刻: kezi matching player's seat wind (applies per-hand, identified by state owner) + foreach (var (player, seatWind) in state.SeatWinds) + if (keziTiles.Any(t => t == seatWind)) + fans.Add("门风"); + + // 单钓将: detected at self-draw time (single tile→pair completion) + if (state.IsSingleWait) fans.Add("单钓将"); } + // 四归一: 4 identical numbered tiles used across 2+ melds (not in a single kong) + var tileCounts = allTiles.Where(t => MahjongTile.IsNumbered(t)).GroupBy(t => t); + bool hasSiGuiYi = tileCounts.Any(g => + g.Count() >= 4 && !melds.Any(m => m.Type == "kezi" && m.Tiles.Contains(g.Key) && m.Tiles.All(x => x == g.Key))); + if (hasSiGuiYi) fans.Add("四归一"); + // 鸡胡: base-level fan when no other fan with positive value exists. // A fan has positive value if it's in _fanConfig with base_fan>0, // or in the hardcoded fallback list (清一色/混一色/对对胡/碰碰和/暗七对/七对/带幺九/十三幺/鸡胡). @@ -797,7 +814,7 @@ public class MeldsSolver or "将一色" or "大四喜" or "大三元" or "小四喜" or "小三元" or "三风" or "双箭刻" or "一色四同顺" or "一色三同顺" or "一般高" or "五门齐" or "全求人" or "门前清" or "鸡胡" or "癞子胡" or "平和" - or "杠上开花" or "海底捞月" or "抢杠胡" or "天胡" or "地胡").ToList(); + or "圈风" or "门风" or "单钓将" or "四归一" or "杠上开花" or "海底捞月" or "抢杠胡" or "天胡" or "地胡").ToList(); } // === 番型互斥应用 ===