diff --git a/README.md b/README.md index 33ab2d5..1585c9f 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,186 @@ # boardgame-core -基于 Preact Signals 的桌游状态管理库。 +A state management library for board games using [Preact Signals](https://preactjs.com/guide/v10/signals/). -## 特性 +## Features -- **响应式状态管理**: 使用 [@preact/signals-core](https://preactjs.com/guide/v10/signals/) 实现细粒度响应式 -- **类型安全**: 完整的 TypeScript 支持 +- **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 +- **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 + +```bash +npm install boardgame-core +``` + +## Usage + +### Game Context + +```ts +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) + +```ts +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 + +```ts +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 + +```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'); +``` + +### Rule Engine + +```ts +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 + +```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 | +|---|---| +| `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` | 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()` | 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