diff options
Diffstat (limited to 'src/Godosa.Core.Tests/Runs/Scripts/FakeGame.cs')
| -rw-r--r-- | src/Godosa.Core.Tests/Runs/Scripts/FakeGame.cs | 100 |
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); +} |
