205 lines
6.3 KiB
C#
205 lines
6.3 KiB
C#
|
|
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<PiecePlacedEvent>(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<PlacementRejectedEvent>(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<CommandRejectedEvent>(events[0]);
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public void StartWithNoPieces_Rejected()
|
||
|
|
{
|
||
|
|
var sim = CreateLevel1Sim();
|
||
|
|
var events = sim.Start();
|
||
|
|
Assert.IsType<CommandRejectedEvent>(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<CommandRejectedEvent>(events[0]);
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public void StopDuringEdit_Rejected()
|
||
|
|
{
|
||
|
|
var sim = CreateLevel1Sim();
|
||
|
|
var events = sim.Stop();
|
||
|
|
Assert.IsType<CommandRejectedEvent>(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<CargoProducedEvent>().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]);
|
||
|
|
}
|
||
|
|
}
|