From 3716f49bbb6bf2fe4337c7c8a8976620e3ae8a4d Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Tue, 22 Sep 2026 15:48:47 -0400 Subject: [PATCH] Players can choose their own name An inline editor in the standfirst; the name is remembered in the browser, applied to the current duel, and used for every duel after. The opponent is drawn from the list avoiding the player's name. Co-Authored-By: Claude Fable 5.1 --- src/lib/game/duel.svelte.ts | 34 +++++++++++++++-- src/routes/+page.svelte | 76 +++++++++++++++++++++++++++++++++++-- 2 files changed, 103 insertions(+), 7 deletions(-) diff --git a/src/lib/game/duel.svelte.ts b/src/lib/game/duel.svelte.ts index ef2b612..833d020 100644 --- a/src/lib/game/duel.svelte.ts +++ b/src/lib/game/duel.svelte.ts @@ -98,9 +98,21 @@ function blankSets(): Record> { return { main: { left: blankDraft(), right: blankDraft() }, haste: { left: blankDraft(), right: blankDraft() } }; } -function pickNames(): { A: string; B: string } { - const pool = [...NAMES]; - const a = pool.splice(Math.floor(Math.random() * pool.length), 1)[0]; +const NAME_KEY = 'wh:name'; +export const NAME_MAX = 24; + +function readName(): string { + try { + return (localStorage.getItem(NAME_KEY) ?? '').trim().slice(0, NAME_MAX); + } catch { + return ''; + } +} + +/** The player's chosen name if they have one, and an opponent who is not called the same. */ +function pickNames(player = readName()): { A: string; B: string } { + const pool = NAMES.filter((n) => n.toLowerCase() !== player.toLowerCase()); + const a = player || pool.splice(Math.floor(Math.random() * pool.length), 1)[0]; const b = pool[Math.floor(Math.random() * pool.length)]; return { A: a, B: b }; } @@ -227,6 +239,22 @@ export class Duel { } } + /** Rename the player for this duel and every duel after it. An empty name goes back to a drawn one. */ + setName(raw: string): void { + const name = raw.trim().slice(0, NAME_MAX); + try { + if (name) localStorage.setItem(NAME_KEY, name); + else localStorage.removeItem(NAME_KEY); + } catch { + // The name still applies to this duel. + } + const next = name || pickNames('').A; + if (next.toLowerCase() === this.foe.name.toLowerCase()) { + this.state.wizards[FOE].name = pickNames(next).B; + } + this.state.wizards[YOU].name = next; + } + /** Write the duel and the half-written turn to local storage. Reads everything it saves, so an effect can track it. */ persist(): void { const bundle: SavedDuel = { diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 1a576d8..4550d93 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -6,11 +6,24 @@ import SpellSheet from '$lib/components/SpellSheet.svelte'; import TurnSummary from '$lib/components/TurnSummary.svelte'; import WizardStatus from '$lib/components/WizardStatus.svelte'; - import { duel, FOE, YOU, castKey, tokensText } from '$lib/game/duel.svelte'; + import { duel, FOE, YOU, NAME_MAX, castKey, tokensText } from '$lib/game/duel.svelte'; import { GESTURE_SHORT, glyph } from '$lib/game/glyphs'; import { MAGIC_GESTURES } from '$lib/game/spells'; let confirmingNew = $state(false); + let renaming = $state(false); + let nameDraft = $state(''); + + function startRename() { + nameDraft = duel.you.name; + renaming = true; + } + + function saveName(e: SubmitEvent) { + e.preventDefault(); + duel.setName(nameDraft); + renaming = false; + } function startNew() { if (duel.inProgress && !confirmingNew) { @@ -45,10 +58,21 @@

Waving Hands

- {#if duel.state.turn === 0 && !duel.timeStopped} -

You are {duel.you.name}, duelling {duel.foe.name}. Each turn, both wizards choose one gesture per hand and reveal them together. The right run of gestures on one hand is a spell.

+ {#if renaming} +
+ + + +
{:else} -

You are {duel.you.name}, duelling {duel.foe.name}.

+

+ You are {duel.you.name}, duelling {duel.foe.name}. + {#if duel.state.turn === 0 && !duel.timeStopped}Each turn, both wizards choose one gesture per hand and reveal them together. The right run of gestures on one hand is a spell.{/if} +

{/if}