675 lines
22 KiB
C#
675 lines
22 KiB
C#
using Vanguard.Rules.Cards;
|
||
using Vanguard.Rules.Engine;
|
||
using Xunit;
|
||
|
||
namespace Vanguard.Rules.Tests;
|
||
|
||
public class GameEngineTests
|
||
{
|
||
private readonly CardCatalog _catalog = TestCatalog.Instance;
|
||
|
||
private const string Barcgal = "BT01/003EN"; // grade 0, без триггера — стартовый Авангард
|
||
private const string Marron = "BT01/042EN"; // grade 1, 8000
|
||
private const string Gallatin = "BT01/021EN"; // grade 2, 10000
|
||
private const string Alfred = "BT01/001EN"; // grade 3
|
||
private const string Nike = "BT01/056EN"; // Critical
|
||
private const string Flogal = "BT01/046EN"; // Stand
|
||
private const string Govannon = "BT01/045EN"; // Draw
|
||
private const string Iseult = "BT01/011EN"; // Sentinel, Royal Paladin
|
||
private const string Nehalem = "BT01/022EN"; // Kagero
|
||
private const string Overlord = "BT01/004EN"; // Kagero, 11000
|
||
|
||
[Fact]
|
||
public void Start_Places_Starting_Vanguard_And_Draws_Five()
|
||
{
|
||
var engine = NewEngine();
|
||
engine.Start();
|
||
|
||
var player = engine.State.Players[0];
|
||
Assert.NotNull(player.Vanguard);
|
||
Assert.Equal(0, _catalog.CardOf(player.Vanguard!.PrintingId).Grade);
|
||
Assert.Equal(TriggerType.None, _catalog.CardOf(player.Vanguard.PrintingId).Trigger);
|
||
Assert.Equal(GameEngine.StartingHand, player.Hand.Count);
|
||
Assert.Equal(44, player.Deck.Count);
|
||
Assert.Equal(Phase.Mulligan, engine.State.Phase);
|
||
}
|
||
|
||
[Fact]
|
||
public void Ride_Rejects_Grade_Jump()
|
||
{
|
||
var engine = StartedEngine();
|
||
Give(engine, 0, Alfred);
|
||
|
||
var result = engine.Execute(new RideCommand(Alfred));
|
||
|
||
Assert.False(result.Accepted);
|
||
Assert.Equal(Barcgal, engine.State.Players[0].Vanguard!.PrintingId);
|
||
}
|
||
|
||
[Fact]
|
||
public void Ride_Moves_Previous_Vanguard_To_Soul()
|
||
{
|
||
var engine = StartedEngine();
|
||
Give(engine, 0, Marron);
|
||
|
||
var result = engine.Execute(new RideCommand(Marron));
|
||
|
||
Assert.True(result.Accepted);
|
||
Assert.Equal(Marron, engine.State.Players[0].Vanguard!.PrintingId);
|
||
Assert.Contains(engine.State.Players[0].Soul, c => c.PrintingId == Barcgal);
|
||
Assert.Equal(Phase.Main, engine.State.Phase);
|
||
}
|
||
|
||
[Fact]
|
||
public void Called_RearGuard_Enters_Rested_And_Slot_Is_Busy()
|
||
{
|
||
var engine = InMainPhase();
|
||
Give(engine, 0, Marron);
|
||
|
||
Assert.True(engine.Execute(new CallCommand(Marron, 0)).Accepted);
|
||
Assert.True(engine.State.Players[0].RearGuards[0]!.Rested);
|
||
|
||
Give(engine, 0, Marron);
|
||
var second = engine.Execute(new CallCommand(Marron, 0));
|
||
|
||
Assert.False(second.Accepted);
|
||
}
|
||
|
||
[Fact]
|
||
public void Attack_Without_Guard_Deals_Damage()
|
||
{
|
||
var engine = ReadyToAttack();
|
||
StackDeck(engine.State.Players[0], Gallatin, Gallatin);
|
||
|
||
Assert.True(engine.Execute(new AttackCommand(-1, -1)).Accepted);
|
||
Assert.True(engine.Execute(new PassGuardCommand()).Accepted);
|
||
|
||
Assert.Single(engine.State.Players[1].Damage);
|
||
Assert.Equal(Step.None, engine.State.Step);
|
||
}
|
||
|
||
[Fact]
|
||
public void Guard_Prevents_Damage()
|
||
{
|
||
var engine = ReadyToAttack();
|
||
StackDeck(engine.State.Players[0], Gallatin, Gallatin);
|
||
Give(engine, 1, Barcgal);
|
||
|
||
Assert.True(engine.Execute(new AttackCommand(-1, -1)).Accepted);
|
||
Assert.True(engine.Execute(new GuardCommand(Barcgal)).Accepted);
|
||
Assert.True(engine.Execute(new PassGuardCommand()).Accepted);
|
||
|
||
Assert.Empty(engine.State.Players[1].Damage);
|
||
Assert.Single(engine.State.Players[1].Drop);
|
||
}
|
||
|
||
[Fact]
|
||
public void Sixth_Damage_Ends_The_Game()
|
||
{
|
||
var engine = ReadyToAttack();
|
||
StackDeck(engine.State.Players[0], Gallatin, Gallatin);
|
||
|
||
var defender = engine.State.Players[1];
|
||
for (var i = 0; i < GameEngine.MaxDamage - 1; i++)
|
||
defender.Damage.Add(TakeAnyFromDeck(defender));
|
||
|
||
Assert.True(engine.Execute(new AttackCommand(-1, -1)).Accepted);
|
||
Assert.True(engine.Execute(new PassGuardCommand()).Accepted);
|
||
|
||
Assert.True(engine.State.IsFinished);
|
||
Assert.Equal(0, engine.State.Winner);
|
||
Assert.Contains(engine.Events, e => e is GameFinishedEvent);
|
||
}
|
||
|
||
[Fact]
|
||
public void Critical_Trigger_Grants_Power_And_Crit_To_Chosen_Unit()
|
||
{
|
||
var engine = ReadyToAttack();
|
||
StackDeck(engine.State.Players[0], Nike, Gallatin);
|
||
|
||
Assert.True(engine.Execute(new AttackCommand(-1, -1)).Accepted);
|
||
Assert.True(engine.Execute(new PassGuardCommand()).Accepted);
|
||
|
||
Assert.NotNull(engine.State.PendingTrigger);
|
||
Assert.Equal(Step.Trigger, engine.State.Step);
|
||
|
||
Assert.True(engine.Execute(new ResolveTriggerCommand(-1, -1)).Accepted);
|
||
|
||
Assert.Null(engine.State.PendingTrigger);
|
||
Assert.Equal(2, engine.State.Players[1].Damage.Count);
|
||
}
|
||
|
||
[Fact]
|
||
public void Stand_Trigger_Stands_Chosen_RearGuard()
|
||
{
|
||
var engine = ReadyToAttackWithRearGuard();
|
||
StackDeck(engine.State.Players[0], Flogal, Gallatin);
|
||
|
||
Assert.True(engine.Execute(new AttackCommand(-1, -1)).Accepted);
|
||
Assert.True(engine.Execute(new PassGuardCommand()).Accepted);
|
||
Assert.NotNull(engine.State.PendingTrigger);
|
||
|
||
var rearGuard = engine.State.Players[0].RearGuards[0]!;
|
||
Assert.True(rearGuard.Rested);
|
||
|
||
Assert.True(engine.Execute(new ResolveTriggerCommand(-1, 0)).Accepted);
|
||
Assert.False(rearGuard.Rested);
|
||
}
|
||
|
||
[Fact]
|
||
public void Damage_Trigger_Also_Asks_For_Targets_And_Applies_Effect()
|
||
{
|
||
var engine = ReadyToAttack();
|
||
StackDeck(engine.State.Players[0], Gallatin, Gallatin);
|
||
StackDeck(engine.State.Players[1], Nike);
|
||
|
||
Assert.True(engine.Execute(new AttackCommand(-1, -1)).Accepted);
|
||
Assert.True(engine.Execute(new PassGuardCommand()).Accepted);
|
||
|
||
var pending = engine.State.PendingTrigger;
|
||
Assert.NotNull(pending);
|
||
Assert.Equal(TriggerContext.DamageCheck, pending!.Context);
|
||
|
||
// до разрешения карта лежит в зоне триггера, а не в уроне
|
||
Assert.Empty(engine.State.Players[1].Damage);
|
||
Assert.Contains(engine.State.Players[1].Trigger, c => c.PrintingId == Nike);
|
||
|
||
Assert.True(engine.Execute(new ResolveTriggerCommand(-1, -1)).Accepted);
|
||
|
||
Assert.Null(engine.State.PendingTrigger);
|
||
Assert.Null(engine.State.Battle);
|
||
Assert.Single(engine.State.Players[1].Damage);
|
||
Assert.Empty(engine.State.Players[1].Trigger);
|
||
Assert.Contains(engine.State.Players[1].Damage, c => c.PrintingId == Nike);
|
||
Assert.Contains(engine.Events, e => e is TriggerAppliedEvent { Player: 1 });
|
||
}
|
||
|
||
[Fact]
|
||
public void Trigger_Power_Lasts_Until_End_Of_Turn()
|
||
{
|
||
var engine = ReadyToAttack();
|
||
StackDeck(engine.State.Players[0], Nike, Gallatin);
|
||
|
||
engine.Execute(new AttackCommand(-1, -1));
|
||
engine.Execute(new PassGuardCommand());
|
||
engine.Execute(new ResolveTriggerCommand(-1, -1));
|
||
|
||
var vanguard = engine.State.Players[0].Vanguard!;
|
||
Assert.Equal(GameEngine.TriggerPowerBonus, vanguard.TemporaryPower);
|
||
|
||
Assert.True(engine.Execute(new EndTurnCommand()).Accepted);
|
||
Assert.Equal(0, vanguard.TemporaryPower);
|
||
}
|
||
|
||
[Fact]
|
||
public void Boost_Adds_Back_Row_Power_To_Attacker()
|
||
{
|
||
var engine = ReadyToBoost();
|
||
|
||
Assert.True(engine.Execute(new AttackCommand(3, -1, 0)).Accepted);
|
||
Assert.True(engine.State.Players[0].RearGuards[0]!.Rested);
|
||
Assert.True(engine.Execute(new PassGuardCommand()).Accepted);
|
||
|
||
Assert.Single(engine.State.Players[1].Damage);
|
||
Assert.Contains(engine.Events, e => e is BoostedEvent { BoosterSlot: 0, AttackerSlot: 3 });
|
||
}
|
||
|
||
[Fact]
|
||
public void Without_Boost_The_Same_Attack_Misses()
|
||
{
|
||
var engine = ReadyToBoost();
|
||
|
||
Assert.True(engine.Execute(new AttackCommand(3, -1)).Accepted);
|
||
Assert.True(engine.Execute(new PassGuardCommand()).Accepted);
|
||
|
||
Assert.Empty(engine.State.Players[1].Damage);
|
||
Assert.False(engine.State.Players[0].RearGuards[0]!.Rested);
|
||
}
|
||
|
||
[Fact]
|
||
public void Boost_Rejects_Wrong_Column()
|
||
{
|
||
var engine = ReadyToBoost();
|
||
|
||
var result = engine.Execute(new AttackCommand(3, -1, 2));
|
||
|
||
Assert.False(result.Accepted);
|
||
}
|
||
|
||
[Fact]
|
||
public void Grade_Two_Cannot_Boost()
|
||
{
|
||
var engine = ReadyToBoost();
|
||
|
||
engine.State.Players[0].RearGuards[0] = TakeFromDeck(engine.State.Players[0], Gallatin);
|
||
|
||
var result = engine.Execute(new AttackCommand(3, -1, 0));
|
||
|
||
Assert.False(result.Accepted);
|
||
}
|
||
|
||
[Fact]
|
||
public void Effect_Can_Grant_Boost_To_Grade_Two()
|
||
{
|
||
var engine = ReadyToBoost();
|
||
var booster = TakeFromDeck(engine.State.Players[0], Gallatin);
|
||
engine.State.Players[0].RearGuards[0] = booster;
|
||
|
||
Assert.False(engine.Execute(new AttackCommand(3, -1, 0)).Accepted);
|
||
|
||
booster.BoostOverride = true;
|
||
|
||
Assert.True(engine.Execute(new AttackCommand(3, -1, 0)).Accepted);
|
||
}
|
||
|
||
[Fact]
|
||
public void Effect_Can_Remove_Boost_From_Grade_One()
|
||
{
|
||
var engine = ReadyToBoost();
|
||
var booster = engine.State.Players[0].RearGuards[0]!;
|
||
|
||
booster.BoostOverride = false;
|
||
|
||
Assert.False(engine.Execute(new AttackCommand(3, -1, 0)).Accepted);
|
||
}
|
||
|
||
[Fact]
|
||
public void Perfect_Guard_Nullifies_Attack_When_Cost_Paid()
|
||
{
|
||
var engine = ReadyToAttack();
|
||
StackDeck(engine.State.Players[0], Gallatin, Gallatin);
|
||
Give(engine, 1, Iseult);
|
||
Give(engine, 1, Marron);
|
||
|
||
Assert.True(engine.Execute(new AttackCommand(-1, -1)).Accepted);
|
||
Assert.True(engine.Execute(new GuardCommand(Iseult, Marron)).Accepted);
|
||
Assert.True(engine.Execute(new PassGuardCommand()).Accepted);
|
||
|
||
Assert.Empty(engine.State.Players[1].Damage);
|
||
Assert.Contains(engine.State.Players[1].Drop, c => c.PrintingId == Marron);
|
||
Assert.Contains(engine.Events, e => e is PerfectGuardEvent);
|
||
}
|
||
|
||
[Fact]
|
||
public void Perfect_Guard_Without_Cost_Adds_No_Shield()
|
||
{
|
||
var engine = ReadyToAttack();
|
||
StackDeck(engine.State.Players[0], Gallatin, Gallatin);
|
||
Give(engine, 1, Iseult);
|
||
|
||
Assert.True(engine.Execute(new AttackCommand(-1, -1)).Accepted);
|
||
Assert.True(engine.Execute(new GuardCommand(Iseult)).Accepted);
|
||
Assert.True(engine.Execute(new PassGuardCommand()).Accepted);
|
||
|
||
Assert.Single(engine.State.Players[1].Damage);
|
||
Assert.DoesNotContain(engine.Events, e => e is PerfectGuardEvent);
|
||
}
|
||
|
||
[Fact]
|
||
public void Perfect_Guard_Rejects_Wrong_Clan_Cost()
|
||
{
|
||
var engine = ReadyToAttack();
|
||
Give(engine, 1, Iseult);
|
||
Give(engine, 1, Nehalem);
|
||
|
||
Assert.True(engine.Execute(new AttackCommand(-1, -1)).Accepted);
|
||
Assert.False(engine.Execute(new GuardCommand(Iseult, Nehalem)).Accepted);
|
||
}
|
||
|
||
[Fact]
|
||
public void Intercept_Saves_Target_And_Retires_Interceptor()
|
||
{
|
||
var engine = ReadyToAttackRearGuard();
|
||
|
||
Assert.True(engine.Execute(new AttackCommand(3, 0)).Accepted);
|
||
Assert.True(engine.Execute(new InterceptCommand(3)).Accepted);
|
||
Assert.True(engine.Execute(new PassGuardCommand()).Accepted);
|
||
|
||
var defender = engine.State.Players[1];
|
||
Assert.NotNull(defender.RearGuards[0]); // цель выжила
|
||
Assert.Null(defender.RearGuards[3]); // перехватчик покинул круг
|
||
Assert.Contains(defender.Drop, c => c.PrintingId == Gallatin);
|
||
Assert.Contains(engine.Events, e => e is InterceptedEvent);
|
||
Assert.Contains(engine.Events, e => e is BattleResolvedEvent { Hit: false });
|
||
}
|
||
|
||
[Fact]
|
||
public void Without_Intercept_RearGuard_Is_Retired()
|
||
{
|
||
var engine = ReadyToAttackRearGuard();
|
||
|
||
Assert.True(engine.Execute(new AttackCommand(3, 0)).Accepted);
|
||
Assert.True(engine.Execute(new PassGuardCommand()).Accepted);
|
||
|
||
var defender = engine.State.Players[1];
|
||
Assert.Null(defender.RearGuards[0]);
|
||
Assert.Contains(defender.Drop, c => c.PrintingId == Marron);
|
||
Assert.Contains(engine.Events, e => e is UnitRetiredEvent);
|
||
}
|
||
|
||
[Fact]
|
||
public void Grade_One_Cannot_Intercept()
|
||
{
|
||
var engine = ReadyToAttackRearGuard();
|
||
var defender = engine.State.Players[1];
|
||
defender.RearGuards[3] = TakeFromDeck(defender, Marron);
|
||
|
||
Assert.True(engine.Execute(new AttackCommand(3, 0)).Accepted);
|
||
Assert.False(engine.Execute(new InterceptCommand(3)).Accepted);
|
||
}
|
||
|
||
[Fact]
|
||
public void Back_Row_Cannot_Intercept_By_Default()
|
||
{
|
||
var engine = ReadyToAttackRearGuard();
|
||
var defender = engine.State.Players[1];
|
||
defender.RearGuards[1] = TakeFromDeck(defender, Gallatin);
|
||
|
||
Assert.True(engine.Execute(new AttackCommand(3, 0)).Accepted);
|
||
Assert.False(engine.Execute(new InterceptCommand(1)).Accepted);
|
||
}
|
||
|
||
[Fact]
|
||
public void Effect_Can_Grant_Back_Row_Intercept()
|
||
{
|
||
var engine = ReadyToAttackRearGuard();
|
||
var defender = engine.State.Players[1];
|
||
var unit = TakeFromDeck(defender, Gallatin);
|
||
unit.InterceptOverride = true;
|
||
unit.InterceptFromBackRow = true;
|
||
defender.RearGuards[1] = unit;
|
||
|
||
Assert.True(engine.Execute(new AttackCommand(3, 0)).Accepted);
|
||
Assert.True(engine.Execute(new InterceptCommand(1)).Accepted);
|
||
}
|
||
|
||
[Fact]
|
||
public void Intercept_Works_Against_Vanguard_Attack()
|
||
{
|
||
var engine = StartedEngine();
|
||
engine.Execute(new SkipRideCommand());
|
||
SetVanguard(engine, 0, Overlord);
|
||
StackDeck(engine.State.Players[0], Gallatin, Gallatin);
|
||
engine.Execute(new EnterBattleCommand());
|
||
|
||
var defender = engine.State.Players[1];
|
||
defender.RearGuards[3] = TakeFromDeck(defender, Gallatin);
|
||
|
||
Assert.True(engine.Execute(new AttackCommand(-1, -1)).Accepted);
|
||
Assert.True(engine.Execute(new InterceptCommand(3)).Accepted);
|
||
Assert.True(engine.Execute(new PassGuardCommand()).Accepted);
|
||
|
||
Assert.Empty(defender.Damage);
|
||
Assert.NotNull(defender.Vanguard);
|
||
Assert.Contains(defender.Drop, c => c.PrintingId == Gallatin);
|
||
Assert.Contains(engine.Events, e => e is InterceptorRetiredEvent);
|
||
}
|
||
|
||
[Fact]
|
||
public void Attacked_RearGuard_Cannot_Intercept_Itself()
|
||
{
|
||
var engine = ReadyToAttackRearGuard();
|
||
|
||
Assert.True(engine.Execute(new AttackCommand(3, 0)).Accepted);
|
||
Assert.False(engine.Execute(new InterceptCommand(0)).Accepted);
|
||
}
|
||
|
||
[Fact]
|
||
public void Move_To_Empty_Circle_In_Same_Column()
|
||
{
|
||
var engine = InMainPhase();
|
||
Give(engine, 0, Marron);
|
||
engine.Execute(new CallCommand(Marron, 0));
|
||
|
||
Assert.True(engine.Execute(new MoveCommand(0, 3)).Accepted);
|
||
|
||
Assert.Null(engine.State.Players[0].RearGuards[0]);
|
||
Assert.NotNull(engine.State.Players[0].RearGuards[3]);
|
||
Assert.Contains(engine.Events, e => e is UnitMovedEvent { FromSlot: 0, ToSlot: 3 });
|
||
}
|
||
|
||
[Fact]
|
||
public void Move_Swaps_Occupied_Circles()
|
||
{
|
||
var engine = InMainPhase();
|
||
Give(engine, 0, Marron);
|
||
engine.Execute(new CallCommand(Marron, 0));
|
||
Give(engine, 0, Gallatin);
|
||
engine.Execute(new CallCommand(Gallatin, 3));
|
||
|
||
Assert.True(engine.Execute(new MoveCommand(0, 3)).Accepted);
|
||
|
||
Assert.Equal(Marron, engine.State.Players[0].RearGuards[3]!.PrintingId);
|
||
Assert.Equal(Gallatin, engine.State.Players[0].RearGuards[0]!.PrintingId);
|
||
}
|
||
|
||
[Fact]
|
||
public void Move_Rejects_Other_Column()
|
||
{
|
||
var engine = InMainPhase();
|
||
Give(engine, 0, Marron);
|
||
engine.Execute(new CallCommand(Marron, 0));
|
||
|
||
Assert.False(engine.Execute(new MoveCommand(0, 4)).Accepted);
|
||
}
|
||
|
||
[Fact]
|
||
public void Center_Back_Row_Has_No_Pair()
|
||
{
|
||
var engine = InMainPhase();
|
||
Give(engine, 0, Marron);
|
||
engine.Execute(new CallCommand(Marron, 1));
|
||
|
||
Assert.False(engine.Execute(new MoveCommand(1, 3)).Accepted);
|
||
Assert.False(engine.Execute(new MoveCommand(1, 0)).Accepted);
|
||
}
|
||
|
||
[Fact]
|
||
public void Move_Is_Available_Only_In_Main_Phase()
|
||
{
|
||
var engine = StartedEngine(); // фаза Смены
|
||
|
||
Assert.False(engine.Execute(new MoveCommand(0, 3)).Accepted);
|
||
}
|
||
|
||
[Fact]
|
||
public void ResolveTrigger_Rejects_When_Nothing_Is_Pending()
|
||
{
|
||
var engine = StartedEngine();
|
||
|
||
Assert.False(engine.Execute(new ResolveTriggerCommand(-1, -1)).Accepted);
|
||
}
|
||
|
||
[Fact]
|
||
public void Same_Seed_And_Commands_Produce_Same_Events()
|
||
{
|
||
var first = RunScriptedMatch(seed: 12345);
|
||
var second = RunScriptedMatch(seed: 12345);
|
||
|
||
Assert.Equal(first, second);
|
||
}
|
||
|
||
private List<GameEvent> RunScriptedMatch(ulong seed)
|
||
{
|
||
var engine = NewEngine(seed);
|
||
engine.Start();
|
||
engine.Execute(new FinishMulliganCommand());
|
||
Give(engine, 0, Marron);
|
||
engine.Execute(new RideCommand(Marron));
|
||
engine.Execute(new AttackCommand(-1, -1));
|
||
engine.Execute(new PassGuardCommand());
|
||
return engine.Events.ToList();
|
||
}
|
||
|
||
// ---------- helpers ----------
|
||
|
||
private GameEngine NewEngine(ulong seed = 7) =>
|
||
new(_catalog, seed, BuildDeck(), BuildDeck());
|
||
|
||
private GameEngine StartedEngine()
|
||
{
|
||
var engine = NewEngine();
|
||
engine.Start();
|
||
engine.Execute(new FinishMulliganCommand());
|
||
return engine;
|
||
}
|
||
|
||
private GameEngine InMainPhase()
|
||
{
|
||
var engine = StartedEngine();
|
||
engine.Execute(new SkipRideCommand());
|
||
return engine;
|
||
}
|
||
|
||
private GameEngine ReadyToAttack()
|
||
{
|
||
var engine = StartedEngine();
|
||
Give(engine, 0, Marron);
|
||
engine.Execute(new RideCommand(Marron));
|
||
engine.Execute(new EnterBattleCommand());
|
||
return engine;
|
||
}
|
||
|
||
private GameEngine ReadyToAttackWithRearGuard()
|
||
{
|
||
var engine = StartedEngine();
|
||
Give(engine, 0, Marron);
|
||
engine.Execute(new RideCommand(Marron));
|
||
Give(engine, 0, Gallatin);
|
||
engine.Execute(new CallCommand(Gallatin, 0));
|
||
engine.Execute(new EnterBattleCommand());
|
||
return engine;
|
||
}
|
||
|
||
private GameEngine ReadyToBoost()
|
||
{
|
||
var engine = StartedEngine();
|
||
Give(engine, 0, Marron);
|
||
engine.Execute(new RideCommand(Marron));
|
||
|
||
// сзади слева — поддержка (1 ранг), впереди слева — атакующий (2 ранг)
|
||
Give(engine, 0, Marron);
|
||
engine.Execute(new CallCommand(Marron, 0));
|
||
|
||
Give(engine, 0, Gallatin);
|
||
engine.Execute(new CallCommand(Gallatin, 3));
|
||
|
||
// призванные карты входят повёрнутыми — для теста поддержки поднимаем обе
|
||
engine.State.Players[0].RearGuards[0]!.Rested = false;
|
||
engine.State.Players[0].RearGuards[3]!.Rested = false;
|
||
|
||
SetVanguard(engine, 1, Gallatin);
|
||
engine.Execute(new EnterBattleCommand());
|
||
return engine;
|
||
}
|
||
|
||
private GameEngine ReadyToAttackRearGuard()
|
||
{
|
||
var engine = StartedEngine();
|
||
Give(engine, 0, Marron);
|
||
engine.Execute(new RideCommand(Marron));
|
||
|
||
Give(engine, 0, Gallatin);
|
||
engine.Execute(new CallCommand(Gallatin, 3));
|
||
engine.State.Players[0].RearGuards[3]!.Rested = false;
|
||
|
||
var defender = engine.State.Players[1];
|
||
defender.RearGuards[0] = TakeFromDeck(defender, Marron);
|
||
defender.RearGuards[3] = TakeFromDeck(defender, Gallatin);
|
||
|
||
engine.Execute(new EnterBattleCommand());
|
||
return engine;
|
||
}
|
||
|
||
private List<string> BuildDeck()
|
||
{
|
||
var mustInclude = new[] { Barcgal, Marron, Gallatin, Alfred, Nike, Flogal, Govannon, Iseult, Nehalem, Overlord };
|
||
var names = mustInclude
|
||
.Select(_catalog.CardOf)
|
||
.Select(c => c.NameId)
|
||
.ToHashSet(StringComparer.Ordinal);
|
||
|
||
var deck = new List<string>();
|
||
foreach (var printing in mustInclude)
|
||
deck.AddRange(Enumerable.Repeat(printing, 4));
|
||
|
||
foreach (var group in _catalog.Printings
|
||
.Where(p => !p.IsParallel)
|
||
.GroupBy(p => p.NameId)
|
||
.OrderBy(g => g.Key, StringComparer.Ordinal))
|
||
{
|
||
if (deck.Count >= 50)
|
||
break;
|
||
|
||
if (names.Contains(group.Key))
|
||
continue;
|
||
|
||
var card = _catalog.Card(group.Key);
|
||
if (card.IsSentinel || card.Trigger == TriggerType.Heal)
|
||
continue;
|
||
|
||
var printingId = group.First().PrintingId;
|
||
for (var i = 0; i < 4 && deck.Count < 50; i++)
|
||
deck.Add(printingId);
|
||
}
|
||
|
||
return deck;
|
||
}
|
||
|
||
private static void Give(GameEngine engine, int player, string printingId)
|
||
{
|
||
var p = engine.State.Players[player];
|
||
var index = p.Deck.FindIndex(c => c.PrintingId == printingId);
|
||
Assert.True(index >= 0, $"В колоде нет {printingId}");
|
||
|
||
var card = p.Deck[index];
|
||
p.Deck.RemoveAt(index);
|
||
p.Hand.Add(card);
|
||
}
|
||
|
||
private static void StackDeck(PlayerState player, params string[] printingIds)
|
||
{
|
||
var cards = new List<CardInstance>();
|
||
foreach (var printingId in printingIds)
|
||
{
|
||
var index = player.Deck.FindIndex(c => c.PrintingId == printingId);
|
||
Assert.True(index >= 0, $"В колоде нет {printingId}");
|
||
|
||
cards.Add(player.Deck[index]);
|
||
player.Deck.RemoveAt(index);
|
||
}
|
||
|
||
for (var i = cards.Count - 1; i >= 0; i--)
|
||
player.Deck.Insert(0, cards[i]);
|
||
}
|
||
|
||
private static void SetVanguard(GameEngine engine, int player, string printingId)
|
||
{
|
||
var p = engine.State.Players[player];
|
||
var index = p.Deck.FindIndex(c => c.PrintingId == printingId);
|
||
Assert.True(index >= 0, $"В колоде нет {printingId}");
|
||
|
||
var card = p.Deck[index];
|
||
p.Deck.RemoveAt(index);
|
||
p.Vanguard = card;
|
||
}
|
||
|
||
private static CardInstance TakeFromDeck(PlayerState player, string printingId)
|
||
{
|
||
var index = player.Deck.FindIndex(c => c.PrintingId == printingId);
|
||
Assert.True(index >= 0, $"В колоде нет {printingId}");
|
||
|
||
var card = player.Deck[index];
|
||
player.Deck.RemoveAt(index);
|
||
return card;
|
||
}
|
||
|
||
private static CardInstance TakeAnyFromDeck(PlayerState player)
|
||
{
|
||
Assert.NotEmpty(player.Deck);
|
||
var card = player.Deck[0];
|
||
player.Deck.RemoveAt(0);
|
||
return card;
|
||
}
|
||
}
|