Crowded squares: every token type gets its own zone, multiples fan out

A square can legally hold terrain, a wizard or three, two treasures
(the win condition itself), floor objects, creatures, a warp token,
and a boobytrap disc all at once. The cell is now zoned: terrain
underlays the whole square, wizards fan across the top, treasures fan
across the bottom, floor objects stack bottom-left, creatures fan in
from the right, the dimensional warp shrinks into the top-left
corner, and boobytrap discs sit top-right. Two treasures on a home
base — the winning tableau — now reads as two treasures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eric Wagoner
2026-08-16 00:27:05 -04:00
co-authored by Claude Fable 5
parent bd8ca99dc0
commit 002dbd2547
+21 -8
View File
@@ -150,9 +150,15 @@
class="home-star" fill={playerColor(p.id)} class="home-star" fill={playerColor(p.id)}
/> />
{/each} {/each}
{#each view.treasures as t (t.id)} {#each [...view.treasures.reduce((m, t) => {
if (!t.position) return m;
const k = `${t.position.x},${t.position.y}`;
m.set(k, [...(m.get(k) ?? []), t]);
return m;
}, new Map()).entries()] as [tKey, group] (tKey)}
{#each group as t, ti (t.id)}
{#if t.position} {#if t.position}
{@const tx = t.position.x * CELL + CELL / 2} {@const tx = t.position.x * CELL + CELL / 2 + (group.length > 1 ? (ti - (group.length - 1) / 2) * CELL * 0.3 : 0)}
{@const ty = t.position.y * CELL + CELL * 0.72} {@const ty = t.position.y * CELL + CELL * 0.72}
{#if USE_TOKEN_ART} {#if USE_TOKEN_ART}
<image <image
@@ -171,6 +177,7 @@
{/if} {/if}
{/if} {/if}
{/each} {/each}
{/each}
<!-- terrain: solid stone and thornbushes --> <!-- terrain: solid stone and thornbushes -->
{#each Object.entries(view.squareContents) as [key, content] (key)} {#each Object.entries(view.squareContents) as [key, content] (key)}
@@ -209,8 +216,8 @@
{#if USE_TOKEN_ART} {#if USE_TOKEN_ART}
<image <image
href="/tokens/dimensional-warp.png" href="/tokens/dimensional-warp.png"
x={tok.x * CELL + CELL * 0.16} y={tok.y * CELL + CELL * 0.16} x={tok.x * CELL + CELL * 0.04} y={tok.y * CELL + CELL * 0.04}
width={CELL * 0.68} height={CELL * 0.68} width={CELL * 0.44} height={CELL * 0.44}
preserveAspectRatio="xMidYMid slice" class="token-art" preserveAspectRatio="xMidYMid slice" class="token-art"
/> />
{:else} {:else}
@@ -223,7 +230,7 @@
<!-- boobytrap tokens: face-down for everyone (the caster knows the real one) --> <!-- boobytrap tokens: face-down for everyone (the caster knows the real one) -->
{#each view.boobytraps as trap, ti (ti)} {#each view.boobytraps as trap, ti (ti)}
{#each trap.cells as tc, i (i)} {#each trap.cells as tc, i (i)}
<circle cx={tc.x * CELL + CELL * 0.26} cy={tc.y * CELL + CELL * 0.26} r="6" <circle cx={tc.x * CELL + CELL * 0.82} cy={tc.y * CELL + CELL * 0.18} r="6"
class="trap-token" class:trap-real={trap.realCell != null && tc.x === trap.realCell.x && tc.y === trap.realCell.y} /> class="trap-token" class:trap-real={trap.realCell != null && tc.x === trap.realCell.x && tc.y === trap.realCell.y} />
{/each} {/each}
{/each} {/each}
@@ -353,9 +360,14 @@
{/each} {/each}
{/each} {/each}
<!-- creatures --> <!-- creatures (fanned when several share a square) -->
{#each view.creatures as c (c.id)} {#each [...view.creatures.reduce((m, c) => {
{@const ccx = c.position.x * CELL + CELL * 0.72} const k = `${c.position.x},${c.position.y}`;
m.set(k, [...(m.get(k) ?? []), c]);
return m;
}, new Map()).entries()] as [cKey, cGroup] (cKey)}
{#each cGroup as c, ci (c.id)}
{@const ccx = c.position.x * CELL + CELL * 0.72 - (cGroup.length > 1 ? ci * CELL * 0.26 : 0)}
{@const ccy = c.position.y * CELL + CELL * 0.7} {@const ccy = c.position.y * CELL + CELL * 0.7}
<g <g
role="button" tabindex="-1" class="creature" role="button" tabindex="-1" class="creature"
@@ -394,6 +406,7 @@
{/if} {/if}
</g> </g>
{/each} {/each}
{/each}
<!-- edge selection hitboxes --> <!-- edge selection hitboxes -->
{#each edgeHitboxes as h (`${h.cell.x},${h.cell.y},${h.side}`)} {#each edgeHitboxes as h (`${h.cell.x},${h.cell.y},${h.side}`)}