A game told: twenty-seven written moves stepped through on the boards with a callout wherever there is something to see, and a composer to write the next one
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CCLa2PaEeCj8njQZSWGqHx
This commit is contained in:
1 parent
c8443c6f50
commit
965a28142c
7 files changed
+298
-4
No files matched your search
@@ -65,7 +65,7 @@
|
||||
|
||||
<section id="first">
|
||||
<h2>A first game</h2>
|
||||
<p>Before that, <a href="/learn">learn the pieces</a>: fifteen short scenes, one a piece, each a small position with a move for you to make and a word on what came of it.</p>
|
||||
<p>Before that, <a href="/learn">learn the pieces</a>, fifteen short scenes, one a piece, each a small position with a move for you to make and a word on what came of it, and then <a href="/learn/game">a game told</a>, twenty-seven moves stepped through with a callout wherever there is something to see.</p>
|
||||
<p>Play a drake first. It plays a fair game and never minds how long you take. Begin as you would at chess, with Warriors stepping forward and the knights of the land, the Unicorns and Heroes, coming out behind them. Gygax's own advice is to hold the Mage and the Paladin back until the lesser pieces are developed, and to keep your Dragon home. It starts directly above your King for a reason. A Dragon that flies off to raid leaves the King open to the other Dragon, which strikes the land from the air without ever coming down.</p>
|
||||
<p>Watch the Basilisks. Anything you leave on the land directly above an enemy Basilisk is frozen on the spot, and a frozen piece may as well be off the board. The Cleric is stronger than it looks, since it is the only piece that plays the same on all three boards, and the Dwarves, if you do not hurry them forward, make good guards late in the game.</p>
|
||||
<p>The full rules, piece by piece, are on the <a href={withRoom('/rules')}>rules page</a>, with Gygax's article beneath them. If a rule here reads wrong to you, or something misbehaves, <a href="mailto:eric@ericwagoner.com">send word</a>, or use <em>Report</em> in the masthead at the table, which pins what you saw to the move it happened on. The hall's <em>about this game</em> lists the other ways to reach the keeper.</p>
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
<header>
|
||||
{#if room}<a class="back" href={`/join/${room}`}>Back to your game</a>{' · '}{/if}<a class="back" href="/">Back to the hall</a> · <a class="back" href={withRoom('/guide')}>How to play</a> · <a class="back" href={withRoom('/rules')}>The rules</a>
|
||||
<h1>Learn the pieces</h1>
|
||||
<p class="lede">Fifteen pieces, one scene each, and a checkmate to finish. Every scene is a small position with something to see and one move to make. Tap any piece to read about it, as at the table.</p>
|
||||
<p class="lede">Fifteen pieces, one scene each, and a checkmate to finish. Every scene is a small position with something to see and one move to make. Tap any piece to read about it, as at the table. When the scenes are done, <a href="/learn/game">a game told</a> shows the pieces working together.</p>
|
||||
</header>
|
||||
|
||||
<nav class="scenes" aria-label="Scenes">
|
||||
@@ -78,7 +78,7 @@
|
||||
<p>{outcome.text}</p>
|
||||
<div class="ways">
|
||||
<button type="button" class="quiet" onclick={() => setScene(n)}>Set the scene again</button>
|
||||
{#if n < SCENES.length - 1}<button type="button" class="commit small" onclick={() => setScene(n + 1)}>Next: {SCENES[n + 1].title}</button>{:else}<a class="commit small" href="/">Back to the hall</a>{/if}
|
||||
{#if n < SCENES.length - 1}<button type="button" class="commit small" onclick={() => setScene(n + 1)}>Next: {SCENES[n + 1].title}</button>{:else}<a class="commit small" href="/learn/game">Next: a game told</a>{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<p class="task">{scene.task}</p>
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
<script lang="ts">
|
||||
// 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 { CALLOUTS, TOLD_MOVES, toldStates } from '$lib/game/told';
|
||||
import { narrate } from '$lib/game/chronicle';
|
||||
import { notation, kingSquare, inCheck } from '$lib/game';
|
||||
import { prefs, reducedMotion } from '$lib/prefs.svelte';
|
||||
|
||||
const states = toldStates();
|
||||
const last = states[states.length - 1];
|
||||
const tellings = narrate(last);
|
||||
let ply = $state(0);
|
||||
const g = $derived(states[ply]);
|
||||
const move = $derived(ply > 0 ? last.moves[ply - 1] : null);
|
||||
const telling = $derived(ply > 0 ? tellings[ply - 1] : null);
|
||||
const callout = $derived(CALLOUTS[ply] ?? '');
|
||||
const shownSquares = $derived(move ? (move.afar ? [move.from] : [move.from, move.to]) : []);
|
||||
const shownCaptured = $derived(move?.captured ? [move.to] : []);
|
||||
const checkSquare = $derived(!g.over && ply > 0 && inCheck(g.cells, g.toMove) ? kingSquare(g.cells, g.toMove) : move?.check ? kingSquare(g.cells, g.toMove) : -1);
|
||||
const calm = $derived(reducedMotion());
|
||||
const showCoords = $derived(prefs.coordinates !== false);
|
||||
const marks = Object.keys(CALLOUTS).map(Number).filter((n) => n > 0);
|
||||
|
||||
function go(n: number) {
|
||||
ply = Math.max(0, Math.min(TOLD_MOVES.length, n));
|
||||
}
|
||||
function key(e: KeyboardEvent) {
|
||||
if (e.key === 'ArrowRight') go(ply + 1);
|
||||
else if (e.key === 'ArrowLeft') go(ply - 1);
|
||||
else return;
|
||||
e.preventDefault();
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Wyrm: a game told</title>
|
||||
</svelte:head>
|
||||
|
||||
<svelte:window onkeydown={key} />
|
||||
|
||||
<main class="lesson">
|
||||
<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 that leaves its roost too soon and the Hero that kills it, a Basilisk’s slow walk and the piece it freezes, a Griffon’s swoop, and a Dragon’s checkmate from the air. Step through it with the buttons or the arrow keys.</p>
|
||||
</header>
|
||||
|
||||
<section class="stage">
|
||||
<div class="boards">
|
||||
<BoardArtwork {g} {showCoords} {shownSquares} {shownCaptured} {checkSquare} {calm} decorative />
|
||||
</div>
|
||||
<div class="words">
|
||||
<p class="eyebrow">{ply === 0 ? 'Before the first move' : `Move ${ply} of ${TOLD_MOVES.length}`}{#if move} · {notation(move)}{/if}</p>
|
||||
{#if telling}
|
||||
<ul class="told">
|
||||
{#each telling.lines as l, i (i)}<li class={l.kind}>{l.text}</li>{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
{#if callout}<p class="callout">{callout}</p>{/if}
|
||||
<div class="ways">
|
||||
<button type="button" class="quiet" disabled={ply === 0} onclick={() => go(ply - 1)}>Back</button>
|
||||
<button type="button" class="commit small" disabled={ply === TOLD_MOVES.length} onclick={() => go(ply + 1)}>Next move</button>
|
||||
{#if ply === TOLD_MOVES.length}<a class="quiet" href="/">Play a drake</a>{/if}
|
||||
</div>
|
||||
<p class="muted small marks">
|
||||
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>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.lesson {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 1.5rem var(--gutter) 4rem;
|
||||
}
|
||||
|
||||
.lesson a.back {
|
||||
color: var(--frost);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.4rem;
|
||||
font-weight: 300;
|
||||
margin: 0.5rem 0 0.3rem;
|
||||
}
|
||||
|
||||
.lede {
|
||||
color: var(--bone-dim);
|
||||
max-width: 44em;
|
||||
margin-bottom: 1.2rem;
|
||||
}
|
||||
|
||||
.stage {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(300px, 420px);
|
||||
gap: 1.5rem clamp(1.5rem, 4vw, 3.5rem);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.stage {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.words {
|
||||
order: -1;
|
||||
}
|
||||
}
|
||||
|
||||
.boards {
|
||||
max-width: 640px;
|
||||
}
|
||||
|
||||
.words {
|
||||
position: sticky;
|
||||
top: 0.5rem;
|
||||
}
|
||||
|
||||
.told {
|
||||
list-style: none;
|
||||
margin: 0.3rem 0 0;
|
||||
padding: 0;
|
||||
border-left: 2px solid var(--frost);
|
||||
padding-left: 0.6rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.told li.capture {
|
||||
color: var(--ember);
|
||||
}
|
||||
|
||||
.told li.check,
|
||||
.told li.freeze,
|
||||
.told li.promotion {
|
||||
color: var(--frost);
|
||||
}
|
||||
|
||||
.told li.note {
|
||||
color: var(--bone-dim);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.told li.end {
|
||||
color: var(--blood);
|
||||
}
|
||||
|
||||
.callout {
|
||||
margin-top: 0.8rem;
|
||||
color: var(--bone);
|
||||
border-left: 2px solid var(--ember);
|
||||
padding-left: 0.6rem;
|
||||
}
|
||||
|
||||
.ways {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem 0.8rem;
|
||||
margin-top: 0.9rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ways .quiet {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.marks {
|
||||
margin-top: 0.8rem;
|
||||
}
|
||||
|
||||
.mark {
|
||||
color: var(--bone-dim);
|
||||
text-decoration: none;
|
||||
margin-left: 0.45rem;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.mark.current {
|
||||
color: var(--ember);
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user