Have more questions? Join our

This documents framework v1, which is deprecated. Games already on it keep running and their authors can keep editing them, but new games are created on the current framework — start with React frontends.

Source files

A BoardWeaver game is a tree of TypeScript files under /src, entered at /src/game.ts. The entry point imports from "boardweaver" and exports gameConfig plus the game-logic hooks; everything else (pieces, spaces, shared helpers) lives in sibling files it imports. See core-concepts for the full layout.

The build bundles /src as a real module graph from the entry point, so files import each other by relative path (./pieces/XToken) or absolute /src path (/src/pieces/XToken). The only off-tree import allowed is the "boardweaver" runtime — bare imports (there's no node_modules) fail the build.

// /src/pieces/XToken.ts
import { PieceDef } from "boardweaver";
export class XToken extends PieceDef { /* see `pieces` */ }
// /src/spaces/GridCell.ts
import { SpaceDef } from "boardweaver";
export class GridCell extends SpaceDef { /* see `spaces` */ }
// /src/game.ts — the required entry point
import {
  ApplyActionsFn,
  GameStateConfigFn,
  GetPlayerScoresFn,
  GetSelectableItemsFn,
  IsGameOverFn,
} from "boardweaver";
import { XToken } from "./pieces/XToken";
import { GridCell } from "./spaces/GridCell";

export const gameConfig: GameStateConfigFn = () => ({
  startingPlayerStrategy: "Random",
  spaces: { "0": new GridCell({ x: 0, y: 0 }) },
  pieces: {},
  pieceDefs: { "x-token": new XToken({ order: 1 }) },
});

export const getSelectableItems: GetSelectableItemsFn = (state) => [];
export const applyActions: ApplyActionsFn = (state, action) => {};
export const getPlayerScores: GetPlayerScoresFn = (state) => ({});
export const isGameOver: IsGameOverFn = (state, scores) => false;

Pieces and spaces follow a strict file layout:

  • One PieceDef subclass per file under /src/pieces/. One SpaceDef subclass per file under /src/spaces/. Do not bundle multiple defs into a single file.
  • Filename matches the class name (e.g. class XToken/src/pieces/XToken.ts, class GridCell/src/spaces/GridCell.ts), as in the examples above.
  • No barrel files. Do not create /src/pieces/index.ts, /src/spaces/index.ts, or any other re-export aggregator. /src/game.ts and other files import each def directly by its path: import { XToken } from "./pieces/XToken".

Required exports

Export Type Notes
gameConfig GameStateConfigFn Returns the initial config. Called once.
getSelectableItems GetSelectableItemsFn Returns the viewer-visible click targets.
applyActions ApplyActionsFn Mutates state in place.
getPlayerScores GetPlayerScoresFn Returns Scores map.
isGameOver IsGameOverFn Returns a boolean.

Optional exports

Export Type Default
preGameInitialization PreGameInitializationFn not run
getButtons GetButtonsFn no buttons
getLayout GetLayoutFn { type: "radial" }
renderOverlay RenderOverlayFn no overlay

See game-logic for each hook's signature and contract; see layout-overlays for getLayout and renderOverlay.

gameConfig return shape

GameStateConfigFn is (params: { numPlayers: number }) => GameStateConfigObject. The returned object:

type GameStateConfigObject = {
  startingPlayerStrategy: "Random" | "All";
  pieces: Record<string, PieceDef>;          // top-level pieces, instantiated at game start
  spaces: Record<string, SpaceDef>;          // top-level spaces, instantiated at game start
  player?: {
    spaces?: Record<string, SpaceDef>;       // per-player spaces (one set per seat)
    publicState?: Record<string, unknown>;   // default per-player public state
    privateState?: Record<string, unknown>;  // default per-player private state
    def?: PlayerDef;                         // optional per-player Def (rare)
  };
  pieceDefs?: Record<string, PieceDef>;      // register kinds used only via state.addPiece
  spaceDefs?: Record<string, SpaceDef>;      // register kinds used only via state.addSpace
  metaData?: Record<string, unknown>;        // initial gameState.metaData
  scoreLabels?: string[];                    // labels for the score columns shown in UI
};

Map keys ("0", "1", ...) become the initial pieceId / spaceId. Per-player spaces synthesize ids ${playerId}/space/${key}.

Gotchas

  • Every value in pieces / spaces MUST be a PieceDef / SpaceDef instance. Raw object literals are rejected.
  • A kind used only at runtime (via state.addPiece(id, new Foo())) MUST also appear in pieceDefs or spaceDefs, otherwise addPiece throws "PieceDef kind ... is not registered".
  • Imports resolve only to other /src files (relative or absolute /src path) and the "boardweaver" runtime. There's no node_modules, so a bare import like import x from "pieces/Foo" fails the build — use "./pieces/Foo" or "/src/pieces/Foo".