diff --git a/src/lib/components/Board.svelte b/src/lib/components/Board.svelte index 7fdbcbf..4419a63 100644 --- a/src/lib/components/Board.svelte +++ b/src/lib/components/Board.svelte @@ -21,8 +21,16 @@ /** The glass board, loaded the first time it is asked for, so three.js stays out of every other visit. */ let Glass = $state(null); let onGlass = $state(false); + let fetching = $state(false); async function seeGlass(open: boolean) { - if (open && !Glass) Glass = (await import('./Glass.svelte')).default; + if (open && !Glass) { + fetching = true; + try { + Glass = (await import('./Glass.svelte')).default; + } finally { + fetching = false; + } + } onGlass = open; } @@ -211,7 +219,7 @@ {#if room.error}

{room.error}

{/if} {#if glass.open} -

+

{/if} {#if onGlass && Glass} diff --git a/src/lib/components/Glass.svelte b/src/lib/components/Glass.svelte index e82edb4..5169be1 100644 --- a/src/lib/components/Glass.svelte +++ b/src/lib/components/Glass.svelte @@ -36,6 +36,8 @@ /** Air between the sheets, in squares: enough for a Dragon's wings. */ const GAP = 4.2; const CELL = 1; + /** The sheet's rim beyond the outer squares, in squares. */ + const RIM = 0.3; const levelY = (level: number) => (level - 1) * GAP; /** Where a square's centre sits: Gold's side towards +z, file a at -x. */ @@ -49,6 +51,8 @@ let frame: HTMLDivElement; let canvas: HTMLCanvasElement; let status = $state('Setting out the figures…'); + /** How many of the fifteen kinds of figure have arrived. */ + let arrived = $state(0); let view = $state<'quarter' | 'seat' | 'above'>('quarter'); type Livery = Record; @@ -58,7 +62,7 @@ const figures = new Map(); /** How many of the game's moves the figures have played through. */ let played = -1; - let ready = false; + let ready = $state(false); onMount(() => { const renderer = new THREE.WebGLRenderer({ canvas, antialias: true }); @@ -89,18 +93,22 @@ // The sheets, tinted by level, and the posts that hold them. const slabs: THREE.Mesh[] = []; for (const level of [1, 2, 3]) { + // The texture covers the whole sheet, rim included, so that the squares sit exactly under the figures. + const PX = 40; const c = document.createElement('canvas'); - c.width = FILES * 32; - c.height = RANKS * 32; + c.width = (FILES + 2 * RIM) * PX; + c.height = (RANKS + 2 * RIM) * PX; const ctx = c.getContext('2d')!; + ctx.fillStyle = SQUARES[level][1]; + ctx.fillRect(0, 0, c.width, c.height); for (let r = 0; r < RANKS; r++) for (let f = 0; f < FILES; f++) { ctx.fillStyle = SQUARES[level][isLight(idx(level, f, r)) ? 0 : 1]; - ctx.fillRect(f * 32, (RANKS - 1 - r) * 32, 32, 32); + ctx.fillRect((RIM + f) * PX, (RIM + RANKS - 1 - r) * PX, PX, PX); } const map = new THREE.CanvasTexture(c); map.colorSpace = THREE.SRGBColorSpace; const slab = new THREE.Mesh( - new THREE.BoxGeometry(FILES * CELL + 0.6, 0.12, RANKS * CELL + 0.6), + new THREE.BoxGeometry((FILES + 2 * RIM) * CELL, 0.12, (RANKS + 2 * RIM) * CELL), new THREE.MeshPhysicalMaterial({ map, transparent: true, opacity: 0.74, roughness: 0.15, metalness: 0, clearcoat: 1 }) ); slab.position.set(0, levelY(level) - 0.06, 0); @@ -346,9 +354,16 @@ look(view); controls.addEventListener('change', wake); + // Each kind's figures take their places as its file arrives, so the board fills in while the rest are fetched. Promise.all( (Object.keys(FIGURE) as Kind[]).map( - (kind) => new Promise((ok, no) => loader.load(`/figures/${FIGURE[kind]}.glb`, (gltf) => { protos.set(kind, { scene: gltf.scene, livery: liveries(gltf.scene) }); ok(); }, undefined, no)) + (kind) => new Promise((ok, no) => loader.load(`/figures/${FIGURE[kind]}.glb`, (gltf) => { + protos.set(kind, { scene: gltf.scene, livery: liveries(gltf.scene) }); + arrived++; + for (let i = 0; i < g.cells.length; i++) if (g.cells[i] !== '' && kindOf(g.cells[i]) === kind && !figures.has(i)) figure(i); + wake(); + ok(); + }, undefined, no)) ) ).then(() => { ready = true; @@ -388,7 +403,12 @@
- {#if status}

{status}

{/if} + {#if status} +
+

{status}

+ {#if !ready}{arrived} of {Object.keys(FIGURE).length}{/if} +
+ {/if}

· · @@ -413,9 +433,22 @@ .status { position: absolute; - top: 1rem; - left: 1rem; + inset: 0 0 auto; margin: 0; + padding: 2rem 1rem 0; + text-align: center; + } + + .status p { + margin: 0 0 0.6rem; + font-size: 1.15rem; + color: var(--bone); + } + + progress { + width: min(60%, 320px); + height: 6px; + accent-color: var(--ember); } .ways { diff --git a/src/routes/learn/game/+page.svelte b/src/routes/learn/game/+page.svelte index bb233da..b539d0b 100644 --- a/src/routes/learn/game/+page.svelte +++ b/src/routes/learn/game/+page.svelte @@ -12,8 +12,16 @@ /** The glass board, loaded the first time it is asked for. */ let Glass = $state(null); let onGlass = $state(false); + let fetching = $state(false); async function seeGlass(open: boolean) { - if (open && !Glass) Glass = (await import('$lib/components/Glass.svelte')).default; + if (open && !Glass) { + fetching = true; + try { + Glass = (await import('$lib/components/Glass.svelte')).default; + } finally { + fetching = false; + } + } onGlass = open; } @@ -72,7 +80,7 @@

{#if glass.open} -

+

{/if} {#if onGlass && Glass} diff --git a/static/figures/basilisk.glb b/static/figures/basilisk.glb index 573f426..01bf465 100644 Binary files a/static/figures/basilisk.glb and b/static/figures/basilisk.glb differ diff --git a/static/figures/cleric.glb b/static/figures/cleric.glb index 3e578d1..ecd2f7d 100644 Binary files a/static/figures/cleric.glb and b/static/figures/cleric.glb differ diff --git a/static/figures/dragon.glb b/static/figures/dragon.glb index 2225757..6b47457 100644 Binary files a/static/figures/dragon.glb and b/static/figures/dragon.glb differ diff --git a/static/figures/dwarf.glb b/static/figures/dwarf.glb index 920f45d..940367c 100644 Binary files a/static/figures/dwarf.glb and b/static/figures/dwarf.glb differ diff --git a/static/figures/elemental.glb b/static/figures/elemental.glb index 66b9f96..5e3d655 100644 Binary files a/static/figures/elemental.glb and b/static/figures/elemental.glb differ diff --git a/static/figures/griffon.glb b/static/figures/griffon.glb index d4e7529..ba1bf74 100644 Binary files a/static/figures/griffon.glb and b/static/figures/griffon.glb differ diff --git a/static/figures/hero.glb b/static/figures/hero.glb index b9f019c..b05351e 100644 Binary files a/static/figures/hero.glb and b/static/figures/hero.glb differ diff --git a/static/figures/king.glb b/static/figures/king.glb index f1020de..6f76a41 100644 Binary files a/static/figures/king.glb and b/static/figures/king.glb differ diff --git a/static/figures/mage.glb b/static/figures/mage.glb index bcbba52..3a18918 100644 Binary files a/static/figures/mage.glb and b/static/figures/mage.glb differ diff --git a/static/figures/oliphant.glb b/static/figures/oliphant.glb index cbf71c8..c712603 100644 Binary files a/static/figures/oliphant.glb and b/static/figures/oliphant.glb differ diff --git a/static/figures/paladin.glb b/static/figures/paladin.glb index 4a4a65e..adda4a5 100644 Binary files a/static/figures/paladin.glb and b/static/figures/paladin.glb differ diff --git a/static/figures/sylph.glb b/static/figures/sylph.glb index b2d1824..4732afb 100644 Binary files a/static/figures/sylph.glb and b/static/figures/sylph.glb differ diff --git a/static/figures/thief.glb b/static/figures/thief.glb index 711c07d..34280d8 100644 Binary files a/static/figures/thief.glb and b/static/figures/thief.glb differ diff --git a/static/figures/unicorn.glb b/static/figures/unicorn.glb index ec72e48..09ddf45 100644 Binary files a/static/figures/unicorn.glb and b/static/figures/unicorn.glb differ diff --git a/static/figures/warrior.glb b/static/figures/warrior.glb index cab8b95..eeab3df 100644 Binary files a/static/figures/warrior.glb and b/static/figures/warrior.glb differ