diff --git a/packages/web/src/App.svelte b/packages/web/src/App.svelte index 44cf406..27a8db5 100644 --- a/packages/web/src/App.svelte +++ b/packages/web/src/App.svelte @@ -27,6 +27,11 @@ } /** Which pending attack the player has already acknowledged (modal dismissed). */ let attackNoticeSeen = $state(null); + const openingRolls = $derived(net.openingRolls ?? local.openingRolls); + function dismissRolls() { + net.openingRolls = null; + local.openingRolls = null; + } /** A face-up card being peeked at from the scoresheet (view-only). */ let peekCard = $state(null); /** When the peeked card is a creature on the board, its live stats ride along. */ @@ -887,6 +892,28 @@ {/if} + {#if openingRolls} +
+
+
The dice decide who casts first
+
+ {#each openingRolls.players as p (p)} +
+ {p === openingRolls.first ? "๐Ÿ† " : ""}{p} + + {#each openingRolls.rolls[p] ?? [] as r, i (i)}๐ŸŽฒ{r}{/each} + +
+ {/each} +
+ {#if Object.values(openingRolls.rolls).some((r) => r.length > 1)} +
Tied wizards rolled again.
+ {/if} + +
+
+ {/if} + {#if view?.stack && attackNoticeKey && attackNoticeSeen !== attackNoticeKey && !noFanfare}
@@ -2094,6 +2121,24 @@ margin: 0.3rem auto 0; } .attack-standing.nullified { color: #b3372b; } + .rolloff { + display: flex; + flex-direction: column; + gap: 0.35rem; + margin: 0.4rem 0 0.6rem; + } + .rolloff-row { + display: flex; + justify-content: space-between; + gap: 1.2rem; + font-size: 1rem; + color: #43331f; + } + .rolloff-winner { font-weight: 700; } + .rolloff-name { font-family: "Oswald", sans-serif; letter-spacing: 0.04em; } + .rolloff-dice { font-family: "Courier Prime", monospace; } + .die-face { margin-left: 0.4rem; } + .rolloff-note { font-size: 0.8rem; color: #6b5a41; font-style: italic; margin-bottom: 0.3rem; } .attack-fist { font-size: 1.1rem; color: #43331f; } .table-talk { diff --git a/packages/web/src/local.svelte.ts b/packages/web/src/local.svelte.ts index b9b9e0e..134090c 100644 --- a/packages/web/src/local.svelte.ts +++ b/packages/web/src/local.svelte.ts @@ -49,6 +49,8 @@ class LocalGame { handoffTo = $state(null); log = $state([]); /** The finished game as a reel, each step from its actor's own seat. */ + /** The opening roll-off, shown once as the boards flip. */ + openingRolls = $state<{ rolls: Record; first: string; players: string[] } | null>(null); replaySteps = $state<{ seq: number; actor: PlayerId; events: GameEvent[]; view: GameView }[] | null>(null); view = $derived( this.gameState && this.viewerId ? viewFor(this.gameState, this.viewerId) : null, @@ -135,6 +137,11 @@ class LocalGame { deckRev: 13, }; const { state, events } = createGame(config); + for (const e of events) { + if (e.type === "gameStarted") { + this.openingRolls = { rolls: e.dieRolls, first: e.firstPlayer, players: e.players }; + } + } this.config = config; this.commands = []; this.gameState = state; diff --git a/packages/web/src/net.svelte.ts b/packages/web/src/net.svelte.ts index 546a9f6..59be4f3 100644 --- a/packages/web/src/net.svelte.ts +++ b/packages/web/src/net.svelte.ts @@ -25,7 +25,12 @@ export function spellName(cardId: string): string { export function humanize(e: GameEvent): string | null { switch (e.type) { - case "gameStarted": return `Game started โ€” ${e.players.join(", ")}. ${e.firstPlayer} goes first.`; + case "gameStarted": { + const rolloff = e.players + .map((p) => `${p} ๐ŸŽฒ ${(e.dieRolls[p] ?? []).join(", ")}`) + .join(" ยท "); + return `Game started. The roll-off: ${rolloff} โ€” ${e.firstPlayer} goes first.`; + } case "turnStarted": return `โ€” ${e.player}'s turn (round ${e.round}) โ€”`; case "turnSkipped": return `${e.player} loses a turn.`; case "extraTurnStarted": return `${e.player} takes an extra turn!`; @@ -246,6 +251,8 @@ class Net { /** Moves you haven't watched yet in the current room. */ missedMoves = $state(0); /** A catch-up reel delivered by the server. */ + /** The opening roll-off, shown once as the boards flip. */ + openingRolls = $state<{ rolls: Record; first: string; players: string[] } | null>(null); catchUp = $state<{ seq: number; actor: string; events: GameEvent[]; view: GameView; chat?: { player: string; text: string }[] }[] | null>(null); private seen: Record = loadSeen(); private currentSeq = 0; @@ -327,6 +334,9 @@ class Net { let talk = 0; for (const e of msg.events as GameEvent[]) { if (e.type === "tableTalk") talk++; + if (e.type === "gameStarted") { + this.openingRolls = { rolls: e.dieRolls, first: e.firstPlayer, players: e.players }; + } const line = humanize(e); if (line) this.log = [...this.log, line]; }