using System; using Luneraria.Core; namespace Luneraria.View { /// /// Runs a bot match on the host (U2C/U2D) against wall-clock time: planning (the six bots plan /// through the command interface), combat in fixed steps, a pause after the settlement, the /// next round, until an Ancient falls. Only the core decides; time only paces the calls, so /// the sequence of core calls is the one of LuneTrace.RunCore at every frame rate. /// public sealed class LuneMatchRunner { public enum Stage { Planning, Combat, Settled, Over } public readonly LuneMatch Match; public readonly LuneClock Clock; public readonly string Profile; public LuneTrace Trace; public double PlanningSeconds = 1.0; public double SettledSeconds = 2.0; public int MaxRound = 60; public Stage Current { get; private set; } public int StepsThisFrame { get; private set; } public event Action CombatStarted; public event Action Settled; public event Action Finished; double _stageTime; public LuneMatchRunner(MatchOptions options, int startRound = 1, int extraGold = 0, string profile = "") { Match = NewMatch(options, startRound, extraGold); Clock = new LuneClock(Match.Balance.SimStep); Profile = profile; EnterPlanning(); } /// A match with the public fixtures the golden scenarios use (start round, gold). public static LuneMatch NewMatch(MatchOptions options, int startRound, int extraGold) { var match = new LuneMatch(); match.Setup(options); if (startRound > 1) match.RoundNumber = startRound; if (extraGold != 0) foreach (Player player in match.Players) player.Draft.Gold += extraGold; return match; } void EnterPlanning() { Current = Stage.Planning; _stageTime = 0.0; for (int slot = 0; slot < 6; slot++) LuneBot.Plan(Match, slot, Profile); } /// Advances by simulated seconds (frame time times match speed). public void Advance(double seconds) { StepsThisFrame = 0; switch (Current) { case Stage.Planning: _stageTime += seconds; if (_stageTime >= PlanningSeconds) { Match.BeginCombat(); Clock.Reset(); Current = Stage.Combat; CombatStarted?.Invoke(Match.Combat); } break; case Stage.Combat: StepsThisFrame = Clock.Advance(seconds, StepOnce); if (Match.Phase != Phase.Combat) EndCombat(); break; case Stage.Settled: _stageTime += seconds; if (_stageTime >= SettledSeconds) EnterPlanning(); break; } } bool StepOnce() { Match.StepCombat(); Trace?.RecordStep(Match.Combat); return Match.Phase == Phase.Combat; } void EndCombat() { Trace?.RecordSettlement(Match); Settled?.Invoke(Match.LastSettlement); _stageTime = 0.0; if (Match.Phase == Phase.Over || Match.RoundNumber > MaxRound) { Current = Stage.Over; Finished?.Invoke(Match.Winner); } else { Current = Stage.Settled; } } } }