Chessistics/chessistics-engine/Simulation/TurnExecutor.cs

184 lines
6.5 KiB
C#
Raw Permalink Normal View History

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: 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<IWorldEvent> 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<IWorldEvent> 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<IWorldEvent> 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<IWorldEvent> 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<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
);
}
}