In the told game, tap any piece to see where it could go next and read about it, before the game shows what it did

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CCLa2PaEeCj8njQZSWGqHx
This commit is contained in:
Eric WagonerandClaude Fable 5.1 committed 2026-10-26 20:14:29 -04:00
1 parent 86f861f651
commit b3234ad19d
1 file changed
+44 -3
+44 -3
View File
@@ -2,9 +2,10 @@
// A game told: the boards, one move at a time, each move as the chronicle
// tells it, and a callout where there is something to see.
import BoardArtwork from '$lib/components/BoardArtwork.svelte';
import PiecePanel from '$lib/components/PiecePanel.svelte';
import { CALLOUTS, TOLD_MOVES, toldStates } from '$lib/game/told';
import { narrate } from '$lib/game/chronicle';
import { notation, kingSquare, inCheck } from '$lib/game';
import { notation, kingSquare, inCheck, movesFrom, legalMovesFrom, levelOf, fileOf, rankOf, idx, FILES, RANKS, LEVELS } from '$lib/game';
import { prefs, reducedMotion } from '$lib/prefs.svelte';
const states = toldStates();
@@ -22,10 +23,39 @@
const showCoords = $derived(prefs.coordinates !== false);
const marks = Object.keys(CALLOUTS).map(Number).filter((n) => n > 0);
/** A piece tapped to read about it and to see where it could go from here, before the game shows what it did. */
let inspected = $state<number | null>(null);
const reach = $derived.by(() => {
if (inspected === null || g.cells[inspected] === '' || g.over) return [];
const side = g.cells[inspected][0] === 'g' ? 'gold' : 'scarlet';
return side === g.toMove ? legalMovesFrom(g.cells, inspected, g.setup) : movesFrom(g.cells, inspected, g.setup);
});
function go(n: number) {
ply = Math.max(0, Math.min(TOLD_MOVES.length, n));
inspected = null;
}
function tap(i: number) {
inspected = g.cells[i] !== '' && inspected !== i ? i : null;
}
function squareKey(e: KeyboardEvent, i: number) {
const step: Record<string, [number, number, number]> = { ArrowLeft: [-1, 0, 0], ArrowRight: [1, 0, 0], ArrowUp: [0, 1, 0], ArrowDown: [0, -1, 0], PageUp: [0, 0, 1], PageDown: [0, 0, -1] };
if (e.key in step) {
e.preventDefault();
e.stopPropagation();
const [df, dr, dl] = step[e.key];
const nf = fileOf(i) + df;
const nr = rankOf(i) + dr;
const nl = levelOf(i) + dl;
if (nf >= 0 && nf < FILES && nr >= 0 && nr < RANKS && nl >= 1 && nl <= LEVELS) document.querySelector<SVGRectElement>(`rect.square[data-i="${idx(nl, nf, nr)}"]`)?.focus();
} else if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
tap(i);
} else if (e.key === 'Escape') inspected = null;
}
/** Left and right step the game, unless the keyboard is on a square, where the arrows walk the board. */
function key(e: KeyboardEvent) {
if ((e.target as HTMLElement | null)?.tagName === 'rect') return;
if (e.key === 'ArrowRight') go(ply + 1);
else if (e.key === 'ArrowLeft') go(ply - 1);
else return;
@@ -43,12 +73,12 @@
<header>
<a class="back" href="/">Back to the hall</a> · <a class="back" href="/learn">Learn the pieces</a> · <a class="back" href="/guide">How to play</a> · <a class="back" href="/rules">The rules</a>
<h1>A game told</h1>
<p class="lede">Twenty-seven moves, written to show what a first game should see. A Dragon leaves its roost too soon and a Hero kills it, a Basilisk walks slowly under a piece that came too deep and freezes it, a Griffon swoops, and a Dragon gives checkmate from the air. Step through it with the buttons or the arrow keys.</p>
<p class="lede">Twenty-seven moves, written to show what a first game should see. A Dragon leaves its roost too soon and a Hero kills it, a Basilisk walks slowly under a piece that came too deep and freezes it, a Griffon swoops, and a Dragon gives checkmate from the air. Step through it with the buttons or the arrow keys, and tap any piece to see where it could go next and to read about it.</p>
</header>
<section class="stage">
<div class="boards">
<BoardArtwork {g} {showCoords} {shownSquares} {shownCaptured} {checkSquare} {calm} decorative />
<BoardArtwork {g} {showCoords} {shownSquares} {shownCaptured} {checkSquare} selected={inspected} {reach} {calm} yourMove={true} {tap} key={squareKey} />
</div>
<div class="words">
<p class="eyebrow">{ply === 0 ? 'Before the first move' : `Move ${ply} of ${TOLD_MOVES.length}`}{#if move}&nbsp;· {notation(move)}{/if}</p>
@@ -67,6 +97,11 @@
Jump to a callout:
{#each marks as n (n)}<button type="button" class="link plain mark" class:current={ply === n} onclick={() => go(n)}>{n}</button>{/each}
</p>
{#if inspected !== null && g.cells[inspected] !== ''}
<div class="panel">
<PiecePanel {g} at={inspected} mySide={null} close={() => (inspected = null)} />
</div>
{/if}
</div>
</section>
</main>
@@ -119,6 +154,8 @@
.words {
position: sticky;
top: 0.5rem;
max-height: calc(100vh - 1rem);
overflow-y: auto;
}
.told {
@@ -182,4 +219,8 @@
.mark.current {
color: var(--ember);
}
.panel {
margin-top: 1rem;
}
</style>