Chessistics/chessistics-engine/Simulation/TurnExecutor.cs
Samuel Bouchet a7280b1a5a Overhaul turn mechanics, collision destruction, and visual animations
- New turn order: produce -> transfer -> move -> collision resolution
- Collisions now destroy weaker pieces (status > level > mutual destruction)
  instead of halting the simulation. SimPhase.Collision removed.
- Add piece Level property (all level 1 in proto, prepared for future)
- Production fires every turn (interval concept removed), buffer = Amount
  (default 1, future 2-4), leftovers overwritten each turn
- Transfer tiebreaker: status > level > clockwise direction (alternating
  even/odd turns in y-up coords), replaces distance-to-production
- Demands always accept matching cargo even when already satisfied
- TurnNumber added to all turn events for animation grouping
- Simultaneous animations: produce flash, cargo slide, parallel piece moves
- Camera centering fix + middle-click pan
- GDD updated with new rules + lore section added

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 21:44:12 +02:00

85 lines
2.9 KiB
C#

using Chessistics.Engine.Events;
using Chessistics.Engine.Model;
using Chessistics.Engine.Rules;
namespace Chessistics.Engine.Simulation;
public static class TurnExecutor
{
public static void ExecuteTurn(BoardState state, List<IWorldEvent> changeList)
{
state.TurnNumber++;
changeList.Add(new TurnStartedEvent(state.TurnNumber));
// Sub-phase 1: PRODUCTION
ExecuteProduction(state, changeList);
// Sub-phase 2: TRANSFERS
var transferEvents = TransferResolver.ResolveTransfers(state);
changeList.AddRange(transferEvents);
// Sub-phase 3: MOVEMENT
ExecuteMovement(state, changeList);
// Sub-phase 4: COLLISION RESOLUTION
var collisions = CollisionResolver.ResolveCollisions(state.Pieces);
foreach (var (survivor, destroyed, cell) in collisions)
{
foreach (var victim in destroyed)
{
state.Pieces.Remove(victim);
state.DestroyedPieces.Add(victim);
victim.Cargo = null;
changeList.Add(new PieceDestroyedEvent(
state.TurnNumber, victim.Id, survivor?.Id, cell));
}
}
// Check victory / defeat
if (VictoryChecker.AllDemandsMet(state))
{
state.Phase = SimPhase.Victory;
changeList.Add(new VictoryEvent(state.TurnNumber, ComputeMetrics(state)));
}
else if (VictoryChecker.AnyDeadlineExpired(state))
{
state.Phase = SimPhase.Defeat;
foreach (var demand in VictoryChecker.GetExpiredDemands(state))
changeList.Add(new DeadlineExpiredEvent(state.TurnNumber, demand.Position, demand.Name));
}
changeList.Add(new TurnEndedEvent(state.TurnNumber));
}
private static void ExecuteMovement(BoardState state, List<IWorldEvent> changeList)
{
// Compute all targets first (simultaneous movement)
var moves = state.Pieces.Select(p => (piece: p, from: p.CurrentCell, to: p.TargetCell)).ToList();
// Apply all moves
foreach (var (piece, from, to) in moves)
{
piece.CurrentCell = to;
state.OccupiedCells.Add(to);
changeList.Add(new PieceMovedEvent(state.TurnNumber, piece.Id, from, to));
}
}
private static void ExecuteProduction(BoardState state, List<IWorldEvent> changeList)
{
foreach (var (pos, prod) in state.Productions)
{
state.ProductionBuffers[pos] = prod.Amount;
changeList.Add(new CargoProducedEvent(state.TurnNumber, pos, prod.Cargo));
}
}
private static Metrics ComputeMetrics(BoardState state)
{
return new Metrics(
PiecesUsed: state.Pieces.Count + state.DestroyedPieces.Count,
TurnsTaken: state.TurnNumber,
CellsOccupied: state.OccupiedCells.Count
);
}
}