Have more questions? Join our

The game source file

/src/game.ts is the rules. The server runs it authoritatively, and your React client runs the same functions locally to predict what a click will do — so there is exactly one implementation of the rules, and it lives here.

Required exports

import {
  type GameStateConfigFn,
  type GameStateConfigObject,
  type ApplyActionsFn,
  type GetAvailableActionsFn,
  type GetPlayerScoresFn,
  type IsGameOverFn,
} from "boardweaver";

export const gameConfig: GameStateConfigFn = ({ numPlayers }) => ({ /* … */ });
export const applyActions: ApplyActionsFn = (state, action) => { /* … */ };
export const getAvailableActions: GetAvailableActionsFn = (state) => [ /* … */ ];
export const getPlayerScores: GetPlayerScoresFn = (state) => ({ /* … */ });
export const isGameOver: IsGameOverFn = (state, scores) => false;

Optional: getButtons, preGameInitialization. All are documented in v2-hooks.

getSelectableItems is the legacy alternative to getAvailableActions — a game may export either one, and older games export the former. New games should use getAvailableActions; see v2-hooks.

gameConfig and the config object

gameConfig({ numPlayers }) runs once and returns the table's starting layout. It must be deterministic — same params, same output — because reconnects and replays re-run it. Randomized setup (shuffling, dealing) belongs in preGameInitialization.

export const gameConfig: GameStateConfigFn = () => {
  const config: GameStateConfigObject = {
    startingPlayerStrategy: "Random",
    scoreLabels: ["Points"],
    spaces: {
      "0": new GridCell({ x: 0, y: 0 }),
      "1": new GridCell({ x: 135, y: 0 }),
    },
    pieces: {},
    pieceDefs: {
      "x-token": new XToken({ order: 1 }),
    },
  };
  return config;
};
Field Holds
spaces Starting spaces, keyed by spaceId.
pieces Starting pieces, keyed by pieceId. May be empty if you create pieces during play.
pieceDefs Every piece kind the game can ever create, keyed by kind. Registers the class so state.addPiece can resolve it at runtime.
scoreLabels Names the entries in each player's publicPoints array, in order.
startingPlayerStrategy How the first active player is chosen, e.g. "Random".
player Optional per-player setup — spaces each player owns (a hand, a board side).

A kind you only create at runtime must still appear in pieceDefs. state.addPiece throws if the kind isn't registered.

Where code lives

Your source is a tree under /src, entered at /src/game.ts, bundled as a real module graph. Files import each other by relative path (./pieces/Knight) or absolute /src path (/src/pieces/Knight).

Path Holds
/src/game.ts Required entry point. Config + hooks. Small games can live entirely here.
/src/frontend.tsx Required. The React client — see react-frontend.
/src/pieces/ One PieceDef subclass per file, by convention.
/src/spaces/ One SpaceDef subclass per file, by convention.

The only off-tree import the game half may use is "boardweaver". Bare imports of anything else fail the build — there is no node_modules.

The split with the client

/src/game.ts decides what is legal and what happens. /src/frontend.tsx decides what the player sees. The client never re-implements a rule: it calls useAvailableActions() and useButtons(), which run your functions on the current state.

If you find yourself writing "is this move allowed" in the client, it belongs here instead.

Next

  • v2-hooks — every hook's signature and contract, and the action union.
  • v2-entitiesPieceDef / SpaceDef subclasses and the three state buckets.
  • v2-game-state — the read/write API these hooks operate on.