From c5d487edbfe5839941726823845de3b57b47c4cd Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Wed, 23 Sep 2026 19:01:42 -0400 Subject: [PATCH] The Tablut board is cut in stone: mottled, chipped at a few corners, and cracked twice, as a slab from a long hall would be Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Jm2auWk6RP71CjaAb4FMoG --- src/lib/components/Board.svelte | 72 ++++++++++++++++++++++++++++++--- 1 file changed, 67 insertions(+), 5 deletions(-) diff --git a/src/lib/components/Board.svelte b/src/lib/components/Board.svelte index 86bbc15..4a521b7 100644 --- a/src/lib/components/Board.svelte +++ b/src/lib/components/Board.svelte @@ -48,6 +48,34 @@ }; const pieceWord = (p: string) => (p === 'k' ? 'the king' : p === 'a' ? 'an attacker' : 'a defender'); + // --- Age: the Tablut board came out of a long hall. A few chipped corners + // and two hairline cracks, placed by a fixed hand so the board is the same + // board every visit; nothing here moves or changes with play. + const chips = $derived.by(() => { + if (!worn) return [] as { x: number; y: number; r: number; d: string }[]; + const spots = [ + [1, 0, 3], [size - 1, 2, 0], [3, size - 1, 1], [size - 2, size - 2, 2], [0, size - 3, 3], [size - 4, 0, 1], [2, 4, 0] + ] as const; + return spots.map(([cx, cy, corner]) => { + const x = PAD_L + cx * CELL + (corner === 1 || corner === 2 ? CELL : 0); + const y = PAD_T + cy * CELL + (corner >= 2 ? CELL : 0); + const sx = corner === 1 || corner === 2 ? -1 : 1; + const sy = corner >= 2 ? -1 : 1; + const a = 5 + ((cx * 7 + cy * 3) % 4); + const b = 4 + ((cx * 5 + cy) % 3); + return { x, y, r: 0, d: `M${x} ${y} l${sx * a} 0 l${-sx * (a - b)} ${sy * b} l${-sx * b} ${sy * (a - b + 1)} z` }; + }); + }); + const cracks = $derived.by(() => { + if (!worn) return [] as string[]; + const w = PAD_L + size * CELL; + const h = PAD_T + size * CELL; + return [ + `M${PAD_L + CELL * 0.6} ${PAD_T + 2} c 6 14, -4 22, 8 36 s 2 18, 14 26 s 4 10, 2 22`, + `M${w - 3} ${h - CELL * 2.4} c -12 4, -18 -6, -30 2 s -14 12, -26 8 s -12 -4, -18 6` + ]; + }); + // --- Tokens: pieces with a life of their own, so a move slides and a capture fades --- interface Token { id: number; @@ -245,15 +273,23 @@ - + - + + + + + + + {#if worn} + + {/if} {#each showCoords ? Array.from({ length: size }, (_, n) => n) : [] as n (n)} {String.fromCharCode(97 + n)} {size - n} @@ -287,6 +323,12 @@ {/if} {/each} + {#each chips as chip (chip.d)} + + {/each} + {#each cracks as crack (crack)} + + {/each} {#each tokens as t (t.id)} {@const c = cellOf(size, t.at)} {@const cx = PAD_L + c.x * CELL + CELL / 2} @@ -375,10 +417,10 @@ --crown: #6b4a0f; } - /* Tablut: the older game, on a board that has been in a barn. */ + /* Tablut: the older game, cut in stone and kept in a long hall for a thousand years. */ .board[data-set='tablut'] { - --wood: #8d8577; - --wood-line: rgba(40, 34, 28, 0.4); + --wood: #8a8578; + --wood-line: rgba(36, 32, 27, 0.45); --coord: rgba(30, 26, 20, 0.5); --square: rgba(255, 250, 235, 0.05); --square-edge: rgba(255, 250, 235, 0.09); @@ -472,6 +514,26 @@ transition: fill 1.2s ease; } + .stain { + fill: rgba(120, 110, 96, 0.22); + pointer-events: none; + } + + .chip { + fill: rgba(28, 24, 20, 0.42); + stroke: rgba(230, 222, 205, 0.18); + stroke-width: 0.6; + pointer-events: none; + } + + .crack { + fill: none; + stroke: rgba(24, 20, 16, 0.55); + stroke-width: 0.9; + stroke-linecap: round; + pointer-events: none; + } + .coord { fill: var(--coord); font-family: var(--font);