using Chessistics.Engine.Commands; using Chessistics.Engine.Events; using Chessistics.Engine.Model; using Chessistics.Tests.Helpers; using Xunit; namespace Chessistics.Tests.Simulation; public class GameSimTests { private SimHelper CreateLevel1Sim() { var level = new BoardBuilder(4, 4) .WithProduction(0, 0, "Scierie", CargoType.Wood, 2) .WithDemand(3, 0, "Depot Royal", CargoType.Wood, 3, 30) .WithStock(PieceKind.Rook, 3) .Build(); return SimHelper.FromLevel(level); } [Fact] public void PlacePiece_Succeeds() { var sim = CreateLevel1Sim(); var events = sim.Place(PieceKind.Rook, (0, 0), (1, 0)); Assert.Single(events); Assert.IsType(events[0]); var placed = (PiecePlacedEvent)events[0]; Assert.Equal(PieceKind.Rook, placed.Kind); } [Fact] public void PlacePiece_StockExhausted_Rejected() { var sim = CreateLevel1Sim(); sim.Place(PieceKind.Rook, (0, 0), (1, 0)); sim.Place(PieceKind.Rook, (0, 1), (1, 1)); sim.Place(PieceKind.Rook, (0, 2), (1, 2)); // 4th rook — only 3 in stock var events = sim.Place(PieceKind.Rook, (0, 3), (1, 3)); Assert.IsType(events[0]); } [Fact] public void PlaceDuringRunning_Rejected() { var sim = CreateLevel1Sim(); sim.Place(PieceKind.Rook, (0, 0), (1, 0)); sim.Start(); var events = sim.Place(PieceKind.Rook, (0, 1), (1, 1)); Assert.IsType(events[0]); } [Fact] public void StartWithNoPieces_Rejected() { var sim = CreateLevel1Sim(); var events = sim.Start(); Assert.IsType(events[0]); } [Fact] public void RemoveDuringRunning_Rejected() { var sim = CreateLevel1Sim(); sim.Place(PieceKind.Rook, (0, 0), (1, 0)); sim.Start(); var events = sim.Remove(1); Assert.IsType(events[0]); } [Fact] public void StopDuringEdit_Rejected() { var sim = CreateLevel1Sim(); var events = sim.Stop(); Assert.IsType(events[0]); } [Fact] public void SinglePiece_Oscillates() { var sim = CreateLevel1Sim(); sim.Place(PieceKind.Rook, (0, 0), (2, 0)); sim.Start(); // Step 1: piece moves from (0,0) to (2,0) var events1 = sim.Step(); Assert.Contains(events1, e => e is PieceMovedEvent m && m.From == new Coords(0, 0) && m.To == new Coords(2, 0)); // Step 2: piece moves back from (2,0) to (0,0) var events2 = sim.Step(); Assert.Contains(events2, e => e is PieceMovedEvent m && m.From == new Coords(2, 0) && m.To == new Coords(0, 0)); // Step 3: piece moves forward again var events3 = sim.Step(); Assert.Contains(events3, e => e is PieceMovedEvent m && m.From == new Coords(0, 0) && m.To == new Coords(2, 0)); } [Fact] public void ChainedPieces_TransferCargo() { var sim = CreateLevel1Sim(); // Piece A: (0,0) → (1,0), Piece B: (2,0) → (3,0) // Adjacent at (1,0)↔(2,0) when A is at end and B is at start sim.Place(PieceKind.Rook, (0, 0), (1, 0)); sim.Place(PieceKind.Rook, (2, 0), (3, 0)); sim.Start(); // Run until we see a cargo transfer between pieces var allEvents = sim.StepN(20); // Should have production events and cargo transfers Assert.Contains(allEvents, e => e is CargoProducedEvent); Assert.Contains(allEvents, e => e is CargoTransferredEvent); } [Fact] public void Production_GeneratesOnInterval() { var sim = CreateLevel1Sim(); // Place rook adjacent to production so it picks up cargo, freeing the buffer sim.Place(PieceKind.Rook, (0, 0), (1, 0)); sim.Start(); var allEvents = sim.StepN(6); var prodEvents = allEvents.OfType().ToList(); // With interval 2, produces on turns 2, 4, 6 (buffer freed each time by adjacent piece) Assert.True(prodEvents.Count >= 2, $"Expected at least 2 productions, got {prodEvents.Count}"); } [Fact] public void Victory_WhenAllDemandsMet() { // Tiny level: prod adjacent to demand, just need one piece to relay var level = new BoardBuilder(3, 1) .WithProduction(0, 0, "P", CargoType.Wood, 1) .WithDemand(2, 0, "D", CargoType.Wood, 1, 30) .WithStock(PieceKind.Rook, 2) .Build(); var sim = SimHelper.FromLevel(level); sim.Place(PieceKind.Rook, (1, 0), (2, 0)); sim.Start(); // Run enough turns for production → piece → demand var allEvents = sim.StepN(10); Assert.Contains(allEvents, e => e is VictoryEvent); } [Fact] public void Defeat_WhenDeadlineExpires() { // Demand with very tight deadline, piece placed far from demand var level = new BoardBuilder(4, 4) .WithProduction(0, 0, "P", CargoType.Wood, 2) .WithDemand(3, 3, "D", CargoType.Wood, 10, 3) // need 10 in 3 turns — impossible .WithStock(PieceKind.Rook, 3) .Build(); var sim = SimHelper.FromLevel(level); sim.Place(PieceKind.Rook, (1, 0), (2, 0)); sim.Start(); var allEvents = sim.StepN(5); Assert.Contains(allEvents, e => e is DeadlineExpiredEvent); } [Fact] public void StopResetsState() { var sim = CreateLevel1Sim(); sim.Place(PieceKind.Rook, (0, 0), (1, 0)); sim.Start(); sim.StepN(5); sim.Stop(); var snap = sim.Snapshot; Assert.Equal(SimPhase.Edit, snap.Phase); Assert.Equal(0, snap.TurnNumber); // Pieces should be back at start cells Assert.All(snap.Pieces, p => Assert.Equal(p.StartCell, p.CurrentCell)); } [Fact] public void ResetClearsEverything() { var sim = CreateLevel1Sim(); sim.Place(PieceKind.Rook, (0, 0), (1, 0)); sim.Reset(); var snap = sim.Snapshot; Assert.Equal(SimPhase.Edit, snap.Phase); Assert.Empty(snap.Pieces); Assert.Equal(3, snap.RemainingStock[PieceKind.Rook]); } }