Root cause: static HashSet跨测试/跨变体污染+硬编码名→编码映射 Changes: - WildcardRegistry: 实例级wildcard管理(IsWildcard/CountWildcards/Clone) - GameState: 新增Wildcards属性(含Clone) - MahjongTile: 移除static_wildcards,IsWildcard仅查编码50-59 +ToString(tile,wildcards)重载用于游戏上下文显示 - MeldsSolver: _wildcards字段→构造函数注入(WildcardRegistry?) - PhaseMachine: _wildcards字段+构造注入 - Deal(): State.Wildcards.Clear()+AddRange替代static ConfigureWildcards +NameToEncoding查表替代switch硬编码 - RandomMahjongAI/HumanMahjongPlayer: state.Wildcards替代static调用 - HumanMahjongPlayer.CheckTing: 补wildcardCount参数(之前遗漏) - ExecuteWinClaim: IsFinished=true→Settle()(之前Phase漏设) Test: CoreTests清理ConfigureWildcards()→WildcardRegistry测试 Impact: 零static state→不同变体/测试完全隔离,未来支持随机宝牌
54 lines
1.8 KiB
C#
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 => !state.Wildcards.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);
|
|
}
|
|
}
|