Chessistics/chessistics-tests/Simulation/CampaignTests.cs

309 lines
12 KiB
C#
Raw Permalink Normal View History

using Chessistics.Engine.Commands;
using Chessistics.Engine.Events;
using Chessistics.Engine.Model;
using Chessistics.Tests.Helpers;
using Xunit;
namespace Chessistics.Tests.Simulation;
public class CampaignTests
{
private static CampaignDef CreateTwOMissionCampaign()
{
return new CampaignDef
{
Name = "Test Campaign",
InitialWidth = 4,
InitialHeight = 4,
Missions =
[
new MissionDef
{
Id = 1,
Name = "Mission 1",
Description = "First mission",
TerrainPatch = new TerrainPatch
{
NewWidth = 4,
NewHeight = 4,
Cells =
[
new PatchCell { Col = 0, Row = 0, Type = CellType.Production,
Production = new ProductionDef(new Coords(0, 0), "Scierie", CargoType.Wood) },
new PatchCell { Col = 3, Row = 0, Type = CellType.Demand,
Demand = new DemandDef(new Coords(3, 0), "Depot", CargoType.Wood, 2) }
]
},
UnlockedPieces = [PieceKind.Pawn, PieceKind.Rook],
UnlockedLevels = [new PieceUpgrade(PieceKind.Pawn, 1), new PieceUpgrade(PieceKind.Rook, 1)],
Stock = [new PieceStock(PieceKind.Rook, 3)]
},
new MissionDef
{
Id = 2,
Name = "Mission 2",
Description = "Second mission — terrain expands east",
TerrainPatch = new TerrainPatch
{
NewWidth = 6,
NewHeight = 4,
Cells =
[
new PatchCell { Col = 4, Row = 0, Type = CellType.Empty },
new PatchCell { Col = 4, Row = 1, Type = CellType.Wall },
new PatchCell { Col = 5, Row = 0, Type = CellType.Production,
Production = new ProductionDef(new Coords(5, 0), "Carriere", CargoType.Stone) },
new PatchCell { Col = 5, Row = 3, Type = CellType.Demand,
Demand = new DemandDef(new Coords(5, 3), "Chantier", CargoType.Stone, 3) }
]
},
UnlockedPieces = [],
UnlockedLevels = [],
Stock = [new PieceStock(PieceKind.Rook, 2)]
}
]
};
}
[Fact]
public void LoadCampaign_InitializesBoard()
{
var campaign = CreateTwOMissionCampaign();
var sim = SimHelper.FromCampaign(campaign);
var events = sim.Sim.ProcessCommand(new LoadCampaignCommand());
Assert.Contains(events, e => e is CampaignLoadedEvent);
Assert.Contains(events, e => e is MissionStartedEvent ms && ms.MissionIndex == 0);
Assert.Contains(events, e => e is PieceUnlockedEvent pu && pu.Kind == PieceKind.Rook);
var snap = sim.Snapshot;
Assert.Equal(4, snap.Width);
Assert.Equal(4, snap.Height);
Assert.Equal(3, snap.RemainingStock[PieceKind.Rook]);
Assert.Equal(SimPhase.Paused, snap.Phase);
Assert.NotNull(snap.Campaign);
Assert.Equal(0, snap.Campaign.CurrentMissionIndex);
}
[Fact]
public void Campaign_CompleteMission1_AutoAdvances()
{
var campaign = CreateTwOMissionCampaign();
var sim = SimHelper.FromCampaign(campaign);
sim.Sim.ProcessCommand(new LoadCampaignCommand());
// Place rook to relay wood
sim.Place(PieceKind.Rook, (1, 0), (2, 0));
// Run — mission 1 completes and auto-advances to mission 2
var allEvents = sim.StepN(30);
Assert.Contains(allEvents, e => e is MissionCompleteEvent mc && mc.MissionIndex == 0);
Assert.Contains(allEvents, e => e is MissionStartedEvent ms && ms.MissionIndex == 1);
Assert.Contains(allEvents, e => e is TerrainExpandedEvent te && te.NewWidth == 6);
var snap = sim.Snapshot;
Assert.Equal(6, snap.Width);
Assert.Equal(4, snap.Height);
// Phase stays Paused (from StepSimulationCommand), NOT MissionComplete
Assert.Equal(SimPhase.Paused, snap.Phase);
Assert.Equal(1, snap.Campaign!.CurrentMissionIndex);
// Original rook is still in place
Assert.Single(snap.Pieces);
// Additional stock from mission 2 added
Assert.Equal(2 + 2, snap.RemainingStock[PieceKind.Rook]); // 2 leftover from M1 + 2 new
}
[Fact]
public void Campaign_PiecesRemainAfterAutoAdvance()
{
var campaign = CreateTwOMissionCampaign();
var sim = SimHelper.FromCampaign(campaign);
sim.Sim.ProcessCommand(new LoadCampaignCommand());
sim.Place(PieceKind.Rook, (1, 0), (2, 0));
// Run until auto-advance
sim.StepN(30);
// Piece is STILL on the board after auto-advancing
Assert.Single(sim.Snapshot.Pieces);
Assert.Equal(new Coords(1, 0), sim.Snapshot.Pieces[0].StartCell);
}
[Fact]
public void Campaign_PlacePieceDuringRunning()
{
var campaign = CreateTwOMissionCampaign();
var sim = SimHelper.FromCampaign(campaign);
sim.Sim.ProcessCommand(new LoadCampaignCommand());
sim.Resume(); // Paused → Running
var events = sim.Place(PieceKind.Rook, (1, 0), (2, 0));
Assert.IsType<PiecePlacedEvent>(events[0]);
}
[Fact]
public void Campaign_RemovePieceDuringRunning()
{
var campaign = CreateTwOMissionCampaign();
var sim = SimHelper.FromCampaign(campaign);
sim.Sim.ProcessCommand(new LoadCampaignCommand());
sim.Place(PieceKind.Rook, (1, 0), (2, 0));
sim.Resume(); // Running
var events = sim.Remove(1);
Assert.IsType<PieceRemovedEvent>(events[0]);
Assert.Equal(3, sim.Snapshot.RemainingStock[PieceKind.Rook]);
}
[Fact]
public void Campaign_CollisionReturnsPieceToStock()
{
// Two rooks heading to same cell → collision
var campaign = new CampaignDef
{
Name = "Collision Test",
InitialWidth = 4,
InitialHeight = 4,
Missions =
[
new MissionDef
{
Id = 1, Name = "M1",
TerrainPatch = new TerrainPatch
{
NewWidth = 4, NewHeight = 4,
Cells =
[
new PatchCell { Col = 0, Row = 0, Type = CellType.Production,
Production = new ProductionDef(new Coords(0, 0), "P", CargoType.Wood) },
new PatchCell { Col = 3, Row = 0, Type = CellType.Demand,
Demand = new DemandDef(new Coords(3, 0), "D", CargoType.Wood, 5) }
]
},
UnlockedPieces = [PieceKind.Rook, PieceKind.Queen],
UnlockedLevels = [new PieceUpgrade(PieceKind.Rook, 1), new PieceUpgrade(PieceKind.Queen, 1)],
Stock = [new PieceStock(PieceKind.Rook, 2), new PieceStock(PieceKind.Queen, 1)]
}
]
};
var sim = SimHelper.FromCampaign(campaign);
sim.Sim.ProcessCommand(new LoadCampaignCommand());
// Rook and Queen both pass through (1,0): collision!
// Rook: (0,0) ↔ (2,0), Queen: (2,0) ↔ (0,0) — they swap cells each turn, meeting at...
// Actually let's make them collide: same end cell
sim.Place(PieceKind.Rook, (0, 1), (1, 1));
sim.Place(PieceKind.Queen, (2, 1), (1, 1)); // same end cell → collision after first step
var stockBefore = sim.Snapshot.RemainingStock[PieceKind.Rook];
var events = sim.Step();
// Queen wins (status 7 vs 5), rook returns to stock
Assert.Contains(events, e => e is PieceReturnedToStockEvent ret && ret.Kind == PieceKind.Rook);
// Rook returned to stock
var stockAfter = sim.Snapshot.RemainingStock[PieceKind.Rook];
Assert.Equal(stockBefore + 1, stockAfter);
// Auto-pause on collision
Assert.Equal(SimPhase.Paused, sim.Snapshot.Phase);
}
[Fact]
public void Campaign_ManualAdvanceWithoutComplete_Rejected()
{
var campaign = CreateTwOMissionCampaign();
var sim = SimHelper.FromCampaign(campaign);
sim.Sim.ProcessCommand(new LoadCampaignCommand());
// AdvanceMissionCommand requires MissionComplete phase
var events = sim.AdvanceMission();
Assert.IsType<CommandRejectedEvent>(events[0]);
}
[Fact]
public void Campaign_LastMission_SetsMissionCompletePhase()
{
// Single-mission campaign — completing it should set MissionComplete phase
var campaign = new CampaignDef
{
Name = "Short", InitialWidth = 3, InitialHeight = 1,
Missions =
[
new MissionDef
{
Id = 1, Name = "Only",
TerrainPatch = new TerrainPatch
{
NewWidth = 3, NewHeight = 1,
Cells =
[
new PatchCell { Col = 0, Row = 0, Type = CellType.Production,
Production = new ProductionDef(new Coords(0, 0), "P", CargoType.Wood) },
new PatchCell { Col = 2, Row = 0, Type = CellType.Demand,
Demand = new DemandDef(new Coords(2, 0), "D", CargoType.Wood, 1) }
]
},
UnlockedPieces = [PieceKind.Rook],
UnlockedLevels = [new PieceUpgrade(PieceKind.Rook, 1)],
Stock = [new PieceStock(PieceKind.Rook, 1)]
}
]
};
var sim = SimHelper.FromCampaign(campaign);
sim.Sim.ProcessCommand(new LoadCampaignCommand());
sim.Place(PieceKind.Rook, (1, 0), (2, 0));
var allEvents = sim.StepN(10);
// Last mission → MissionComplete phase (no auto-advance)
Assert.Contains(allEvents, e => e is MissionCompleteEvent);
Assert.DoesNotContain(allEvents, e => e is MissionStartedEvent);
Assert.Equal(SimPhase.MissionComplete, sim.Snapshot.Phase);
}
[Fact]
public void Campaign_UnlockedPiecesEnforced()
{
var campaign = CreateTwOMissionCampaign();
var sim = SimHelper.FromCampaign(campaign);
sim.Sim.ProcessCommand(new LoadCampaignCommand());
// Bishop not unlocked — placement rejected
// First, add bishop to stock manually for this test
// Actually, the stock doesn't have bishops. Let's check that even if stock existed, unlock blocks it.
// The campaign only unlocks Pawn and Rook. No bishop stock either.
// Let's just verify the unlock set is correct.
var snap = sim.Snapshot;
Assert.Contains(PieceKind.Rook, snap.Campaign!.AvailablePieceKinds);
Assert.Contains(PieceKind.Pawn, snap.Campaign!.AvailablePieceKinds);
Assert.DoesNotContain(PieceKind.Bishop, snap.Campaign!.AvailablePieceKinds);
}
[Fact]
public void MovePiece_UpdatesPosition()
{
var campaign = CreateTwOMissionCampaign();
var sim = SimHelper.FromCampaign(campaign);
sim.Sim.ProcessCommand(new LoadCampaignCommand());
sim.Place(PieceKind.Rook, (1, 0), (2, 0));
var events = sim.Sim.ProcessCommand(new MovePieceCommand(1, new Coords(1, 1), new Coords(2, 1)));
Assert.Contains(events, e => e is PieceMovedByPlayerEvent mv
&& mv.OldStart == new Coords(1, 0) && mv.NewStart == new Coords(1, 1));
var snap = sim.Snapshot;
Assert.Equal(new Coords(1, 1), snap.Pieces[0].StartCell);
Assert.Equal(new Coords(2, 1), snap.Pieces[0].EndCell);
Assert.Equal(new Coords(1, 1), snap.Pieces[0].CurrentCell);
}
}