mirror of
https://github.com/henrikpersson/potatis.git
synced 2025-04-02 10:32:09 -04:00
124 lines
No EOL
2.9 KiB
TypeScript
124 lines
No EOL
2.9 KiB
TypeScript
import wasmInit, { NesWasm, KeyState } from "nes-wasm";
|
|
|
|
const keymap: { [key: string]: number } = {
|
|
['l']: 7,
|
|
['k']: 6,
|
|
[' ']: 5,
|
|
['Enter']: 4,
|
|
['w']: 3,
|
|
['s']: 2,
|
|
['a']: 1,
|
|
['d']: 0,
|
|
};
|
|
|
|
export class BrowserNes {
|
|
nes: NesWasm;
|
|
mem: WebAssembly.Memory;
|
|
ctx: CanvasRenderingContext2D;
|
|
frame_ready: boolean;
|
|
keyboard: KeyState[];
|
|
killed: boolean;
|
|
req_frame: number;
|
|
delay_ms: number;
|
|
|
|
constructor(ctx: CanvasRenderingContext2D, mem: WebAssembly.Memory, bincart: Uint8Array) {
|
|
this.nes = NesWasm.new(this, bincart);
|
|
this.ctx = ctx;
|
|
this.mem = mem;
|
|
this.frame_ready = false;
|
|
this.keyboard = new Array(8).fill(KeyState.None);
|
|
|
|
document.addEventListener('keydown', (ev) => {
|
|
let btn = keymap[ev.key];
|
|
if (btn != null) {
|
|
this.keyboard[btn] = KeyState.Pressed;
|
|
ev.preventDefault();
|
|
}
|
|
})
|
|
|
|
document.addEventListener('keyup', (ev) => {
|
|
let btn = keymap[ev.key];
|
|
if (btn != null) {
|
|
this.keyboard[btn] = KeyState.Released;
|
|
|
|
// LOL
|
|
setTimeout(() => {
|
|
this.keyboard[btn] = KeyState.None;
|
|
}, 20);
|
|
ev.preventDefault();
|
|
}
|
|
})
|
|
}
|
|
|
|
poll_keyboard(ptr: number) {
|
|
let state = new Uint8ClampedArray(this.mem.buffer, ptr, 8);
|
|
state.set(this.keyboard)
|
|
}
|
|
|
|
on_frame_ready(frame_ptr: number, len: number) {
|
|
const buf = new Uint8ClampedArray(this.mem.buffer, frame_ptr, len);
|
|
this.ctx.putImageData(new ImageData(buf, 240, 224), 0, 0);
|
|
this.frame_ready = true;
|
|
}
|
|
|
|
delay(millis: number) {
|
|
this.delay_ms = millis;
|
|
}
|
|
|
|
loop() {
|
|
const inner = () => {
|
|
this.frame_ready = false;
|
|
while (!this.frame_ready) {
|
|
this.nes.tick();
|
|
}
|
|
|
|
if (!this.killed) {
|
|
if (this.delay_ms != 0) {
|
|
setTimeout(() => {
|
|
this.delay_ms = 0;
|
|
this.req_frame = requestAnimationFrame(inner);
|
|
}, this.delay_ms);
|
|
}
|
|
else {
|
|
this.req_frame = requestAnimationFrame(inner);
|
|
}
|
|
}
|
|
}
|
|
|
|
this.req_frame = requestAnimationFrame(inner);
|
|
}
|
|
|
|
kill() {
|
|
cancelAnimationFrame(this.req_frame);
|
|
this.killed = true;
|
|
}
|
|
}
|
|
|
|
const c = document.getElementById('c');
|
|
const ctx = c.getContext('2d');
|
|
const wasm = await wasmInit();
|
|
let instance = new BrowserNes(ctx, wasm.memory, []); // empty bin == nestest
|
|
instance.loop();
|
|
c.focus();
|
|
|
|
document.getElementById('file_field').addEventListener('change', (input: any) => {
|
|
const file: File = input.target.files[0]
|
|
const reader = new FileReader()
|
|
reader.onload = async () => {
|
|
if (instance != null) {
|
|
instance.kill();
|
|
instance = null;
|
|
}
|
|
|
|
const bincart = new Uint8Array(reader.result)
|
|
instance = new BrowserNes(ctx, wasm.memory, bincart);
|
|
instance.loop();
|
|
c.focus();
|
|
}
|
|
reader.onerror = () => {
|
|
alert(reader.error)
|
|
}
|
|
if (file) {
|
|
reader.readAsArrayBuffer(file)
|
|
}
|
|
}) |