# boardgame-core A state management library for board games using [Preact Signals](https://preactjs.com/guide/v10/signals/). Build turn-based board games with reactive state, entity collections, spatial regions, and a command-driven game loop. ## Features - **Reactive State Management**: Fine-grained reactivity powered by [@preact/signals-core](https://preactjs.com/guide/v10/signals/) - **Type Safe**: Full TypeScript support with strict mode and generic context extension - **Entity Collections**: Signal-backed collections for managing game pieces (cards, dice, tokens, meeples, etc.) - **Region System**: Spatial management with multi-axis positioning, alignment, and shuffling - **Command System**: CLI-style command parsing with schema validation, type coercion, and prompt support - **Deterministic RNG**: Seeded pseudo-random number generator (Mulberry32) for reproducible game states ## Installation ```bash npm install boardgame-core ``` ## Writing a Game ### Defining a Game Each game defines its own context type by extending `IGameContext`, creates a command registry, and exports helper functions: ```ts import { IGameContext, createGameCommand, createGameContext, createCommandRegistry, registerCommand } from 'boardgame-core'; // 1. Define your game-specific state type MyGameState = { score: number; round: number; }; // 2. Extend IGameContext with your state type MyGameContext = IGameContext & { state: MyGameState; }; // 3. Define typed commands const addScore = createGameCommand( 'add-score ', async function(cmd) { this.context.state.score += cmd.params[0] as number; return this.context.state.score; } ); // 4. Create and populate a registry const registry = createCommandRegistry(); registerCommand(registry, addScore); // 5. Export a context factory with initial state export function createMyGameContext() { return createGameContext(registry, () => ({ state: { score: 0, round: 1 }, })); } // 6. Export helper functions for your game logic export function getScore(ctx: MyGameContext) { return ctx.state.score; } ``` ### Running a Game ```ts import { createMyGameContext } from './my-game'; const game = createMyGameContext(); // Run commands through the context const result = await game.commands.run('add-score 10'); if (result.success) { console.log(result.result); // 10 } // Access game state console.log(game.state.score); // 10 ``` ## Sample Games ### Tic-Tac-Toe The simplest example. Shows the basic command loop, 2D board regions, and win detection. See [`src/samples/tic-tac-toe.ts`](src/samples/tic-tac-toe.ts). ### Boop A more complex game with piece types (kittens/cats), supply management, the "boop" push mechanic, and graduation rules. // Compact cards in a hand towards the start applyAlign(handRegion); // Shuffle positions of all parts in a region shuffle(handRegion, rng); ``` ### Command Parsing ```ts import { parseCommand, parseCommandSchema, validateCommand } from 'boardgame-core'; // Parse a command string const cmd = parseCommand('move card1 hand --force -x 10'); // { name: 'move', params: ['card1', 'hand'], flags: { force: true }, options: { x: '10' } } // Define and validate against a schema const schema = parseCommandSchema('move [--force] [-x: number]'); const result = validateCommand(cmd, schema); // { valid: true } ``` ### Entity Collections ```ts import { createEntityCollection } from 'boardgame-core'; const collection = createEntityCollection(); collection.add({ id: 'a', name: 'Item A' }, { id: 'b', name: 'Item B' }); const accessor = collection.get('a'); console.log(accessor.value); // reactive access collection.remove('a'); ``` ### Random Number Generation ```ts import { createRNG } from 'boardgame-core'; const rng = createRNG(12345); rng.nextInt(6); // 0-5 rng.next(); // [0, 1) rng.next(100); // [0, 100) rng.setSeed(999); // reseed ``` ## API Reference ### Core | Export | Description | |---|---| | `IGameContext` | Base interface for the game context (parts, regions, commands, prompts) | | `createGameContext(registry, initialState?)` | Create a game context instance. `initialState` can be an object or factory function for custom properties | | `createGameCommand(schema, handler)` | Create a typed command with access to `this.context` | ### Parts | Export | Description | |---|---| | `Part` | Entity type representing a game piece | | `entity(id, data)` | Create a reactive entity | | `flip(part)` | Cycle to the next side | | `flipTo(part, side)` | Set to a specific side | | `roll(part, rng)` | Randomize side using RNG | ### Regions | Export | Description | |---|---| | `RegionEntity` | Entity type for spatial grouping of parts | | `RegionAxis` | Axis definition with min/max/align | | `applyAlign(region)` | Compact parts according to axis alignment | | `shuffle(region, rng)` | Randomize part positions | | `moveToRegion(part, targetRegion, position?)` | Move a part to another region | | `moveToRegionAll(parts, targetRegion, positions?)` | Move multiple parts to another region | | `removeFromRegion(part)` | Remove a part from its region | ### Commands | Export | Description | |---|---| | `parseCommand(input)` | Parse a command string into a `Command` object | | `parseCommandSchema(schema)` | Parse a schema string into a `CommandSchema` | | `validateCommand(cmd, schema)` | Validate a command against a schema | | `parseCommandWithSchema(cmd, schema)` | Parse and validate in one step | | `applyCommandSchema(cmd, schema)` | Apply schema validation and return validated command | | `createCommandRegistry()` | Create a new command registry | | `registerCommand(registry, runner)` | Register a command runner | | `unregisterCommand(registry, name)` | Remove a command from the registry | | `hasCommand(registry, name)` | Check if a command exists | | `getCommand(registry, name)` | Get a command runner by name | | `runCommand(registry, context, input)` | Parse and run a command string | | `runCommandParsed(registry, context, command)` | Run a pre-parsed command | | `createCommandRunnerContext(registry, context)` | Create a command runner context | | `CommandRunner` | Command runner type with schema and run function | | `CommandRunnerContext` | Context available inside command handlers | | `PromptEvent` | Event dispatched when a command prompts for input | ### Utilities | Export | Description | |---|---| | `createEntityCollection()` | Create a reactive entity collection | | `createRNG(seed?)` | Create a seeded RNG instance | | `Mulberry32RNG` | Mulberry32 PRNG class | ## Scripts ```bash npm run build # Build with tsup npm run test # Run tests in watch mode npm run test:run # Run tests once npm run typecheck # Type check with TypeScript ``` ## License MIT