Chessistics/chessistics-tests/Simulation/SolvabilityTests.cs

319 lines
12 KiB
C#
Raw 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;
/// <summary>
/// End-to-end solvability tests: each test places pieces, runs the simulation,
/// and asserts MissionCompleteEvent is produced — proving the level is winnable.
/// </summary>
public class SolvabilityTests
{
[Fact]
public void SingleRook_ShortRelay_MissionComplete()
{
var level = new BoardBuilder(3, 1)
.WithProduction(0, 0, "Scierie", CargoType.Wood)
.WithDemand(2, 0, "Depot", CargoType.Wood, 2, 30)
.WithStock(PieceKind.Rook, 1)
.Build();
var sim = SimHelper.FromLevel(level);
sim.Place(PieceKind.Rook, (1, 0), (2, 0));
var allEvents = sim.StepN(20);
Assert.Contains(allEvents, e => e is MissionCompleteEvent);
}
[Fact]
public void ThreePieceChain_SharedRelayPoints_MissionComplete()
{
var level = new BoardBuilder(5, 2)
.WithProduction(0, 0, "Scierie", CargoType.Wood)
.WithDemand(4, 0, "Depot", CargoType.Wood, 2, 40)
.WithStock(PieceKind.Rook, 3)
.Build();
var sim = SimHelper.FromLevel(level);
sim.Place(PieceKind.Rook, (1, 0), (2, 0));
sim.Place(PieceKind.Rook, (2, 0), (3, 0));
sim.Place(PieceKind.Rook, (3, 0), (4, 0));
var allEvents = sim.StepN(30);
Assert.Contains(allEvents, e => e is MissionCompleteEvent);
Assert.True(
allEvents.OfType<CargoTransferredEvent>().Count() >= 4,
"Expected at least 4 cargo transfers across the 3-piece chain");
}
[Fact]
public void TwoDemands_SingleSource_BothSatisfied()
{
var level = new BoardBuilder(4, 3)
.WithProduction(0, 0, "Scierie", CargoType.Wood)
.WithDemand(2, 0, "Depot Royal", CargoType.Wood, 2, 30)
.WithDemand(0, 2, "Caserne", CargoType.Wood, 2, 30)
.WithStock(PieceKind.Rook, 2)
.Build();
var sim = SimHelper.FromLevel(level);
sim.Place(PieceKind.Rook, (1, 0), (2, 0));
sim.Place(PieceKind.Rook, (0, 1), (0, 2));
var allEvents = sim.StepN(20);
Assert.Contains(allEvents, e => e is MissionCompleteEvent);
var demandProgress = allEvents.OfType<DemandProgressEvent>().ToList();
Assert.Contains(demandProgress, dp => dp.DemandCell == new Coords(2, 0) && dp.Current == dp.Required);
Assert.Contains(demandProgress, dp => dp.DemandCell == new Coords(0, 2) && dp.Current == dp.Required);
}
[Fact]
public void TwoCargoTypes_ParallelRoutes_MissionComplete()
{
var level = new BoardBuilder(4, 2)
.WithProduction(0, 0, "Scierie", CargoType.Wood)
.WithProduction(0, 1, "Carriere", CargoType.Stone)
.WithDemand(3, 0, "Depot Royal", CargoType.Wood, 2, 30)
.WithDemand(3, 1, "Forge", CargoType.Stone, 2, 30)
.WithStock(PieceKind.Rook, 2)
.Build();
var sim = SimHelper.FromLevel(level);
sim.Place(PieceKind.Rook, (1, 0), (2, 0));
sim.Place(PieceKind.Rook, (1, 1), (2, 1));
var allEvents = sim.StepN(20);
Assert.Contains(allEvents, e => e is MissionCompleteEvent);
var transfers = allEvents.OfType<CargoTransferredEvent>().ToList();
foreach (var t in transfers.Where(t => t.To == new Coords(3, 0)))
Assert.Equal(CargoType.Wood, t.Type);
foreach (var t in transfers.Where(t => t.To == new Coords(3, 1)))
Assert.Equal(CargoType.Stone, t.Type);
}
[Fact]
public void Bishop_DiagonalRelay_MissionComplete()
{
var level = new BoardBuilder(4, 3)
.WithProduction(0, 0, "Scierie", CargoType.Wood)
.WithDemand(2, 1, "Depot", CargoType.Wood, 2, 30)
.WithStock(PieceKind.Rook, 1)
.WithStock(PieceKind.Bishop, 1)
.Build();
var sim = SimHelper.FromLevel(level);
sim.Place(PieceKind.Rook, (0, 1), (0, 0));
sim.Place(PieceKind.Bishop, (1, 1), (2, 2));
var allEvents = sim.StepN(20);
Assert.Contains(allEvents, e => e is MissionCompleteEvent);
}
[Fact]
public void Knight_JumpsWall_MissionComplete()
{
var level = new BoardBuilder(5, 3)
.WithProduction(0, 0, "Scierie", CargoType.Wood)
.WithDemand(4, 0, "Depot", CargoType.Wood, 2, 30)
.WithWall(2, 0).WithWall(2, 1).WithWall(2, 2)
.WithStock(PieceKind.Rook, 1)
.WithStock(PieceKind.Knight, 1)
.Build();
var sim = SimHelper.FromLevel(level);
sim.Place(PieceKind.Rook, (1, 0), (1, 1));
sim.Place(PieceKind.Knight, (1, 1), (3, 0));
var allEvents = sim.StepN(20);
Assert.Contains(allEvents, e => e is MissionCompleteEvent);
Assert.Contains(allEvents, e => e is PieceMovedEvent m && m.To == new Coords(3, 0));
}
[Fact]
public void NoCollision_WithSharedRelayPoints()
{
var level = new BoardBuilder(5, 2)
.WithProduction(0, 0, "P", CargoType.Wood)
.WithDemand(4, 0, "D", CargoType.Wood, 1, 40)
.WithStock(PieceKind.Rook, 2)
.Build();
var sim = SimHelper.FromLevel(level);
sim.Place(PieceKind.Rook, (1, 0), (2, 0));
sim.Place(PieceKind.Rook, (2, 0), (3, 0));
var allEvents = sim.StepN(20);
Assert.DoesNotContain(allEvents, e => e is PieceReturnedToStockEvent);
}
[Fact]
public void CargoFilter_AutoAssigned_PreventsContamination()
{
var level = new BoardBuilder(4, 1)
.WithProduction(0, 0, "Scierie", CargoType.Wood)
.WithProduction(3, 0, "Carriere", CargoType.Stone)
.WithDemand(2, 0, "D_Wood", CargoType.Wood, 2, 20)
.WithStock(PieceKind.Rook, 1)
.Build();
var sim = SimHelper.FromLevel(level);
sim.Place(PieceKind.Rook, (1, 0), (2, 0));
var snapshot = sim.Snapshot;
Assert.Equal(CargoType.Wood, snapshot.Pieces[0].CargoFilter);
var allEvents = sim.StepN(20);
var transfers = allEvents.OfType<CargoTransferredEvent>().ToList();
Assert.All(transfers, t => Assert.Equal(CargoType.Wood, t.Type));
Assert.Contains(allEvents, e => e is MissionCompleteEvent);
}
[Fact]
public void CargoFilter_PropagatesThroughChain()
{
var level = new BoardBuilder(5, 2)
.WithProduction(0, 0, "Scierie", CargoType.Wood)
.WithDemand(4, 0, "Depot", CargoType.Wood, 2, 40)
.WithStock(PieceKind.Rook, 3)
.Build();
var sim = SimHelper.FromLevel(level);
sim.Place(PieceKind.Rook, (1, 0), (2, 0));
sim.Place(PieceKind.Rook, (2, 0), (3, 0));
sim.Place(PieceKind.Rook, (3, 0), (4, 0));
var snapshot = sim.Snapshot;
Assert.Equal(CargoType.Wood, snapshot.Pieces[0].CargoFilter);
Assert.Equal(CargoType.Wood, snapshot.Pieces[1].CargoFilter);
Assert.Equal(CargoType.Wood, snapshot.Pieces[2].CargoFilter);
}
[Fact]
public void StepFromPaused_Works()
{
var level = new BoardBuilder(3, 1)
.WithProduction(0, 0, "P", CargoType.Wood)
.WithDemand(2, 0, "D", CargoType.Wood, 1, 30)
.WithStock(PieceKind.Rook, 1)
.Build();
var sim = SimHelper.FromLevel(level);
sim.Place(PieceKind.Rook, (1, 0), (2, 0));
// Step directly from Paused
var allEvents = sim.StepN(20);
Assert.Contains(allEvents, e => e is TurnStartedEvent);
Assert.Contains(allEvents, e => e is MissionCompleteEvent);
}
/// <summary>
/// Full transformer chain: Production(Wood) → Piece → Forge(Wood→Tools) → Piece → Demand(Tools)
/// </summary>
[Fact]
public void TransformerChain_WoodToTools_MissionComplete()
{
var campaign = new CampaignDef
{
Name = "Solvability: Transformer", InitialWidth = 5, InitialHeight = 1,
Missions =
[
new MissionDef
{
Id = 1, Name = "Forge",
TerrainPatch = new TerrainPatch
{
NewWidth = 5, NewHeight = 1,
Cells =
[
new PatchCell { Col = 0, Row = 0, Type = CellType.Production,
Production = new ProductionDef(new Coords(0, 0), "Scierie", CargoType.Wood, 4) },
new PatchCell { Col = 2, Row = 0, Type = CellType.Transformer,
Transformer = new TransformerDef(new Coords(2, 0), "Forge", CargoType.Wood, 2, CargoType.Tools, 1) },
new PatchCell { Col = 4, Row = 0, Type = CellType.Demand,
Demand = new DemandDef(new Coords(4, 0), "Atelier", CargoType.Tools, 2) }
]
},
UnlockedPieces = [PieceKind.Rook],
UnlockedLevels = [new PieceUpgrade(PieceKind.Rook, 1)],
Stock = [new PieceStock(PieceKind.Rook, 3)]
}
]
};
var sim = SimHelper.FromCampaign(campaign);
sim.Sim.ProcessCommand(new LoadCampaignCommand());
// Rook 1: delivers wood to forge input
sim.Place(PieceKind.Rook, (0, 0), (1, 0));
// Rook 2: picks up tools from forge output, delivers to demand
sim.Place(PieceKind.Rook, (3, 0), (4, 0));
var allEvents = sim.StepN(50);
Assert.Contains(allEvents, e => e is CargoConvertedEvent);
Assert.Contains(allEvents, e => e is MissionCompleteEvent);
}
/// <summary>
/// Two-stage transformation: Wood → Forge → Tools → Comptoir → Gold
/// </summary>
[Fact]
public void DoubleTransformerChain_WoodToToolsToGold_MissionComplete()
{
var campaign = new CampaignDef
{
Name = "Solvability: Double Transformer", InitialWidth = 7, InitialHeight = 1,
Missions =
[
new MissionDef
{
Id = 1, Name = "Double Chain",
TerrainPatch = new TerrainPatch
{
NewWidth = 7, NewHeight = 1,
Cells =
[
new PatchCell { Col = 0, Row = 0, Type = CellType.Production,
Production = new ProductionDef(new Coords(0, 0), "Scierie", CargoType.Wood, 4) },
new PatchCell { Col = 2, Row = 0, Type = CellType.Transformer,
Transformer = new TransformerDef(new Coords(2, 0), "Forge", CargoType.Wood, 2, CargoType.Tools, 1) },
new PatchCell { Col = 4, Row = 0, Type = CellType.Transformer,
Transformer = new TransformerDef(new Coords(4, 0), "Comptoir", CargoType.Tools, 2, CargoType.Gold, 1) },
new PatchCell { Col = 6, Row = 0, Type = CellType.Demand,
Demand = new DemandDef(new Coords(6, 0), "Tresor", CargoType.Gold, 1) }
]
},
UnlockedPieces = [PieceKind.Rook],
UnlockedLevels = [new PieceUpgrade(PieceKind.Rook, 1)],
Stock = [new PieceStock(PieceKind.Rook, 4)]
}
]
};
var sim = SimHelper.FromCampaign(campaign);
sim.Sim.ProcessCommand(new LoadCampaignCommand());
// Chain: Scierie → Rook1 → Forge → Rook2 → Comptoir → Rook3 → Tresor
sim.Place(PieceKind.Rook, (0, 0), (1, 0)); // wood delivery
sim.Place(PieceKind.Rook, (3, 0), (4, 0)); // tools delivery (picks from forge, delivers to comptoir)
sim.Place(PieceKind.Rook, (5, 0), (6, 0)); // gold delivery
var allEvents = sim.StepN(80);
// Should see both transformations
var conversions = allEvents.OfType<CargoConvertedEvent>().ToList();
Assert.Contains(conversions, c => c.OutputCargo == CargoType.Tools);
Assert.Contains(conversions, c => c.OutputCargo == CargoType.Gold);
Assert.Contains(allEvents, e => e is MissionCompleteEvent);
}
}