aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/src/Godosa.Core.Tests/Runs/Scripts/FakeGame.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Godosa.Core.Tests/Runs/Scripts/FakeGame.cs')
-rw-r--r--src/Godosa.Core.Tests/Runs/Scripts/FakeGame.cs100
1 files changed, 100 insertions, 0 deletions
diff --git a/src/Godosa.Core.Tests/Runs/Scripts/FakeGame.cs b/src/Godosa.Core.Tests/Runs/Scripts/FakeGame.cs
new file mode 100644
index 0000000..8318148
--- /dev/null
+++ b/src/Godosa.Core.Tests/Runs/Scripts/FakeGame.cs
@@ -0,0 +1,100 @@
+using Godosa.Core.Runs.Scripts;
+
+namespace Godosa.Core.Tests.Runs.Scripts;
+
+/// <summary>Scriptable game for runner tests: probes are a dictionary, actions are lambdas, input is recorded with the
+/// frame it arrived on.</summary>
+public sealed class FakeGame : IRunGame, IInputRouter
+{
+ public long Frame { get; private set; }
+ public List<(long Frame, InputEvent Event)> Sent { get; } = [];
+ public Dictionary<string, Func<IReadOnlyList<string>, RunValue>> ProbeFns { get; } = new();
+ public Dictionary<string, IRunAction> ActionTable { get; } = new();
+ public Dictionary<string, (int X, int Y)> Objects { get; } = new();
+ public Dictionary<string, long> ConstantTable { get; } = new() { ["accepted"] = 2 };
+ public Action<long>? OnAdvance { get; set; }
+ public int State { get; set; }
+ public FakeRng? RngImpl { get; set; } = new();
+
+ public string Name => "fake";
+ public string ContentVersion { get; set; } = "c1";
+ public IInputRouter Input => this;
+ public IReadOnlyDictionary<string, IProbe> Probes => ProbeFns.ToDictionary(p => p.Key, p => (IProbe)new FnProbe(p.Value));
+ public IReadOnlyDictionary<string, long> Constants => ConstantTable;
+ public IReadOnlyDictionary<string, ILocator> Locators => new Dictionary<string, ILocator> { ["obj"] = new ObjLocator(this) };
+ public IReadOnlyDictionary<string, IRunAction> Actions => ActionTable;
+ public IRunRng? Rng => RngImpl;
+ public bool SupportsSnapshot { get; set; } = true;
+ public bool RestoreDeferred { get; set; }
+
+ public Action<InputEvent>? OnSend { get; set; }
+ public bool RestoreThrows { get; set; }
+ public string? Refusal { get; set; }
+ public string? SnapshotRefusal() => Refusal;
+
+ public void Send(InputEvent e)
+ {
+ OnSend?.Invoke(e);
+ Sent.Add((Frame, e));
+ }
+
+ public byte[] SnapshotSave() => BitConverter.GetBytes(State).Concat(BitConverter.GetBytes(Frame)).ToArray();
+
+ public void SnapshotRestore(byte[] data)
+ {
+ if (RestoreThrows)
+ throw new IOException("restore broke");
+ State = BitConverter.ToInt32(data, 0);
+ Frame = BitConverter.ToInt64(data, 4);
+ }
+
+ public int Digests { get; private set; }
+ public ulong Digest() { Digests++; return (ulong)State * 1_000_003UL + (ulong)Frame; }
+ public string Dump() => $"state={State} frame={Frame}";
+
+ public void Advance()
+ {
+ Frame++;
+ OnAdvance?.Invoke(Frame);
+ }
+
+ private sealed class FnProbe(Func<IReadOnlyList<string>, RunValue> fn) : IProbe
+ {
+ public int MinArgs => 0;
+ public int MaxArgs => 4;
+ public RunValue Eval(IReadOnlyList<string> args) => fn(args);
+ }
+
+ private sealed class ObjLocator(FakeGame g) : ILocator
+ {
+ public LocatorResult Locate(IReadOnlyList<string> args) =>
+ g.Objects.TryGetValue(args[0], out var p) ? LocatorResult.Ok(p.X, p.Y) : LocatorResult.Fail($"no object {args[0]} on screen");
+ }
+}
+
+public sealed class FakeRng : IRunRng
+{
+ public List<string> Calls { get; } = [];
+ public void Seed(long seed) => Calls.Add($"seed {seed}");
+ public void ForceNext(string kind, long value) => Calls.Add($"next {kind} {value}");
+}
+
+/// <summary>An action: optional input plan, then Poll() answers from a function of the frames polled so far.</summary>
+public sealed class FakeAction(VerbKind kind, Func<IReadOnlyList<string>, ActionStart> begin, Func<int, StepStatus>? poll = null,
+ int min = 0, int max = 4) : IRunAction
+{
+ private int _polls;
+ public VerbKind Kind => kind;
+ public int MinArgs => min;
+ public int MaxArgs => max;
+ public List<IReadOnlyList<string>> Begun { get; } = [];
+
+ public ActionStart Begin(IReadOnlyList<string> args)
+ {
+ Begun.Add(args);
+ _polls = 0;
+ return begin(args);
+ }
+
+ public StepStatus Poll() => (poll ?? (_ => StepStatus.Done))(++_polls);
+}