/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.
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 objectgameConfig({ 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.
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.
/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.
v2-hooks — every hook's signature and contract, and the action union.v2-entities — PieceDef / SpaceDef subclasses and the three state buckets.v2-game-state — the read/write API these hooks operate on.