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 changeList) { state.TurnNumber++; changeList.Add(new TurnStartedEvent(state.TurnNumber)); // Sub-phase 1: PRODUCTION ExecuteProduction(state, changeList); // Sub-phase 2: TRANSFORMATION (convert accumulated input → output) ExecuteTransformation(state, changeList); // Sub-phase 3: TRANSFERS var transferEvents = TransferResolver.ResolveTransfers(state); changeList.AddRange(transferEvents); // Sub-phase 4: MOVEMENT ExecuteMovement(state, changeList); // Sub-phase 4b: RECURRING DEMAND CONSUMPTION ExecuteRecurringConsumption(state, changeList); // Sub-phase 5: COLLISION RESOLUTION var collisions = CollisionResolver.ResolveCollisions(state.Pieces); foreach (var (survivor, destroyed, cell) in collisions) { foreach (var victim in destroyed) { state.Pieces.Remove(victim); victim.Cargo = null; // Return piece to stock instead of destroying permanently state.RemainingStock[victim.Kind] = state.RemainingStock.GetValueOrDefault(victim.Kind) + 1; changeList.Add(new PieceReturnedToStockEvent( state.TurnNumber, victim.Id, victim.Kind, survivor?.Id, cell)); } } // Auto-pause on collision if (collisions.Count > 0) { state.Phase = SimPhase.Paused; changeList.Add(new SimulationPausedEvent()); } // Check mission completion if (MissionChecker.AllCurrentDemandsMet(state) && state.Demands.Count > 0) { var campaign = state.Campaign; var missionIndex = campaign?.CurrentMissionIndex ?? 0; campaign?.CompletedMissions.Add(missionIndex); changeList.Add(new MissionCompleteEvent(state.TurnNumber, missionIndex)); // Auto-advance to next mission if available (campaign mode) if (campaign != null && !campaign.IsLastMission) { AdvanceToNextMission(state, campaign, changeList); // Phase stays Running — simulation continues } else { // Last mission or legacy mode — pause state.Phase = SimPhase.MissionComplete; } } changeList.Add(new TurnEndedEvent(state.TurnNumber)); } private static void AdvanceToNextMission(BoardState state, CampaignState campaign, List changeList) { campaign.CurrentMissionIndex++; var mission = campaign.CurrentMission; var oldWidth = state.Width; var oldHeight = state.Height; state.ApplyTerrainPatch(mission.TerrainPatch, campaign.CurrentMissionIndex); if (state.Width != oldWidth || state.Height != oldHeight) changeList.Add(new TerrainExpandedEvent(state.Width, state.Height, mission.TerrainPatch.Cells)); state.AddStock(mission.Stock); foreach (var kind in mission.UnlockedPieces) { campaign.AvailablePieceKinds.Add(kind); changeList.Add(new PieceUnlockedEvent(kind, 1)); } foreach (var upgrade in mission.UnlockedLevels) { campaign.AvailableLevels.Add(upgrade); changeList.Add(new PieceUnlockedEvent(upgrade.Kind, upgrade.Level)); } changeList.Add(new MissionStartedEvent(campaign.CurrentMissionIndex, state.Width, state.Height)); } private static void ExecuteMovement(BoardState state, List changeList) { var moves = state.Pieces.Select(p => (piece: p, from: p.CurrentCell, to: p.TargetCell)).ToList(); 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 ExecuteTransformation(BoardState state, List changeList) { foreach (var (pos, transformer) in state.Transformers) { var inputBuffer = state.TransformerInputBuffers[pos]; if (inputBuffer >= transformer.InputRequired) { state.TransformerInputBuffers[pos] = inputBuffer - transformer.InputRequired; state.TransformerOutputBuffers[pos] += transformer.OutputAmount; changeList.Add(new CargoConvertedEvent( state.TurnNumber, pos, transformer.InputCargo, transformer.OutputCargo, transformer.OutputAmount)); } } } private static void ExecuteRecurringConsumption(BoardState state, List changeList) { foreach (var demand in state.Demands.Values) { if (!demand.IsRecurring) continue; // Consume from buffer var consumed = Math.Min(demand.Buffer, demand.Definition.ConsumptionPerTurn); demand.Buffer -= consumed; var short_ = demand.Buffer <= 0; if (short_) { if (!demand.InShortage) { demand.InShortage = true; changeList.Add(new DemandShortageStartedEvent( state.TurnNumber, demand.Position, demand.Name)); } demand.SustainedTurns = 0; } else { if (demand.InShortage) { demand.InShortage = false; changeList.Add(new DemandShortageClearedEvent( state.TurnNumber, demand.Position, demand.Name)); } demand.SustainedTurns++; } } } private static void ExecuteProduction(BoardState state, List 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 ); } }