Chessistics/chessistics-engine/Commands/WorldCommands.cs
Samuel Bouchet e6eaae43ab Initial commit: Chessistics prototype v0.3
Black box sim engine (commands in, events out) with 3 piece types
(Rook, Bishop, Knight), cargo transfer system with social status
priority, collision detection, and victory/defeat conditions.

57 tests covering rules, simulation, loading, and solvability.
Godot 4 presentation layer scaffolding.
2026-04-10 14:58:03 +02:00

208 lines
7 KiB
C#

using Chessistics.Engine.Events;
using Chessistics.Engine.Model;
using Chessistics.Engine.Rules;
using Chessistics.Engine.Simulation;
namespace Chessistics.Engine.Commands;
public class PlacePieceCommand : WorldCommand
{
public PieceKind Kind { get; }
public Coords Start { get; }
public Coords End { get; }
public PlacePieceCommand(PieceKind kind, Coords start, Coords end)
{
Kind = kind;
Start = start;
End = end;
}
public override void AssertApplicationConditions(BoardState state)
{
if (state.Phase != SimPhase.Edit)
throw new CommandRejectedException(
new CommandRejectedEvent(nameof(PlacePieceCommand), "Can only place pieces during Edit phase."));
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."));
}
protected override void DoApply(BoardState state, List<IWorldEvent> changeList)
{
var piece = new PieceState(
state.NextPieceId++, Kind, Start, End, state.Pieces.Count);
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));
}
}
public class RemovePieceCommand : WorldCommand
{
public int PieceId { get; }
public RemovePieceCommand(int pieceId)
{
PieceId = pieceId;
}
public override void AssertApplicationConditions(BoardState state)
{
if (state.Phase != SimPhase.Edit)
throw new CommandRejectedException(
new CommandRejectedEvent(nameof(RemovePieceCommand), "Can only remove pieces during Edit phase."));
if (state.GetPieceById(PieceId) == null)
throw new CommandRejectedException(
new CommandRejectedEvent(nameof(RemovePieceCommand), $"Piece {PieceId} not found."));
}
protected override void DoApply(BoardState state, List<IWorldEvent> 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 StartSimulationCommand : WorldCommand
{
public override void AssertApplicationConditions(BoardState state)
{
if (state.Phase != SimPhase.Edit)
throw new CommandRejectedException(
new CommandRejectedEvent(nameof(StartSimulationCommand), "Can only start from Edit phase."));
if (state.Pieces.Count == 0)
throw new CommandRejectedException(
new CommandRejectedEvent(nameof(StartSimulationCommand), "Place at least one piece before starting."));
}
protected override void DoApply(BoardState state, List<IWorldEvent> changeList)
{
state.Phase = SimPhase.Running;
changeList.Add(new SimulationStartedEvent());
}
}
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<IWorldEvent> changeList)
{
state.Phase = SimPhase.Paused;
changeList.Add(new SimulationPausedEvent());
}
}
public class ResumeSimulationCommand : WorldCommand
{
public override void AssertApplicationConditions(BoardState state)
{
if (state.Phase != SimPhase.Paused)
throw new CommandRejectedException(
new CommandRejectedEvent(nameof(ResumeSimulationCommand), "Can only resume from Paused phase."));
}
protected override void DoApply(BoardState state, List<IWorldEvent> changeList)
{
state.Phase = SimPhase.Running;
changeList.Add(new SimulationResumedEvent());
}
}
public class StepSimulationCommand : WorldCommand
{
public override void AssertApplicationConditions(BoardState state)
{
if (state.Phase == SimPhase.Edit && state.Pieces.Count == 0)
throw new CommandRejectedException(
new CommandRejectedEvent(nameof(StepSimulationCommand), "Place at least one piece before stepping."));
if (state.Phase != SimPhase.Edit && 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<IWorldEvent> changeList)
{
if (state.Phase == SimPhase.Edit)
state.Phase = SimPhase.Paused;
TurnExecutor.ExecuteTurn(state, changeList);
// After a step, remain in Paused unless victory/defeat/collision occurred
if (state.Phase == SimPhase.Running)
state.Phase = SimPhase.Paused;
}
}
public class StopSimulationCommand : WorldCommand
{
public override void AssertApplicationConditions(BoardState state)
{
if (state.Phase == SimPhase.Edit)
throw new CommandRejectedException(
new CommandRejectedEvent(nameof(StopSimulationCommand), "Already in Edit phase."));
}
protected override void DoApply(BoardState state, List<IWorldEvent> changeList)
{
foreach (var piece in state.Pieces)
{
piece.CurrentCell = piece.StartCell;
piece.Cargo = null;
}
foreach (var pos in state.ProductionBuffers.Keys.ToList())
state.ProductionBuffers[pos] = null;
foreach (var demand in state.Demands.Values)
demand.ReceivedCount = 0;
state.TurnNumber = 0;
state.Phase = SimPhase.Edit;
changeList.Add(new SimulationStoppedEvent());
}
}
public class ResetLevelCommand : WorldCommand
{
public override void AssertApplicationConditions(BoardState state)
{
// Reset is always valid
}
protected override void DoApply(BoardState state, List<IWorldEvent> changeList)
{
state.ResetFromLevel();
changeList.Add(new LevelResetEvent());
}
}