using RobotAndDonkey.Game.Execution.Commands; using RobotAndDonkey.Game.Execution.Requests; using RobotAndDonkey.Game.Execution.Results; using RobotAndDonkey.Game.GameState; using RobotAndDonkey.Game.Intents; using System; using System.Collections.Immutable; using System.Threading; namespace RobotAndDonkey.Game.Execution; public sealed record GameEvent(long Sequence, DateTimeOffset Time, object Payload); public sealed record NextStepIssued(Request Step); public sealed record StepApplied(ImmutableArray Results); public sealed record StepRejected(Guid RequestId, string Reason); public sealed record StateChanged; public sealed record GameOver(string Reason); public sealed class GameRuntime { public event EventHandler? Published; public void Start(CoreLoop coreLoop) { lock (m_Gate) { m_CoreLoop = coreLoop; Publish(new StateChanged()); IssueNextStepLocked(); } } public void Submit(Command command, CancellationToken ct = default) { lock (m_Gate) { if (m_CoreLoop == null) { Publish(new StepRejected(command.RequestId, "No core loop started.")); return; } if (m_ExpectedRequest is null) { Publish(new StepRejected(command.RequestId, "No step expected (game over or not started).")); return; } if (m_ExpectedRequest.RequestId != command.RequestId) { Publish(new StepRejected(command.RequestId, "Command does not match the expected step.")); return; } if (!m_ExpectedRequest.IsCommandCompatible(command)) { Publish(new StepRejected(command.RequestId, "Command not compatible with the current step.")); return; } var results = command.Execute(m_CoreLoop, false, false); Publish(new StepApplied([..results])); Publish(new StateChanged()); IssueNextStepLocked(); } } private void IssueNextStepLocked() { m_ExpectedRequest = NextRequest(); if (m_ExpectedRequest is null) Publish(new GameOver("No more steps.")); else Publish(new NextStepIssued(m_ExpectedRequest)); } private Request? NextRequest() { var coreLoop = m_CoreLoop!; switch (coreLoop.RunPhase) { case ERunPhase.Init: { const ERunPhase firstPhase = ERunPhase.ExecuteProgram; coreLoop.ResetShop(); new EnterPreview().Run(Guid.Empty, coreLoop, [], []); // DEBUG // const ERunPhase firstPhase = ERunPhase.DrawGlitch; coreLoop.RunPhase = firstPhase; goto case firstPhase; } case ERunPhase.DrawGlitch: { return DrawGlitchRequest.Create(coreLoop); } case ERunPhase.Improve: { return ImproveRequest.Create(coreLoop); } case ERunPhase.Gamble: { return GambleRequest.Create(coreLoop); } case ERunPhase.BufferOverflow: { return BufferOverflowRequest.Create(coreLoop); } case ERunPhase.ExecuteProgram: { return ExecuteProgramRequest.Create(coreLoop); } case ERunPhase.Scoring: { return ScoringRequest.Create(coreLoop); } } return null; } private void Publish(object payload) { Published?.Invoke(this, new(Interlocked.Increment(ref m_Sequence), DateTimeOffset.UtcNow, payload)); } private CoreLoop? m_CoreLoop; private readonly Lock m_Gate = new(); private Request? m_ExpectedRequest; private long m_Sequence; }