feat(replay): add script runner
This commit is contained in:
1 parent
44fbd79c04
commit
ae9f623b49
2 files changed
+152
No files matched your search
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
|
||||
namespace Chip8.Replay;
|
||||
|
||||
public class ScriptException : Exception
|
||||
{
|
||||
public int line { get; }
|
||||
|
||||
public ScriptException(int line, string message)
|
||||
: base("line " + line + ": " + message)
|
||||
{
|
||||
this.line = line;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ScriptParseException : ScriptException
|
||||
{
|
||||
public ScriptParseException(int line, string message)
|
||||
: base(line, message)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ScriptAssertionException : ScriptException
|
||||
{
|
||||
public int frame { get; }
|
||||
|
||||
public ScriptAssertionException(int line, int frame, string message)
|
||||
: base(line, "frame " + frame + ": " + message)
|
||||
{
|
||||
this.frame = frame;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
using Chip8.Core;
|
||||
|
||||
namespace Chip8.Replay;
|
||||
|
||||
public sealed class ScriptRunner
|
||||
{
|
||||
public Machine machine { get; }
|
||||
public int instructionsPerFrame { get; }
|
||||
public int currentFrame { get; private set; }
|
||||
public KeyMask heldKeys { get; private set; }
|
||||
public int assertionsPassed { get; private set; }
|
||||
|
||||
readonly InputMovie? record;
|
||||
int activeLine;
|
||||
|
||||
public ScriptRunner(Machine machine, int instructionsPerFrame, InputMovie? record = null)
|
||||
{
|
||||
this.machine = machine;
|
||||
this.instructionsPerFrame = instructionsPerFrame;
|
||||
this.record = record;
|
||||
heldKeys = KeyMask.empty;
|
||||
}
|
||||
|
||||
public static ScriptRunner forRom(byte[] rom, Platform platform, Quirks? quirks, int instructionsPerFrame, int? randomSeed = 1)
|
||||
{
|
||||
var machine = new Machine(quirks, randomSeed, platform);
|
||||
machine.loadRom(rom);
|
||||
return new ScriptRunner(machine, instructionsPerFrame);
|
||||
}
|
||||
|
||||
public void run(InputScript script)
|
||||
{
|
||||
foreach (ScriptCommand command in script.commands)
|
||||
{
|
||||
activeLine = command.sourceLine;
|
||||
command.execute(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void press(int key)
|
||||
{
|
||||
heldKeys = heldKeys.withPressed(key);
|
||||
}
|
||||
|
||||
public void release(int key)
|
||||
{
|
||||
heldKeys = heldKeys.withReleased(key);
|
||||
}
|
||||
|
||||
public void advanceFrames(int count)
|
||||
{
|
||||
for (int frame = 0; frame < count; frame++)
|
||||
{
|
||||
heldKeys.applyTo(machine.keypad);
|
||||
record?.recordFrame(currentFrame, heldKeys);
|
||||
machine.runFrame(instructionsPerFrame);
|
||||
currentFrame++;
|
||||
}
|
||||
|
||||
record?.extendLength(currentFrame);
|
||||
}
|
||||
|
||||
public void advanceTo(int frame)
|
||||
{
|
||||
if (frame < currentFrame)
|
||||
{
|
||||
throw new ScriptException(activeLine,
|
||||
"cannot schedule frame " + frame + ", timeline is already at frame " + currentFrame);
|
||||
}
|
||||
advanceFrames(frame - currentFrame);
|
||||
}
|
||||
|
||||
public void checkCompare(ScriptCommand command, string name, int actual, CompareOp op, int expected)
|
||||
{
|
||||
if (CompareOps.evaluate(op, actual, expected))
|
||||
{
|
||||
assertionsPassed++;
|
||||
return;
|
||||
}
|
||||
|
||||
throw new ScriptAssertionException(command.sourceLine, currentFrame,
|
||||
"expected " + name + " " + CompareOps.symbol(op) + " " + expected + " but was " + actual);
|
||||
}
|
||||
|
||||
public void checkPixel(AssertPixelCommand command)
|
||||
{
|
||||
FramebufferSnapshot snapshot = FramebufferSnapshot.capture(machine);
|
||||
if (command.x < 0 || command.y < 0 || command.x >= snapshot.width || command.y >= snapshot.height)
|
||||
{
|
||||
throw new ScriptAssertionException(command.sourceLine, currentFrame,
|
||||
"pixel (" + command.x + "," + command.y + ") is outside the "
|
||||
+ snapshot.width + "x" + snapshot.height + " framebuffer");
|
||||
}
|
||||
|
||||
bool lit = snapshot.isLit(command.x, command.y);
|
||||
if (lit == command.expectedLit)
|
||||
{
|
||||
assertionsPassed++;
|
||||
return;
|
||||
}
|
||||
|
||||
throw new ScriptAssertionException(command.sourceLine, currentFrame,
|
||||
"expected pixel (" + command.x + "," + command.y + ") to be "
|
||||
+ (command.expectedLit ? "on" : "off") + " but it was " + (lit ? "on" : "off"));
|
||||
}
|
||||
|
||||
public void checkExited(AssertExitedCommand command)
|
||||
{
|
||||
if (machine.exited == command.expectedExited)
|
||||
{
|
||||
assertionsPassed++;
|
||||
return;
|
||||
}
|
||||
|
||||
throw new ScriptAssertionException(command.sourceLine, currentFrame,
|
||||
"expected machine to be " + (command.expectedExited ? "exited" : "running")
|
||||
+ " but exited=" + machine.exited);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user