board game state management
Go to file
hyper c886e904a8 refactor: change PromptEvent reject/resolve to cancel/tryCommit 2026-04-02 19:08:14 +08:00
src refactor: change PromptEvent reject/resolve to cancel/tryCommit 2026-04-02 19:08:14 +08:00
tests refactor: change PromptEvent reject/resolve to cancel/tryCommit 2026-04-02 19:08:14 +08:00
.gitignore Initial commit: boardgame-core with build fixes 2026-03-31 18:01:57 +08:00
AGENTS.md refactor: add src alias to @/ 2026-04-02 16:03:44 +08:00
README.md docs: readme.md 2026-04-01 22:00:16 +08:00
package-lock.json feat: upgrade inline-schema 2026-04-02 18:39:59 +08:00
package.json refactor: install mutative 2026-04-02 13:11:45 +08:00
tsconfig.json refactor: add src alias to @/ 2026-04-02 16:03:44 +08:00
tsup.config.ts refactor: add src alias to @/ 2026-04-02 16:03:44 +08:00
vitest.config.ts refactor: add src alias to @/ 2026-04-02 16:03:44 +08:00

README.md

boardgame-core

A state management library for board games using Preact Signals.

Features

  • Reactive State Management: Fine-grained reactivity powered by @preact/signals-core
  • Type Safe: Full TypeScript support with strict mode
  • 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 Parsing: CLI-style command parsing with schema validation and type coercion
  • Rule Engine: Generator-based rule system with reactive context management
  • Deterministic RNG: Seeded pseudo-random number generator (Mulberry32) for reproducible game states

Installation

npm install boardgame-core

Usage

Game Context

import { createGameContext } from 'boardgame-core';

const game = createGameContext({ type: 'game' });

// Access entity collections
game.parts.add({ id: 'card1', sides: 2, side: 0, region: /* ... */, position: [0] });
game.regions.add({ id: 'hand', axes: [{ name: 'slot', min: 0, max: 7, align: 'start' }], children: [] });

// Context stack for nested rule scopes
game.pushContext({ type: 'combat' });
const combatCtx = game.latestContext('combat');
game.popContext();

Parts (Cards, Dice, Tokens)

import { flip, flipTo, roll, createRNG } from 'boardgame-core';

const rng = createRNG(42);

flip(card);           // cycle to next side
flipTo(card, 0);      // set to specific side
roll(dice, rng);      // random side using seeded RNG

Regions & Alignment

import { applyAlign, shuffle } from 'boardgame-core';

// Compact cards in a hand towards the start
applyAlign(handRegion);

// Shuffle positions of all parts in a region
shuffle(handRegion, rng);

Command Parsing

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');

Rule Engine

import { createRule, invokeRuleContext } from 'boardgame-core';

const myRule = createRule('drawCard', (ctx) => {
  // yield action types to pause and wait for external handling
  const action = yield 'draw';
  ctx.resolution = action;
});

const result = invokeRuleContext(
  game.pushContext.bind(game),
  'drawCard',
  myRule({ type: 'drawCard', actions: [], handledActions: 0, invocations: [] })
);

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
createGameContext(root?) Create a new game context instance
GameContext The game context model class
Context Base context type

Parts

Export Description
Part Entity type representing a game piece (card, die, token, etc.)
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
Region 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

Rules

Export Description
RuleContext<T> Rule execution context type
createRule(type, fn) Create a rule generator factory
invokeRuleContext(pushContext, type, rule) Execute a rule with context management

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

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