BoardState.CaptureSave/RestoreFromSave deep-copy every mutable field (grid, pieces, demands, transformers, buffers, stock, campaign progress) into a WorldSave slot. GameSim.QuickSave/QuickLoad expose slotted saves and emit StateSavedEvent / StateRestoredEvent — the latter carries a fresh BoardSnapshot so the presentation can rebuild board, pieces, trajectories, objectives, stock, camera, and control bar in one pass. F5/F9 trigger it in Main; harness gains quick_save/quick_load commands so UI tests can checkpoint a scenario and resume without replaying from scratch. Seven xUnit tests cover the roundtrip (including independence from post-save mutations, campaign state, and multi-slot isolation).
181 lines
5.6 KiB
C#
181 lines
5.6 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 QuickSaveTests
|
|
{
|
|
private SimHelper CreateSim()
|
|
{
|
|
var level = new BoardBuilder(4, 4)
|
|
.WithProduction(0, 0, "Scierie", CargoType.Wood, amount: 1)
|
|
.WithDemand(3, 0, "Depot", CargoType.Wood, 3, 30)
|
|
.WithStock(PieceKind.Rook, 3)
|
|
.Build();
|
|
return SimHelper.FromLevel(level);
|
|
}
|
|
|
|
[Fact]
|
|
public void QuickSave_ReturnsSavedEvent()
|
|
{
|
|
var sim = CreateSim();
|
|
var events = sim.Sim.QuickSave();
|
|
Assert.Single(events);
|
|
Assert.IsType<StateSavedEvent>(events[0]);
|
|
}
|
|
|
|
[Fact]
|
|
public void QuickLoad_WithoutSave_ReturnsEmpty()
|
|
{
|
|
var sim = CreateSim();
|
|
var events = sim.Sim.QuickLoad();
|
|
Assert.Empty(events);
|
|
}
|
|
|
|
[Fact]
|
|
public void QuickLoad_AfterSave_EmitsRestoredEvent()
|
|
{
|
|
var sim = CreateSim();
|
|
sim.Sim.QuickSave();
|
|
var events = sim.Sim.QuickLoad();
|
|
Assert.Single(events);
|
|
var restored = Assert.IsType<StateRestoredEvent>(events[0]);
|
|
Assert.NotNull(restored.Snapshot);
|
|
}
|
|
|
|
[Fact]
|
|
public void Save_MutateState_Load_RestoresPreviousState()
|
|
{
|
|
var sim = CreateSim();
|
|
|
|
// Place a piece, then save
|
|
sim.Place(PieceKind.Rook, (0, 0), (1, 0));
|
|
sim.Sim.QuickSave();
|
|
|
|
var beforeSnap = sim.Snapshot;
|
|
Assert.Single(beforeSnap.Pieces);
|
|
Assert.Equal(3 - 1, beforeSnap.RemainingStock[PieceKind.Rook]);
|
|
|
|
// Mutate: place another piece, step simulation
|
|
sim.Place(PieceKind.Rook, (0, 1), (1, 1));
|
|
sim.Step();
|
|
sim.Step();
|
|
|
|
var dirtySnap = sim.Snapshot;
|
|
Assert.Equal(2, dirtySnap.Pieces.Count);
|
|
Assert.Equal(2, dirtySnap.TurnNumber);
|
|
|
|
// Load: should match beforeSnap
|
|
sim.Sim.QuickLoad();
|
|
var afterSnap = sim.Snapshot;
|
|
|
|
Assert.Single(afterSnap.Pieces);
|
|
Assert.Equal(0, afterSnap.TurnNumber);
|
|
Assert.Equal(3 - 1, afterSnap.RemainingStock[PieceKind.Rook]);
|
|
Assert.Equal(beforeSnap.Pieces[0].Id, afterSnap.Pieces[0].Id);
|
|
Assert.Equal(beforeSnap.Pieces[0].StartCell, afterSnap.Pieces[0].StartCell);
|
|
}
|
|
|
|
[Fact]
|
|
public void Load_IsIndependent_FromFurtherChanges()
|
|
{
|
|
// Saving should not alias; mutating state after save must not affect the save.
|
|
var sim = CreateSim();
|
|
sim.Place(PieceKind.Rook, (0, 0), (1, 0));
|
|
sim.Sim.QuickSave();
|
|
|
|
// Mutate after save
|
|
sim.Place(PieceKind.Rook, (0, 1), (1, 1));
|
|
sim.Step();
|
|
|
|
// Load should restore to 1 piece, turn 0
|
|
sim.Sim.QuickLoad();
|
|
var snap = sim.Snapshot;
|
|
Assert.Single(snap.Pieces);
|
|
Assert.Equal(0, snap.TurnNumber);
|
|
|
|
// Mutate again, load again — save still usable
|
|
sim.Place(PieceKind.Rook, (0, 2), (1, 2));
|
|
sim.Sim.QuickLoad();
|
|
snap = sim.Snapshot;
|
|
Assert.Single(snap.Pieces);
|
|
}
|
|
|
|
[Fact]
|
|
public void QuickSave_PreservesCampaignProgression()
|
|
{
|
|
var campaign = CampaignBuilder();
|
|
var sim = SimHelper.FromCampaign(campaign);
|
|
sim.Sim.ProcessCommand(new LoadCampaignCommand());
|
|
|
|
sim.Place(PieceKind.Pawn, (0, 0), (1, 0));
|
|
sim.Sim.QuickSave();
|
|
|
|
var before = sim.Snapshot;
|
|
Assert.Equal(0, before.Campaign!.CurrentMissionIndex);
|
|
|
|
// Mutate
|
|
sim.Step();
|
|
sim.Step();
|
|
|
|
sim.Sim.QuickLoad();
|
|
var after = sim.Snapshot;
|
|
Assert.Equal(before.Campaign!.CurrentMissionIndex, after.Campaign!.CurrentMissionIndex);
|
|
Assert.Equal(before.Pieces.Count, after.Pieces.Count);
|
|
Assert.Equal(0, after.TurnNumber);
|
|
}
|
|
|
|
[Fact]
|
|
public void MultipleSlots_AreIndependent()
|
|
{
|
|
var sim = CreateSim();
|
|
sim.Place(PieceKind.Rook, (0, 0), (1, 0));
|
|
sim.Sim.QuickSave(slot: 1);
|
|
|
|
sim.Place(PieceKind.Rook, (0, 1), (1, 1));
|
|
sim.Sim.QuickSave(slot: 2);
|
|
|
|
// Slot 1 should have 1 piece; slot 2 should have 2
|
|
sim.Sim.QuickLoad(slot: 1);
|
|
Assert.Single(sim.Snapshot.Pieces);
|
|
|
|
sim.Sim.QuickLoad(slot: 2);
|
|
Assert.Equal(2, sim.Snapshot.Pieces.Count);
|
|
}
|
|
|
|
private static CampaignDef CampaignBuilder()
|
|
{
|
|
return new CampaignDef
|
|
{
|
|
Name = "TestCampaign",
|
|
InitialWidth = 4,
|
|
InitialHeight = 4,
|
|
Missions = new List<MissionDef>
|
|
{
|
|
new()
|
|
{
|
|
Id = 1,
|
|
Name = "M1",
|
|
TerrainPatch = new TerrainPatch
|
|
{
|
|
NewWidth = 4,
|
|
NewHeight = 4,
|
|
Cells = new List<PatchCell>
|
|
{
|
|
new() { Col = 0, Row = 0, Type = CellType.Production,
|
|
Production = new ProductionDef(new Coords(0, 0), "S", CargoType.Wood, 1) },
|
|
new() { Col = 3, Row = 0, Type = CellType.Demand,
|
|
Demand = new DemandDef(new Coords(3, 0), "D", CargoType.Wood, 3) }
|
|
}
|
|
},
|
|
UnlockedPieces = new List<PieceKind> { PieceKind.Pawn },
|
|
UnlockedLevels = new List<PieceUpgrade> { new(PieceKind.Pawn, 1) },
|
|
Stock = new List<PieceStock> { new(PieceKind.Pawn, 4) }
|
|
}
|
|
}
|
|
};
|
|
}
|
|
}
|