GameSim snapshots the state before each undoable command (PlacePiece / RemovePiece / MovePiece) into a bounded LinkedList stack (max 32). Undo() pops the last checkpoint and emits StateRestoredEvent, reusing the presentation rebuild path already wired for QuickLoad. Ctrl+Z in Main triggers the engine method; the harness exposes undo() for tests. QuickLoad clears the stack (fresh timeline). Seven unit tests cover empty stack, place/remove/move undo, reverse-order multiple undos, rejected commands not checkpointing, and post-simulation rewind.
120 lines
3.4 KiB
C#
120 lines
3.4 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 UndoTests
|
|
{
|
|
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 Undo_EmptyStack_ReturnsNoEvents()
|
|
{
|
|
var sim = CreateSim();
|
|
Assert.False(sim.Sim.CanUndo);
|
|
Assert.Empty(sim.Sim.Undo());
|
|
}
|
|
|
|
[Fact]
|
|
public void Undo_AfterPlace_RestoresPreviousState()
|
|
{
|
|
var sim = CreateSim();
|
|
Assert.False(sim.Sim.CanUndo);
|
|
|
|
sim.Place(PieceKind.Rook, (0, 0), (1, 0));
|
|
Assert.Single(sim.Snapshot.Pieces);
|
|
Assert.True(sim.Sim.CanUndo);
|
|
|
|
var events = sim.Sim.Undo();
|
|
Assert.Single(events);
|
|
Assert.IsType<StateRestoredEvent>(events[0]);
|
|
Assert.Empty(sim.Snapshot.Pieces);
|
|
Assert.Equal(3, sim.Snapshot.RemainingStock[PieceKind.Rook]);
|
|
Assert.False(sim.Sim.CanUndo);
|
|
}
|
|
|
|
[Fact]
|
|
public void Undo_AfterRemove_RestoresPiece()
|
|
{
|
|
var sim = CreateSim();
|
|
sim.Place(PieceKind.Rook, (0, 0), (1, 0));
|
|
var pieceId = sim.Snapshot.Pieces[0].Id;
|
|
|
|
sim.Remove(pieceId);
|
|
Assert.Empty(sim.Snapshot.Pieces);
|
|
|
|
sim.Sim.Undo();
|
|
Assert.Single(sim.Snapshot.Pieces);
|
|
Assert.Equal(pieceId, sim.Snapshot.Pieces[0].Id);
|
|
}
|
|
|
|
[Fact]
|
|
public void Undo_MultipleMutations_UndoneInReverseOrder()
|
|
{
|
|
var sim = CreateSim();
|
|
sim.Place(PieceKind.Rook, (0, 0), (1, 0));
|
|
sim.Place(PieceKind.Rook, (0, 1), (1, 1));
|
|
sim.Place(PieceKind.Rook, (0, 2), (1, 2));
|
|
Assert.Equal(3, sim.Snapshot.Pieces.Count);
|
|
|
|
sim.Sim.Undo();
|
|
Assert.Equal(2, sim.Snapshot.Pieces.Count);
|
|
|
|
sim.Sim.Undo();
|
|
Assert.Single(sim.Snapshot.Pieces);
|
|
|
|
sim.Sim.Undo();
|
|
Assert.Empty(sim.Snapshot.Pieces);
|
|
|
|
Assert.False(sim.Sim.CanUndo);
|
|
}
|
|
|
|
[Fact]
|
|
public void Undo_RejectedPlacement_DoesNotCheckpoint()
|
|
{
|
|
var sim = CreateSim();
|
|
// Invalid placement (off the board) — should be rejected and not undoable
|
|
sim.Place(PieceKind.Rook, (99, 99), (100, 100));
|
|
Assert.False(sim.Sim.CanUndo);
|
|
}
|
|
|
|
[Fact]
|
|
public void Undo_AfterSimulationStep_RewindsTurnsToo()
|
|
{
|
|
// Placing then stepping means the sim advanced. Undo of the placement
|
|
// should restore the pre-placement state — which also means turn 0.
|
|
var sim = CreateSim();
|
|
sim.Place(PieceKind.Rook, (0, 0), (1, 0));
|
|
sim.Step();
|
|
sim.Step();
|
|
Assert.Equal(2, sim.Snapshot.TurnNumber);
|
|
|
|
sim.Sim.Undo();
|
|
Assert.Equal(0, sim.Snapshot.TurnNumber);
|
|
Assert.Empty(sim.Snapshot.Pieces);
|
|
}
|
|
|
|
[Fact]
|
|
public void QuickLoad_ClearsUndoStack()
|
|
{
|
|
var sim = CreateSim();
|
|
sim.Place(PieceKind.Rook, (0, 0), (1, 0));
|
|
sim.Sim.QuickSave();
|
|
sim.Place(PieceKind.Rook, (0, 1), (1, 1));
|
|
|
|
// Two undoable mutations so far — load clears the stack
|
|
sim.Sim.QuickLoad();
|
|
Assert.False(sim.Sim.CanUndo);
|
|
}
|
|
}
|