Documentation

The client

v3 · Preview

Framework v3 is an opt-in preview available to everyone. New games use the stable framework by default.

Control playback

API

Hook Contract
usePlayback(options?) options is { ownControls?: boolean }: true hides Boardweaver's return card, the bubble with each step's log lines, and the step controls while the component is mounted. Returns { status: "now" | "playing" | "paused"; behind: number; more: boolean; fetching: boolean; canStepBack: boolean; speed: PlaybackSpeed; setSpeed; play; pause; stepForward; stepBack; skipToNow }, where PlaybackSpeed is 0.5 | 1 | 2 | 4 | 8. Boardweaver's bar plays at 1, and a finished match's replay page offers 1 to 8. behind counts the steps read after the one on the board, and more is true when steps past those are not read yet. See Other players' steps.
useStepDuration(duration) void. duration is milliseconds per change at 1x, a number or (change: LastChange) => number, while the component is mounted. When several components set one, the one mounted last applies. Boardweaver shows each step for the longer of this time and the time to read and see it (Step time).

Other players' steps

Boardweaver shows the steps of other players (people and bots alike) one at a time, in order, even when several arrive together. useLastChange() reports each one as it is shown. Each step stays on the board long enough for the player to read its log lines and see its movements (Step time).

usePlayback() says where the board is. status is now when the board is the match as it stands. It is playing or paused while the board shows an earlier board. That is a step the player has not seen, a step they went back to, or, on return, the board they last saw. Then useBoard() is that earlier board, useOffers() is empty, useDraft() has no steps, and an act or Undo fails as PLAYING. The player's draft comes back at now. useScores() and useMatch() still describe the match as it stands, so do not show scores beside an earlier board as if they belonged to it.

When a player opens a match after other players moved, useMatch().ready is false until the first page of the steps they missed arrives (boardweaver/react reference). Then the board shows the last board they saw, with status paused. A spectator opens at now.

Boardweaver then covers the board with a card: how many rows of the game log the player missed, Watch, Skip to now, and Always skip to now in this match. Watch plays the missed steps, and Skip to now jumps to now without playing them. While the player watches, the bar under the board has Back, Play or Pause, Forward and Skip to now, and plays at 1x. Play shows every step, but Back and Forward move one row of the game log at a time: Forward to the end of the next row, past the steps that wrote no line, and Back to the end of the row before. Each step's log lines show with their players' names in a bubble over the bottom center of the board. Steps that arrive while the match is open play on their own, with the same bubble. At now, the bar offers Back and the game log, oldest first, with the rows the player missed marked. Each step that writes lines is a row. A step that writes no line joins the row before it in the same decision, or the decision's first row when no earlier step writes a line. A decision whose steps write no line is one row: the player's name and "made a move". A tap on a row moves the board to its last step. On a phone the log rises from the bar with a swipe up.

Keep the bottom center of the board free of anything the player reads while steps play, or draw your own controls: pass ownControls: true, and Boardweaver hides its card, its bubble and its step controls while that component is mounted. Then give the player pause, play, stepForward, stepBack, skipToNow and setSpeed, and always show skipToNow while status is not now, so the player can reach the board they act on:

const playback = usePlayback({ ownControls: true });
{playback.status !== "now" && <button onClick={playback.skipToNow}>Skip to now ({playback.behind} behind)</button>}
The game log lists moves and links to earlier positions in the match.
The game log lists moves and links to earlier positions in the match. Example data from the local development UI. Select the image to enlarge.

Step time

At 1x, Boardweaver shows each step of other players for at least this time:

The step has Time at 1x
log lines 800 ms, plus 55 ms for each character of their text, from 1500 ms to 5000 ms
movements and no log lines 700 ms, plus 100 ms for each movement after the first, up to 1500 ms
no log lines and no movements 250 ms

An undo stays for 600 ms. The speed divides each time: at 2x, a step of 1800 ms stays for 900 ms.

Call useStepDuration in every client that animates a change, such as a piece that slides or a card that flips. Give it the time your animations take. Boardweaver shows each step for the longer of your time and the time in the table. Without it, the next step can start before your animation ends.

Give every step one time:

useStepDuration(1200);

Or compute the time from your animations. This client slides a piece for 450 ms and flips a piece for 300 ms. It adds 400 ms so the player sees the result:

import type { LastChange } from "boardweaver/react";

const SLIDE_MS = 450;
const FLIP_MS = 300;
const SETTLE_MS = 400;

export const stepTime = (change: LastChange) =>
  change.steps.reduce((total, step) => {
    const slides = step.movements.filter((movement) => movement.type === "Moved").length;
    const flips = step.movements.filter((movement) => movement.type === "Flipped").length;
    return total + slides * SLIDE_MS + flips * FLIP_MS + SETTLE_MS;
  }, 0);

Then call useStepDuration(stepTime) in the component that draws the board.

Need a hand with this?

Join our Discord