feat: 集成测试 + 手动出牌模式 + discart pool幽灵牌修复

集成测试 (10个):
- 发牌: 庄14闲13, 牌墙55
- 回合流转: 庄家第一轮不摸牌, 非庄家摸牌后出牌
- 数据完整性: 10回合后手牌+副露+牌墙+弃牌=108
- 对局完整性: 4种DSL全部不出错完成
- 构造听牌: 完整手牌第一轮自摸

手动出牌 (--human):
- HumanMahjongPlayer: 显示手牌/可选操作/h=胡/p=碰/k=杠/1-N=出牌
- MahjongRoom: humanPlayer参数

修复: DiscardPool碰/胡后不移除→幽灵牌→总牌数溢出
This commit is contained in:
xiaoou
2026-07-04 11:45:54 +08:00
parent bb43ed7c89
commit 4302ec2fd5
4 changed files with 285 additions and 4 deletions

View File

@ -13,17 +13,21 @@ public class MahjongRoom
public bool IsFinished { get; private set; }
private readonly MahjongDslRoot _rules;
private readonly List<RandomMahjongAI> _ais;
private readonly HumanMahjongPlayer? _human;
private readonly MeldsSolver _solver;
private readonly MahjongPhaseMachine _phaseMachine;
private readonly MahjongScoreEngine _scoreEngine;
private readonly bool _autoMode;
private readonly Random _rng = Random.Shared;
public MahjongRoom(MahjongDslRoot rules, string[] playerNames, bool autoMode = false)
public MahjongRoom(MahjongDslRoot rules, string[] playerNames, bool autoMode = false,
string? humanPlayer = null)
{
_rules = rules;
_autoMode = autoMode;
_ais = playerNames.Select((n, i) => new RandomMahjongAI(n, new Random(i * 7919))).ToList();
if (humanPlayer != null)
_human = new HumanMahjongPlayer(humanPlayer);
var fanConfig = BuildFanConfig();
_solver = new MeldsSolver(fanConfig);
@ -179,7 +183,9 @@ public class MahjongRoom
// Phase 3: Player decides what to do with their drawn hand
bool requireWinFan = _rules.WinMinFan > 0;
var legalActions = _phaseMachine.GetLegalActions(State, player, requireWinFan);
var (action, tile) = _ais.First(a => a.Name == player).Decide(legalActions, State);
var (action, tile) = _human != null && player == _human.Name
? _human.Decide(legalActions, State)
: _ais.First(a => a.Name == player).Decide(legalActions, State);
if (action == "win")
{
@ -285,6 +291,9 @@ public class MahjongRoom
if (best.action == "win")
{
// Remove the winning tile from pool (it's now part of the winning hand)
State.DiscardPool.Remove(discardTile);
var handWithTile = new List<int>(State.Hands[bp]) { discardTile };
var (wc2, r2582) = GetWinParams();
int wildInHand2 = CountWildcardsInHand(handWithTile);
@ -305,6 +314,9 @@ public class MahjongRoom
if (best.action == "pung" || best.action == "ming_kong")
{
// Remove the discarded tile from pool (it's now in the meld)
State.DiscardPool.Remove(discardTile);
// Remove tiles from hand (pung: 2, ming_kong: 3)
// The discard tile is taken from pool, not from hand
int toRemove = best.action == "ming_kong" ? 3 : 2;