规则、卡包与测试放在同一套核心里,避免表现层自行结算。 Co-authored-by: Cursor <cursoragent@cursor.com>
50 lines
1.8 KiB
C#
50 lines
1.8 KiB
C#
using CardGame.Core;
|
|
using Xunit;
|
|
|
|
namespace Game.Core.Tests;
|
|
|
|
public sealed class AiSearchTests
|
|
{
|
|
[Fact]
|
|
public void Prefers_face_damage_when_lethal_or_high_value()
|
|
{
|
|
var state = new GameState(9) { Catalog = Cards.Catalog };
|
|
MatchSetup.PlaceSystemField(state, 0, Cards.BaseField);
|
|
MatchSetup.PlaceSystemField(state, 1, Cards.BaseField);
|
|
state.Players[1].Life = 1;
|
|
var attacker = state.NewEntity(Cards.Wisp, 0, Zone.Board, Factions.Guard);
|
|
attacker.EnteredOnTurn = 0;
|
|
attacker.AttacksRemaining = 1;
|
|
state.Board.Add(attacker);
|
|
state.ActivePlayerIndex = 0;
|
|
state.TurnNumber = 2;
|
|
|
|
var legal = RulesEngine.GetLegalActions(state);
|
|
var (action, top) = TurnBeamSearch.ChooseWithTrace(state, legal);
|
|
Assert.IsType<AttackAction>(action);
|
|
Assert.Contains(top, t => t.Chosen && t.Label.Contains("敌方主战者", StringComparison.Ordinal));
|
|
}
|
|
|
|
[Fact]
|
|
public void PlayUntil_returns_step_trace()
|
|
{
|
|
var state = MatchSetup.CreateStartingBattle(11, Cards.Catalog, shuffle: false);
|
|
state = RulesEngine.Execute(state, new EndTurnAction(0));
|
|
var (_, trace) = TurnBeamSearch.PlayUntil(state, untilPlayerIndex: 0);
|
|
Assert.NotEmpty(trace.Steps);
|
|
Assert.All(trace.Steps, s => Assert.NotEmpty(s.TopCandidates));
|
|
}
|
|
|
|
[Fact]
|
|
public void ChooseWithTrace_works_when_all_scores_are_negative()
|
|
{
|
|
var state = MatchSetup.CreateStartingBattle(13, Cards.Catalog, shuffle: false);
|
|
state.Players[0].Life = 1;
|
|
state.Players[1].Life = 20;
|
|
var legal = RulesEngine.GetLegalActions(state);
|
|
var (action, top) = TurnBeamSearch.ChooseWithTrace(state, legal);
|
|
Assert.NotNull(action);
|
|
Assert.Contains(top, t => t.Chosen);
|
|
}
|
|
}
|