Bundles in-flight work on the campaign/missions system (CampaignDef, MissionDef, TerrainPatch, TransformerDef, MissionChecker, CampaignLoader, FlavorBanner, transformer rules), plan files, and matching tests. Baseline commit so the upcoming automation testing harness lands on a clean tree.
146 lines
5.3 KiB
C#
146 lines
5.3 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: 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 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 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
|
|
);
|
|
}
|
|
}
|