|
|
||
|---|---|---|
| src | ||
| tests | ||
| .gitignore | ||
| AGENTS.md | ||
| README.md | ||
| package-lock.json | ||
| package.json | ||
| tsconfig.json | ||
| tsup.config.ts | ||
| vitest.config.ts | ||
README.md
boardgame-core
A state management library for board games using Preact 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
- 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
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:
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<MyGameContext, number>(
'add-score <amount:number>',
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<MyGameContext>();
registerCommand(registry, addScore);
// 5. Export a context factory with initial state
export function createMyGameContext() {
return createGameContext<MyGameContext>(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
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.
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 <from> <to> [--force] [-x: number]');
const result = validateCommand(cmd, schema);
// { valid: true }
Entity Collections
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
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<TContext>(registry, initialState?) |
Create a game context instance. initialState can be an object or factory function for custom properties |
createGameCommand<TContext, TResult>(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<TContext>() |
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<T>() |
Create a reactive entity collection |
createRNG(seed?) |
Create a seeded RNG instance |
Mulberry32RNG |
Mulberry32 PRNG class |
Scripts
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