using Chessistics.Engine.Commands; using Chessistics.Engine.Events; using Chessistics.Engine.Model; namespace Chessistics.Engine.Simulation; public class GameSim { private readonly BoardState _state; private readonly Dictionary _saveSlots = new(); private readonly LinkedList _undoStack = new(); private const int DefaultSlot = 0; private const int UndoStackLimit = 32; public GameSim(LevelDef level) { _state = BoardState.FromLevel(level); } public GameSim(CampaignDef campaign) { _state = BoardState.FromCampaign(campaign); } public IReadOnlyList ProcessCommand(IWorldCommand command) { WorldSave? undoCheckpoint = IsUndoable(command) ? _state.CaptureSave() : null; var changeList = new List(); try { command.Apply(_state, changeList); } catch (CommandRejectedException ex) { return [ex.RejectionEvent]; } if (undoCheckpoint != null && ContainsMutation(changeList)) PushUndo(undoCheckpoint); return changeList; } private static bool IsUndoable(IWorldCommand command) => command switch { PlacePieceCommand => true, RemovePieceCommand => true, MovePieceCommand => true, _ => false }; private static bool ContainsMutation(List events) => events.Any(e => e is PiecePlacedEvent or PieceRemovedEvent or PieceMovedByPlayerEvent); private void PushUndo(WorldSave save) { _undoStack.AddLast(save); while (_undoStack.Count > UndoStackLimit) _undoStack.RemoveFirst(); } public bool CanUndo => _undoStack.Count > 0; /// /// Revert the last undoable mutation (placement, removal, or move) by /// restoring the pre-command snapshot. Emits StateRestoredEvent so the /// presentation can rebuild visuals. Returns empty if nothing to undo. /// public IReadOnlyList Undo() { if (_undoStack.Count == 0) return []; var save = _undoStack.Last!.Value; _undoStack.RemoveLast(); _state.RestoreFromSave(save); return [new StateRestoredEvent(new BoardSnapshot(_state), null)]; } public BoardSnapshot GetSnapshot() => new(_state); /// /// Capture a full deep-copy of the world into an in-memory slot. /// Returns a single StateSavedEvent — no state mutation. /// public IReadOnlyList QuickSave(int slot = DefaultSlot) { _saveSlots[slot] = _state.CaptureSave(); return [new StateSavedEvent(_state.TurnNumber, slot)]; } /// /// Restore the world from a saved slot. Emits StateRestoredEvent with a /// fresh snapshot — the presentation layer must rebuild all visuals. /// Returns an empty list (no event) if the slot is empty. /// public IReadOnlyList QuickLoad(int slot = DefaultSlot) { if (!_saveSlots.TryGetValue(slot, out var save)) return []; _state.RestoreFromSave(save); _undoStack.Clear(); return [new StateRestoredEvent(new BoardSnapshot(_state), slot)]; } public bool HasSave(int slot = DefaultSlot) => _saveSlots.ContainsKey(slot); }