feat(cli): add Raylib graphical frontend
This commit is contained in:
1 parent
e02d60002f
commit
3659b9ff44
1 file changed
+149
@@ -0,0 +1,149 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using Chip8.Core;
|
||||||
|
using Raylib_cs;
|
||||||
|
|
||||||
|
namespace Chip8.Cli;
|
||||||
|
|
||||||
|
static class RaylibFrontend
|
||||||
|
{
|
||||||
|
static readonly (int chip8Key, KeyboardKey physical)[] keyMap =
|
||||||
|
{
|
||||||
|
(0x1, KeyboardKey.KEY_ONE), (0x2, KeyboardKey.KEY_TWO), (0x3, KeyboardKey.KEY_THREE), (0xC, KeyboardKey.KEY_FOUR),
|
||||||
|
(0x4, KeyboardKey.KEY_Q), (0x5, KeyboardKey.KEY_W), (0x6, KeyboardKey.KEY_E), (0xD, KeyboardKey.KEY_R),
|
||||||
|
(0x7, KeyboardKey.KEY_A), (0x8, KeyboardKey.KEY_S), (0x9, KeyboardKey.KEY_D), (0xE, KeyboardKey.KEY_F),
|
||||||
|
(0xA, KeyboardKey.KEY_Z), (0x0, KeyboardKey.KEY_X), (0xB, KeyboardKey.KEY_C), (0xF, KeyboardKey.KEY_V),
|
||||||
|
};
|
||||||
|
|
||||||
|
static readonly Color background = new(15, 15, 20, 255);
|
||||||
|
static readonly Color foreground = new(180, 240, 200, 255);
|
||||||
|
|
||||||
|
public static void run(Machine machine, Options options)
|
||||||
|
{
|
||||||
|
int width = Machine.displayWidth * options.scale;
|
||||||
|
int height = Machine.displayHeight * options.scale;
|
||||||
|
|
||||||
|
Raylib.InitWindow(width, height, "chip8-sharp");
|
||||||
|
Raylib.SetTargetFPS(60);
|
||||||
|
Raylib.InitAudioDevice();
|
||||||
|
|
||||||
|
AudioStream beep = Raylib.LoadAudioStream(44100, 16, 1);
|
||||||
|
Raylib.PlayAudioStream(beep);
|
||||||
|
var phase = new BeepPhase();
|
||||||
|
|
||||||
|
int frame = 0;
|
||||||
|
while (!Raylib.WindowShouldClose())
|
||||||
|
{
|
||||||
|
pumpInput(machine);
|
||||||
|
machine.runFrame(options.instructionsPerFrame);
|
||||||
|
feedAudio(beep, machine.soundActive, phase);
|
||||||
|
draw(machine, options.scale);
|
||||||
|
|
||||||
|
frame++;
|
||||||
|
if (options.maxFrames > 0 && frame >= options.maxFrames)
|
||||||
|
{
|
||||||
|
saveScreenshot(options.screenshotPath);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Raylib.UnloadAudioStream(beep);
|
||||||
|
Raylib.CloseAudioDevice();
|
||||||
|
Raylib.CloseWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void saveScreenshot(string? path)
|
||||||
|
{
|
||||||
|
if (path is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string fileName = Path.GetFileName(path);
|
||||||
|
Raylib.TakeScreenshot(fileName);
|
||||||
|
|
||||||
|
string produced = Path.Combine(Directory.GetCurrentDirectory(), fileName);
|
||||||
|
string target = Path.GetFullPath(path);
|
||||||
|
if (produced != target)
|
||||||
|
{
|
||||||
|
File.Move(produced, target, overwrite: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pumpInput(Machine machine)
|
||||||
|
{
|
||||||
|
foreach ((int chip8Key, KeyboardKey physical) in keyMap)
|
||||||
|
{
|
||||||
|
if (Raylib.IsKeyDown(physical))
|
||||||
|
{
|
||||||
|
machine.pressKey(chip8Key);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
machine.releaseKey(chip8Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw(Machine machine, int scale)
|
||||||
|
{
|
||||||
|
Raylib.BeginDrawing();
|
||||||
|
Raylib.ClearBackground(background);
|
||||||
|
|
||||||
|
for (int y = 0; y < Machine.displayHeight; y++)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < Machine.displayWidth; x++)
|
||||||
|
{
|
||||||
|
if (machine.display[y * Machine.displayWidth + x])
|
||||||
|
{
|
||||||
|
Raylib.DrawRectangle(x * scale, y * scale, scale, scale, foreground);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Raylib.EndDrawing();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void feedAudio(AudioStream beep, bool active, BeepPhase phase)
|
||||||
|
{
|
||||||
|
const int sampleRate = 44100;
|
||||||
|
const int toneHz = 440;
|
||||||
|
const short amplitude = 4000;
|
||||||
|
int samplesPerHalfPeriod = sampleRate / (toneHz * 2);
|
||||||
|
|
||||||
|
while (Raylib.IsAudioStreamProcessed(beep))
|
||||||
|
{
|
||||||
|
var buffer = new short[512];
|
||||||
|
for (int n = 0; n < buffer.Length; n++)
|
||||||
|
{
|
||||||
|
if (!active)
|
||||||
|
{
|
||||||
|
buffer[n] = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer[n] = phase.high ? amplitude : (short)-amplitude;
|
||||||
|
phase.counter++;
|
||||||
|
if (phase.counter >= samplesPerHalfPeriod)
|
||||||
|
{
|
||||||
|
phase.counter = 0;
|
||||||
|
phase.high = !phase.high;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
fixed (short* samples = buffer)
|
||||||
|
{
|
||||||
|
Raylib.UpdateAudioStream(beep, samples, buffer.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class BeepPhase
|
||||||
|
{
|
||||||
|
public int counter;
|
||||||
|
public bool high;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user