Files
waving-hands/src/lib/game/duel.test.ts
T
Eric WagonerandClaude Opus 5.5 ee630276b3 Easier to make a move, and plainer words around the game
From feedback on the BoardGameGeek thread. The hands are framed and
labelled as yours, so they no longer read as sitting under your
opponent's columns. A move can be typed: the first letter goes to the
left hand, the second to the right, K stabs, a dash rests, Backspace
takes one back and Enter seals. The chosen pair, what it will do, any
warning and the button now sit in one row that stays in view. A clap
with one hand is warned of before the turn is sealed. The button says
what the player does: Seal the turn. The chronicle reads oldest first
and scrolls to the newest line. Headings, tabs and notes lose the
label-and-colon construction and the abstract headings; the chronicle's
imagery stays.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141G6xqLeNRYEtviLWSB5Up
2026-10-10 18:29:49 -04:00

77 lines
2.9 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { Duel } from './duel.svelte';
import { resolveTurn } from './resolve';
import { createGame, type GameState } from './state';
import type { Gesture } from './spells';
import { noop, play } from './test-helpers';
/** A is one gesture from a lightning bolt and has just been struck by amnesia. */
function amnesiaBeforeLightning(): GameState {
let s = createGame({ A: 'You', B: 'Foe' }, 1);
const left = 'DFFD';
const foe = '-DPP';
for (let i = 0; i < 4; i++) {
s = play(s, { left: left[i] as Gesture }, { left: foe[i] as Gesture, casts: i === 3 ? [{ hand: 'left', spellId: 'amnesia' }] : [] });
}
expect(s.wizards.A.constraints.amnesia).toBe(true);
return s;
}
describe('the duel store', () => {
it('casts the spell a forced repeat completes, without a click', () => {
const duel = new Duel(amnesiaBeforeLightning());
expect(duel.forced.left).toBe('D');
expect(duel.completions.main.left.map((c) => c.spell.id)).toContain('lightning_bolt');
expect(duel.sets.main.left.spellId).toBe('lightning_bolt');
expect(duel.ready).toBe(true);
duel.reveal();
expect(duel.foe.hp).toBe(10);
});
});
describe('threats at a crowded table', () => {
it('keys each opponent apart when two build the same spell with the same hand', () => {
let s = createGame({ A: 'You', B: 'Bryn', C: 'Cass' }, 1);
s = resolveTurn(s, { A: noop(), B: { ...noop(), left: 'S' }, C: { ...noop(), left: 'S' } });
const duel = new Duel(s);
const missiles = duel.threatsNow.filter((t) => t.spell.id === 'missile');
expect(missiles.map((t) => t.who).sort()).toEqual(['B', 'C']);
const keys = duel.threats.map((t) => duel.threatKey(t));
expect(new Set(keys).size).toBe(keys.length);
});
it('labels targets with health and condition', () => {
let s = createGame({ A: 'You', B: 'Bryn', C: 'Cass' }, 1);
s = resolveTurn(s, { A: noop(), B: noop(), C: noop() });
s.wizards.C.hp = 8;
s.wizards.C.invisibleTurns = 2;
const duel = new Duel(s);
expect(duel.targetLabel('C')).toBe('Cass · 8 HP · invisible');
});
});
describe('typing a move', () => {
it('fills the left hand, then the right, and backspace takes the last one back', () => {
const duel = new Duel(createGame({ A: 'You', B: 'Foe' }, 1));
expect(duel.typeGesture('S')).toBe(true);
expect(duel.typeGesture('P')).toBe(true);
expect(duel.sets.main.left.gesture).toBe('S');
expect(duel.sets.main.right.gesture).toBe('P');
expect(duel.typeGesture('W')).toBe(false);
duel.untype();
expect(duel.sets.main.right.gesture).toBeNull();
expect(duel.sets.main.left.gesture).toBe('S');
});
it('warns of a clap made with one hand before the turn is sealed', () => {
const duel = new Duel(createGame({ A: 'You', B: 'Foe' }, 1));
duel.typeGesture('C');
duel.typeGesture('W');
expect(duel.loneClap).toBe(true);
expect(duel.ready).toBe(true);
duel.untype();
duel.typeGesture('C');
expect(duel.loneClap).toBe(false);
});
});