SOURCE / PINNED RELEASE
Made of little things.
Wizard Duel
- Release
- 48b4ed933a32…
- Author-recorded commit
- 3ed8fcbbf62c…
- License
- LICENSE
- Author’s source reference
- nostr://npub1ny7l44q0au5xkv3mjgt6zks5f0e2ydefslq4nhh2ve2xypgc2mqqat8qx2/wss%3A%2F%2Fgit.napplet.soy%2F/n-86005d75eb4
Archive hash verified: 4196bfb69e536a86…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import test from 'node:test';
import ts from 'typescript';
async function load(path) {
const source = await readFile(new URL(`../src/game/${path}`, import.meta.url), 'utf8');
const { outputText } = ts.transpileModule(source, {
compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ES2022, verbatimModuleSyntax: true },
});
return import(`data:text/javascript,${encodeURIComponent(outputText)}`);
}
const { createGamepadInput } = await load('gamepad.ts');
const { PadRouter, wizardPadBindings } = await load('gamepad-input.ts');
function pad(index, { axes = [0, 0], pressed = [] } = {}) {
const buttons = Array.from({ length: 17 }, (_, i) => ({ value: pressed.includes(i) ? 1 : 0, pressed: pressed.includes(i) }));
return { index, id: `Xbox ${index}`, mapping: 'standard', connected: true, axes, buttons };
}
function rig() {
let samples = [];
const log = [];
const duel = {
padConnected: [false, false],
keyDown: (code) => log.push(`+${code}`),
keyUp: (code) => log.push(`-${code}`),
pointerStart: () => log.push('start'),
pausePressed: () => log.push('pause'),
padUnplugged: (player) => log.push(`unplug${player + 1}`),
};
const input = createGamepadInput(wizardPadBindings, { read: () => samples, active: () => true });
const router = new PadRouter(duel);
return {
duel,
log,
frame(...pads) {
samples = pads;
router.update(input.poll());
return log.splice(0);
},
};
}
test('first controller is player 1, second is player 2', () => {
const r = rig();
r.frame(pad(3), pad(0));
assert.deepEqual(r.duel.padConnected, [true, true]);
assert.deepEqual(r.frame(pad(3, { pressed: [7] }), pad(0, { pressed: [4] })), ['+Pad1Cast', '+Pad2Cast']);
});
test('hold a trigger and flick the stick: cast plus one rune per flick', () => {
const r = rig();
r.frame(pad(0));
assert.deepEqual(r.frame(pad(0, { pressed: [6] })), ['+Pad1Cast']);
assert.deepEqual(r.frame(pad(0, { pressed: [6], axes: [0.9, 0] })), ['+Pad1Right']);
assert.deepEqual(r.frame(pad(0, { pressed: [6], axes: [0.5, 0] })), [], 'still past the let-go point');
assert.deepEqual(r.frame(pad(0, { pressed: [6], axes: [0, 0] })), ['-Pad1Right']);
assert.deepEqual(r.frame(pad(0, { pressed: [6], axes: [0, -1] })), ['+Pad1Up']);
assert.deepEqual(r.frame(pad(0, { axes: [0, -1] })), ['-Pad1Cast']);
});
test('a diagonal flick only makes one rune', () => {
const r = rig();
r.frame(pad(0));
assert.deepEqual(r.frame(pad(0, { axes: [0.8, 0.6] })), ['+Pad1Right']);
assert.deepEqual(r.frame(pad(0, { axes: [0.75, 0.65] })), []);
});
test('A jumps (acts like up) and starts the duel', () => {
const r = rig();
r.frame(pad(0));
assert.deepEqual(r.frame(pad(0, { pressed: [0] })), ['+Pad1Up', 'start']);
assert.deepEqual(r.frame(pad(0, { pressed: [9] })), ['-Pad1Up', 'pause', 'start']);
});
test('unplugging cancels the spell first, then lets go of held keys', () => {
const r = rig();
r.frame(pad(0), pad(1));
r.frame(pad(0), pad(1, { pressed: [5], axes: [-1, 0] }));
assert.deepEqual(r.frame(pad(0)), ['unplug2', '-Pad2Left', '-Pad2Cast']);
assert.deepEqual(r.duel.padConnected, [true, false]);
r.frame(pad(0), pad(2));
assert.deepEqual(r.duel.padConnected, [true, true], 'a new controller takes the free spot');
});
test('Start pauses once per press', () => {
const r = rig();
r.frame(pad(0));
assert.deepEqual(r.frame(pad(0, { pressed: [9] })), ['pause', 'start']);
assert.deepEqual(r.frame(pad(0, { pressed: [9] })), [], 'holding Start does not flicker the pause');
r.frame(pad(0));
assert.deepEqual(r.frame(pad(0, { pressed: [9] })), ['pause', 'start']);
});
