规则、卡包与测试放在同一套核心里,避免表现层自行结算。 Co-authored-by: Cursor <cursoragent@cursor.com>
154 lines
6.2 KiB
C#
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));
|
|
}
|
|
}
|