Files
card-game-engine/RuleEngine/AI/RandomMahjongAI.cs
xiaoou bb43ed7c89 fix: CheckWin缺少wildcard/258参数导致武汉麻将0%胡牌 + 庄家第一轮跳过摸牌
Root cause 1: 所有CheckWin调用未传wildcardCount/require258Pair
- StepTurn自摸: 5处遗漏 → 癞子在手牌中被当废牌
- HandleDiscardReactions: 2处遗漏
- PhaseMachine: 2处遗漏
→ 武汉麻将永远不能胡牌

Root cause 2: 庄家开局14张+摸牌=15张, CheckWin守卫≠14拒绝
→ 庄家永远不能自摸 → 修复: 庄家第一轮跳过摸牌

额外修复:
- RandomMahjongAI: 永远不出癞子(预留wildcard)
- ScoreEngine.FanValue: 从DSL _fanConfig读取番值
- HandleDiscardReactions: 同优先级座位顺序
- 新增GetWinParams/CountWildcardsInHand辅助方法

验证: 47/47测试, Wuhan 1.6%胡牌(500局), Sichuan 0.4%
2026-07-04 11:39:00 +08:00

54 lines
1.8 KiB
C#

namespace RuleEngine.AI;
using RuleEngine.Core;
using RuleEngine.Phase;
public class RandomMahjongAI
{
public string Name { get; }
private readonly Random _rng;
public RandomMahjongAI(string name, Random? rng = null)
{
Name = name;
_rng = rng ?? Random.Shared;
}
public (string action, int? tile) Decide(List<ActionOption> legalActions, MahjongGameState state)
{
if (legalActions.Count == 0)
return ("pass", null);
// Priority: win > kong > pung > chi > discard
var win = legalActions.FirstOrDefault(a => a.Action == "win");
if (win != null) return ("win", null);
var mingKong = legalActions.FirstOrDefault(a => a.Action == "ming_kong");
if (mingKong != null) return ("ming_kong", null);
var anKong = legalActions.FirstOrDefault(a => a.Action == "an_kong");
if (anKong != null) return ("an_kong", null);
var pung = legalActions.FirstOrDefault(a => a.Action == "pung");
if (pung != null) return ("pung", null);
var chi = legalActions.FirstOrDefault(a => a.Action == "chi");
if (chi != null) return ("chi", null);
// Discard: random non-wildcard tile
var discards = legalActions.Where(a => a.Action == "discard").ToList();
if (discards.Count > 0)
{
var hand = state.Hands.GetValueOrDefault(Name, new List<int>());
var nonWildTiles = hand.Where(t => !MahjongTile.IsWildcard(t)).ToList();
if (nonWildTiles.Count > 0)
return ("discard", nonWildTiles[_rng.Next(nonWildTiles.Count)]);
// Only wildcards left — discard one
if (hand.Count > 0)
return ("discard", hand[_rng.Next(hand.Count)]);
}
return ("pass", null);
}
}