Test and maintain

v3 · Preview

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

Upgrade a game

Follow these steps to move a framework v1 or v2 game to this framework. upgrade_framework answers this section when it moves a game here.

This is a rewrite of the game logic and the client, not an edit. Port one rule at a time. Keep the old files to read from until step 5.

Use what Boardweaver provides

Boardweaver provides features that a framework v1 or v2 game built for itself. When the old game has its own version of one, delete its code and its state. Use the feature in the table below instead.

The old game built its own Use this instead
Undo: an undo stack, saved snapshots, Immer patches or an undo intent table.allow_undo() in the game logic, and the Undo button from useUndo() in the client (Changing the state)
A move made in parts: selection state, tentative moves or arrange-then-commit in the client A decision of several steps: choice offers, then a confirm offer. The client shows the steps so far with useDraft() (Offers, intents and decisions)
Match history: a move log, a last-move banner, a replay, or a list of moves kept in metaData table.log(line) on each step (Changing the state). Boardweaver shows each step to the other players with its log lines, gives Back, Forward and Skip to now, and replays a finished match. The client reads the lines with useLastChange() and the board's place with usePlayback() (The React client)
Move animations from a diff of the old and new state useBoardTransition() pairs viewer-visible endpoints in either direction. Use useTransitionEffect for custom timing and cleanup, or optional measured overlays (Directional animation)
Hidden hand order: shuffling ids, decoy pieces or sorting tricks .collection() on the space, .hiding() on the piece and .revealing() on the offer (Hidden information)
Random numbers: a seed or a generator kept in the state The rng argument of setup and apply_action (Randomness and determinism)
Bots: extra seats added in preGameInitialization, or bot moves picked in the client or on a timer A Bot in the game logic. The lobby seats it (Bots)
Player names and avatars kept in the state usePlayers()
A queue of moves on their way, or an offline banner usePending(), and useFailures() for refused moves
An end screen built into the board Boardweaver's own end screen, or a GameOver export with useMatchEnd() (The React client)
Rules, sound, full screen or leave buttons drawn by the client Nothing: Boardweaver draws them in the bar under the board
While reviewing moves, the match bar offers Back, Play or Pause, Forward, and Skip to now, with Rules and match actions alongside.
While reviewing moves, the match bar offers Back, Play or Pause, Forward, and Skip to now, with Rules and match actions alongside. Example data from the local development UI. Select the image to enlarge.

What is new

Framework v1 or v2 This framework
Game logic in TypeScript, in /src/game.ts Game logic in Rust, in /src/lib.rs and the modules it declares
gameConfig and preGameInitialization setup
getAvailableActions or getSelectableItems, and getButtons offers: one Offer per legal step, each an action, an intent and an optional label
applyActions changes the state in place apply_action changes the state only through Table, and refuses a move with Err
getPlayerScores, scoreLabels, isGameOver scores and is_game_over; the client names the score columns (Scores)
PieceDef and SpaceDef classes, each with a kind Piece::new(public_kind) and Space::new(space_id, kind) values
A piece has a pieceId A piece has no id: it is named by its space and its index there
static constants of a kind A const or a data file in /src/data/
publicState, privateState public_state, private_state, the piece's owner, and private_kind for a hidden face
metaData, activePlayerIds meta_data, active_player_ids
v1: Boardweaver draws from render(). v2: useGameState, useAvailableActions and match.click useBoard, useOffers and useAct in /src/frontend.tsx

These carry over unchanged: uploaded art (the client reads it with useImage(key)), /theme.json, the rules pages and the marketing page.

1. Read the new contract

Read How a game works. Call get_example with name hexbloom, then a listed path for each file you need. Find the five game functions in /src/lib.rs. Find where /src/frontend.tsx takes an offer with useAct().

2. Map the state

Write down how each def, bucket and map of the old game becomes a space, a piece or a map of the new state. Give every field the old game read or wrote a place there. Find a piece the old code found by pieceId by its space and index, or by a field in public_state. Decide who owns each private part. Leave out every field that only served a feature in Use what Boardweaver provides, such as an undo stack, a move log or a random seed.

References: The state, Hidden information.

3. Port the game logic

Write /src/lib.rs with the five game functions and export_game!. Port the old rules one at a time. Write a #[test] for each rule as you port it. Draw randomness only from the rng argument.

Run validate_code until it answers no Rust error and its tests counts a passed test for each rule. Then play a start_game session to the end at each seat count.

References: The game functions, Changing the state, Offers, intents and decisions, Randomness and determinism, Testing a game.

4. Rewrite the client

Rewrite /src/frontend.tsx against boardweaver/react of this framework. Draw from useBoard(), make only the offers of useOffers() clickable, and take them with useAct().

Run validate_code until it answers no error that names /src/frontend.tsx.

References: The React client, boardweaver/react reference.

5. Delete the old game logic

Delete /src/game.ts, /src/pieces/, /src/spaces/ and every other TypeScript file that only the old game logic imported. Run validate_code until it answers valid: true.

6. Commit

Call commit. The commit builds against this framework and pins the game to it.

Tips

  • Port the rules, not the code. TypeScript that mutates a piece found by id does not translate line by line: rewrite each rule against Table.
  • A move that took one v2 action and picked several values is several steps here, one value each. Mark the last step confirm.
  • Mark every offer whose result depends on hidden information with .revealing() (Reveals).
  • Read a seat's position with state.seat_of(id). A PlayerId is not a seat number (Players).
  • To stay on the old framework, call upgrade_framework with the game's committed major before you commit. It removes the pending upgrade, and your file changes stay.

Upgrade animation code

  1. Replace each diff or snapshot match of two boards with useBoardTransition(). Animate when its outcome is animate. Show the new board at once when it is snap.
  2. Draw each piece from its endpoint's piece. Draw a Hidden spot as a card back at the group's anchor.
  3. Keep your art, paths, easing and effects. Run them with useTransitionEffect or TransitionProvider overlays (Optional animation helpers).
  4. Scale every duration and delay with run.milliseconds. Return a cleanup that cancels everything your renderer starts.
  5. Test each transition for each viewer, direction and interruption, as Client checks lists. Run the tests through the real timeline.

Need a hand with this?

Join our Discord