Back to Wizard Duel
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.

tests/gamepad.test.mjs
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']);
});