using Chessistics.Engine.Events; using Chessistics.Engine.Model; using Chessistics.Engine.Rules; using Chessistics.Engine.Simulation; namespace Chessistics.Engine.Commands; /// /// Place a piece on the board. Works in any phase (Running or Paused). /// The placement takes effect between turns. /// public class PlacePieceCommand : WorldCommand { public PieceKind Kind { get; } public Coords Start { get; } public Coords End { get; } public int Level { get; } public PlacePieceCommand(PieceKind kind, Coords start, Coords end, int level = 1) { Kind = kind; Start = start; End = end; Level = level; } public override void AssertApplicationConditions(BoardState state) { if (!state.RemainingStock.TryGetValue(Kind, out var remaining) || remaining <= 0) throw new CommandRejectedException( new PlacementRejectedEvent(Kind, Start, End, "No pieces of this type remaining in stock.")); if (!state.IsOnBoard(Start) || !state.IsOnBoard(End)) throw new CommandRejectedException( new PlacementRejectedEvent(Kind, Start, End, "Position is off the board.")); if (state.GetCell(Start) == CellType.Wall) throw new CommandRejectedException( new PlacementRejectedEvent(Kind, Start, End, "Cannot place on a wall.")); if (!MoveValidator.IsLegalPlacement(Kind, Start, End, state)) throw new CommandRejectedException( new PlacementRejectedEvent(Kind, Start, End, "Illegal move for this piece type.")); // Check piece kind is unlocked (campaign mode) if (state.Campaign != null && !state.Campaign.IsPieceAvailable(Kind)) throw new CommandRejectedException( new PlacementRejectedEvent(Kind, Start, End, $"Piece type {Kind} is not unlocked yet.")); // Check piece level is unlocked (campaign mode) if (state.Campaign != null && !state.Campaign.IsLevelAvailable(Kind, Level)) throw new CommandRejectedException( new PlacementRejectedEvent(Kind, Start, End, $"Level {Level} for {Kind} is not unlocked yet.")); } protected override void DoApply(BoardState state, List changeList) { var piece = new PieceState( state.NextPieceId++, Kind, Start, End, state.Pieces.Count, Level); piece.CargoFilter = InferCargoFilter(state, piece); state.Pieces.Add(piece); state.RemainingStock[Kind] = state.RemainingStock[Kind] - 1; state.OccupiedCells.Add(Start); state.OccupiedCells.Add(End); changeList.Add(new PiecePlacedEvent(piece.Id, Kind, Start, End)); } private static CargoType? InferCargoFilter(BoardState state, PieceState piece) { foreach (var (prodPos, prod) in state.Productions) { if (piece.StartCell.IsAdjacent4(prodPos) || piece.EndCell.IsAdjacent4(prodPos)) return prod.Cargo; } // Transformer output acts like a production foreach (var (tPos, transformer) in state.Transformers) { if (piece.StartCell.IsAdjacent4(tPos) || piece.EndCell.IsAdjacent4(tPos)) return transformer.OutputCargo; } foreach (var existing in state.Pieces) { if (existing.CargoFilter == null) continue; bool sharesRelay = piece.StartCell == existing.StartCell || piece.StartCell == existing.EndCell || piece.EndCell == existing.StartCell || piece.EndCell == existing.EndCell; if (sharesRelay) return existing.CargoFilter; } return null; } } /// /// Remove a piece from the board. Works in any phase. /// public class RemovePieceCommand : WorldCommand { public int PieceId { get; } public RemovePieceCommand(int pieceId) { PieceId = pieceId; } public override void AssertApplicationConditions(BoardState state) { if (state.GetPieceById(PieceId) == null) throw new CommandRejectedException( new CommandRejectedEvent(nameof(RemovePieceCommand), $"Piece {PieceId} not found.")); } protected override void DoApply(BoardState state, List changeList) { var piece = state.GetPieceById(PieceId)!; state.Pieces.Remove(piece); state.RemainingStock[piece.Kind] = state.RemainingStock.GetValueOrDefault(piece.Kind) + 1; changeList.Add(new PieceRemovedEvent(PieceId)); } } public class PauseSimulationCommand : WorldCommand { public override void AssertApplicationConditions(BoardState state) { if (state.Phase != SimPhase.Running) throw new CommandRejectedException( new CommandRejectedEvent(nameof(PauseSimulationCommand), "Can only pause while running.")); } protected override void DoApply(BoardState state, List changeList) { state.Phase = SimPhase.Paused; changeList.Add(new SimulationPausedEvent()); } } public class ResumeSimulationCommand : WorldCommand { public override void AssertApplicationConditions(BoardState state) { if (state.Phase != SimPhase.Paused && state.Phase != SimPhase.MissionComplete) throw new CommandRejectedException( new CommandRejectedEvent(nameof(ResumeSimulationCommand), "Can only resume from Paused or MissionComplete phase.")); } protected override void DoApply(BoardState state, List changeList) { state.Phase = SimPhase.Running; changeList.Add(new SimulationResumedEvent()); } } public class StepSimulationCommand : WorldCommand { public override void AssertApplicationConditions(BoardState state) { if (state.Phase != SimPhase.Running && state.Phase != SimPhase.Paused) throw new CommandRejectedException( new CommandRejectedEvent(nameof(StepSimulationCommand), "Cannot step in current phase.")); } protected override void DoApply(BoardState state, List changeList) { var wasRunning = state.Phase == SimPhase.Running; TurnExecutor.ExecuteTurn(state, changeList); // After a manual step (was Paused), remain Paused. // After an auto-play step (was Running), stay Running unless // TurnExecutor changed it (collision → Paused, last mission → MissionComplete). if (!wasRunning && state.Phase == SimPhase.Running) state.Phase = SimPhase.Paused; } }