Files
card_game/tests/Game.Core.Tests/StartingCombatTests.cs
T
chenmoranzhiandCursor ae13f1d06a 落地无头规则引擎与局内内容,便于命令行复现对战。
规则、卡包与测试放在同一套核心里,避免表现层自行结算。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-09 02:06:23 +08:00

154 lines
6.2 KiB
C#

using CardGame.Core;
using Xunit;
namespace Game.Core.Tests;
public sealed class StartingCombatTests
{
[Fact]
public void Starting_package_is_sixteen_cards()
{
var deck = StartingPackage.BuildSharedDeck(Cards.Catalog);
Assert.Equal(16, deck.Count);
}
[Fact]
public void Patrol_summons_militia_on_play()
{
var state = MatchSetup.Create(
1,
Cards.Repeat(Cards.Patrol, 10),
Cards.Repeat(Cards.Wisp, 10),
Cards.Catalog,
shuffle: false);
var patrol = state.Players[0].Hand[0];
state = RulesEngine.Execute(state, new PlayCardAction(0, patrol.Id));
Assert.Equal(2, state.Board.Count(e => e.ControllerIndex == 0 && e.Definition.Type == CardType.Creature));
Assert.Contains(state.Board, e => e.Definition.Id == "card.guard.militia");
}
[Fact]
public void Conscript_creates_1_cost_crossbow_that_keeps_cost_in_zones()
{
var state = MatchSetup.Create(
1,
new List<CardDefinition> { Cards.Conscript }.Concat(Cards.Repeat(Cards.Wisp, 15)).ToList(),
Cards.Repeat(Cards.Wisp, 10),
Cards.Catalog,
shuffle: false);
state.Players[0].Energy = 5;
var spell = state.Players[0].Hand.First(c => c.Definition.Id == Cards.Conscript.Id);
state = RulesEngine.Execute(state, new PlayCardAction(0, spell.Id));
var bow = state.Players[0].Hand.Single(c => c.Definition.Id == Cards.Crossbow.Id);
Assert.Equal(1, bow.Cost);
Assert.Equal(3, bow.Definition.Cost);
state = RulesEngine.Execute(state, new PlayCardAction(0, bow.Id));
var onBoard = state.Board.Single(e => e.Definition.Id == Cards.Crossbow.Id);
Assert.Equal(1, onBoard.Cost);
}
[Fact]
public void Captain_aura_buffs_current_1_cost_creatures()
{
var state = MatchSetup.Create(
1,
new List<CardDefinition> { Cards.Captain, Cards.Patrol }.Concat(Cards.Repeat(Cards.Wisp, 14)).ToList(),
Cards.Repeat(Cards.Wisp, 10),
Cards.Catalog,
shuffle: false);
state.Players[0].Energy = 10;
var captain = state.Players[0].Hand.First(c => c.Definition.Id == Cards.Captain.Id);
var patrol = state.Players[0].Hand.First(c => c.Definition.Id == Cards.Patrol.Id);
state = RulesEngine.Execute(state, new PlayCardAction(0, captain.Id));
state = RulesEngine.Execute(state, new PlayCardAction(0, patrol.Id));
var militia = state.Board.Single(e => e.Definition.Id == "card.guard.militia");
Assert.Equal(2, state.EffectiveAttack(militia));
Assert.Equal(2, state.EffectiveAttack(state.Find(patrol.Id)!));
}
[Fact]
public void Believer_value_chain_attacks_then_transfers()
{
var state = new GameState(3) { Catalog = Cards.Catalog };
MatchSetup.PlaceSystemField(state, 0, Cards.BaseField);
MatchSetup.PlaceSystemField(state, 1, Cards.BaseField);
var a = state.NewEntity(Cards.Believer, 0, Zone.Board, Factions.Cult);
var b = state.NewEntity(Cards.Believer, 0, Zone.Board, Factions.Cult);
a.EnteredOnTurn = 0;
b.EnteredOnTurn = 0;
a.AttacksRemaining = 1;
b.AttacksRemaining = 1;
a.ActivatesRemaining = 1;
b.ActivatesRemaining = 1;
state.Board.Add(a);
state.Board.Add(b);
var civilian = state.NewEntity(Cards.Get("card.cult.civilian"), 0, Zone.Board, Factions.Cult);
civilian.EnteredOnTurn = 0;
civilian.AttacksRemaining = 1;
state.Board.Add(civilian);
state.ActivePlayerIndex = 0;
state.TurnNumber = 2;
state = RulesEngine.Execute(state, new AttackAction(0, a.Id, new PlayerTarget(1)));
Assert.Equal(19, state.Players[1].Life);
state = RulesEngine.Execute(state, new ActivateAction(0, a.Id, new EntityTarget(b.Id)));
Assert.DoesNotContain(state.Board, e => e.Id == a.Id);
Assert.Equal(2, state.Find(b.Id)!.Attack);
Assert.Equal(4, state.Find(b.Id)!.Health);
state = RulesEngine.Execute(state, new AttackAction(0, b.Id, new PlayerTarget(1)));
Assert.Equal(17, state.Players[1].Life);
state = RulesEngine.Execute(state, new ActivateAction(0, b.Id, new EntityTarget(civilian.Id)));
var civ = state.Find(civilian.Id)!;
Assert.Equal(2, civ.Attack);
Assert.Equal(5, civ.Health);
Assert.Equal(1, civ.AttacksRemaining);
state = RulesEngine.Execute(state, new AttackAction(0, civilian.Id, new PlayerTarget(1)));
Assert.Equal(15, state.Players[1].Life);
}
[Fact]
public void Can_activate_on_enter_turn_but_not_attack()
{
var state = MatchSetup.Create(
1,
Cards.Repeat(Cards.Believer, 10),
Cards.Repeat(Cards.Believer, 10),
Cards.Catalog,
shuffle: false);
var first = state.Players[0].Hand[0];
state = RulesEngine.Execute(state, new PlayCardAction(0, first.Id));
var played = state.Find(first.Id)!;
Assert.Equal(0, played.AttacksRemaining);
Assert.Equal(1, played.ActivatesRemaining);
Assert.DoesNotContain(RulesEngine.GetLegalActions(state), a => a is AttackAction);
Assert.DoesNotContain(
RulesEngine.GetLegalActions(state),
a => a is ActivateAction act && act.SourceId == first.Id);
// only one believer on board — activate needs another friendly
var second = state.Players[0].Hand[0];
state.Players[0].Energy = 1;
state = RulesEngine.Execute(state, new PlayCardAction(0, second.Id));
Assert.Contains(
RulesEngine.GetLegalActions(state),
a => a is ActivateAction act && act.SourceId == first.Id);
}
[Fact]
public void Starting_battle_boots()
{
var state = MatchSetup.CreateStartingBattle(20261006, Cards.Catalog, shuffle: false);
Assert.Equal(16 - GameState.OpeningHand - 1, state.Players[0].Deck.Count);
Assert.Equal(GameState.OpeningHand + 1, state.Players[0].Hand.Count);
Assert.Equal(1, state.Players[0].Energy);
Assert.Contains(state.Board, e => e.IsSystem);
Assert.NotEmpty(RulesEngine.GetLegalActions(state));
}
}