Files
zfxaction25/RobotAndDonkey.Game/Execution/GameRuntime.cs
2026-04-19 00:43:27 +02:00

139 lines
4.0 KiB
C#

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<Result> 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<GameEvent>? 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;
}