diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 8e6d4f4..d937472 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -293,7 +293,7 @@ wss.on("connection", (socket) => { return send(socket, { type: "error", message: "catching up already — one moment" }); } session.lastCatchUpAt = now; - const steps = catchUpSteps(room, session.playerId, Number(msg.sinceSeq ?? 0)); + const steps = catchUpSteps(room, session.playerId, Number(msg.sinceSeq ?? 0), msg.full === true); if ("error" in steps) return send(socket, { type: "error", message: steps.error }); send(socket, { type: "catchUp", steps }); break; diff --git a/packages/server/src/rooms.ts b/packages/server/src/rooms.ts index 76661ba..b8ba26c 100644 --- a/packages/server/src/rooms.ts +++ b/packages/server/src/rooms.ts @@ -268,11 +268,12 @@ export interface CatchUpStep { * Rebuild the game and capture a redacted view after each command from * `sinceSeq` on — the "what happened while you were away" reel. */ -export function catchUpSteps(room: Room, playerId: PlayerId, sinceSeq: number): CatchUpStep[] | { error: string } { +export function catchUpSteps(room: Room, playerId: PlayerId, sinceSeq: number, full = false): CatchUpStep[] | { error: string } { if (!room.state) return { error: "game not started" }; if (!room.players.includes(playerId)) return { error: "you hold no seat in this room" }; + if (full && room.state.phase !== "finished") return { error: "full replays wait for the game to finish" }; const MAX_STEPS = 200; - const from = Math.max(sinceSeq, room.log.length - MAX_STEPS); + const from = full ? 0 : Math.max(sinceSeq, room.log.length - MAX_STEPS); const { state: fresh } = createGame(room.state.config); let current = fresh; const steps: CatchUpStep[] = []; diff --git a/packages/web/src/App.svelte b/packages/web/src/App.svelte index 1850b69..e079b5e 100644 --- a/packages/web/src/App.svelte +++ b/packages/web/src/App.svelte @@ -1293,7 +1293,12 @@ {#if view.phase === "finished" && view.revealedHands}
-
The hands, face-up
+
+ The hands, face-up + {#if !local.active} + + {/if} +
{#each view.players as p (p.id)}
@@ -1926,6 +1931,10 @@ margin-bottom: 0.6rem; } .reveal-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.5rem; font-family: "Oswald", sans-serif; font-size: 0.8rem; letter-spacing: 0.12em; diff --git a/packages/web/src/Replay.svelte b/packages/web/src/Replay.svelte index 6576083..2114863 100644 --- a/packages/web/src/Replay.svelte +++ b/packages/web/src/Replay.svelte @@ -13,6 +13,7 @@ let idx = $state(0); let playing = $state(true); + let speed = $state(1); const step = $derived(steps[Math.min(idx, steps.length - 1)]!); const lines = $derived( step.events.map(humanize).filter((l): l is string => l !== null), @@ -24,7 +25,7 @@ const t = setInterval(() => { if (idx < steps.length - 1) idx += 1; else playing = false; - }, 1500); + }, 1500 / speed); return () => clearInterval(t); }); @@ -41,7 +42,7 @@
@@ -129,6 +133,11 @@ line-height: 1.45; min-height: 3.4rem; } + .replay-controls .speed { + font-size: 0.75rem; + opacity: 0.7; + } + .replay-controls .speed.current { opacity: 1; text-decoration: underline; } .replay-controls { display: flex; justify-content: center; diff --git a/packages/web/src/net.svelte.ts b/packages/web/src/net.svelte.ts index c74556b..4e505a3 100644 --- a/packages/web/src/net.svelte.ts +++ b/packages/web/src/net.svelte.ts @@ -461,6 +461,11 @@ class Net { } /** Ask for the reel of everything since we last watched. */ + /** The whole game from the deal, once it is finished. */ + requestFullReplay(): void { + this.send({ type: "catchUp", sinceSeq: 0, full: true }); + } + requestCatchUp(): void { if (!this.roomId) return; this.send({ type: "catchUp", sinceSeq: this.seen[this.roomId] ?? 0 });