93 lines
5.5 KiB
Markdown
93 lines
5.5 KiB
Markdown
# AGENTS.md - boardgame-core
|
|
|
|
## Commands
|
|
|
|
Shell environment: git bash (bash.exe) on Windows.
|
|
|
|
```bash
|
|
npm run build # ESM bundle + declarations to dist/ (tsup)
|
|
npm run build:samples # Build samples separately (tsup.samples.config.ts)
|
|
npm run test # vitest in watch mode
|
|
npm run test:run # vitest once (no watch)
|
|
npm run typecheck # tsc --noEmit
|
|
```
|
|
|
|
Run a single test file: `npx vitest run tests/core/game.test.ts`
|
|
Run tests matching a name: `npx vitest run -t "should create"`
|
|
|
|
`npm run prepare` runs `npm run build` on install — if `inline-schema` isn't available, installs will fail.
|
|
|
|
## Local Dependency
|
|
|
|
`inline-schema` is a **local peer dependency** at `file:../inline-schema` — must exist as a sibling directory before `npm install`. Both vitest and tsup load custom plugins from it (`inline-schema/csv-loader/rollup` and `inline-schema/csv-loader/esbuild`). `yarn-spinner-loader` is also a local dependency at `file:../yarn-spinner-loader` — it can be changed and published if needed. It provides vitest and tsup/esbuild plugins for `.yarnproject` dialogue files.
|
|
|
|
## Architecture
|
|
|
|
- **MutableSignal\<T\>**: extends Preact Signal — `.value` to read, `.produce(draft => ...)` to mutate (uses `mutative`). `.produceAsync()` awaits interruption promises first, then mutates.
|
|
- **GameHost**: lifecycle manager — `start(seed?)`, `tryInput(string)`, `tryAnswerPrompt(def, ...args)`, `dispose()`. Reactive signals: `state`, `status`, `activePromptSchema`, `activePromptPlayer`, `activePromptHint`.
|
|
- **GameModule**: `{ registry?, createInitialState, start }`. `registry` defaults to `createGameCommandRegistry()` if omitted.
|
|
- **Command system**: CLI-style parsing with schema validation via `inline-schema`. `CommandRunner.run()` uses `this` bound to a `CommandRunnerContext`.
|
|
- **Prompt system**: `game.prompt(def, validator, currentPlayer?)` — validator **throws a string** to reject, **returns a value** to accept. `PromptEvent.tryCommit(Command | string)` returns `null` on success, error string on failure. `promptEnd` event fires after resolve or cancel.
|
|
- **Part collections**: `Record<string, Part<TMeta>>` keyed by ID. `Object.values()` for iteration, `Object.keys()` for count/IDs.
|
|
- **Region system**: `createRegion()` factory; `partMap` maps position keys to part IDs.
|
|
- **Barrel exports**: `src/index.ts` is the single public API surface.
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
src/
|
|
index.ts # Barrel export (single public API)
|
|
core/
|
|
game.ts # Game core logic
|
|
game-host.ts # GameHost lifecycle manager
|
|
part.ts # Part entity
|
|
part-factory.ts # Part factory
|
|
region.ts # Region/space management
|
|
utils/
|
|
mutable-signal.ts # MutableSignal<T> (extends Preact Signal)
|
|
rng.ts # Mulberry32 seeded PRNG
|
|
async-queue.ts # Async queue
|
|
middleware.ts # Middleware chain
|
|
command/ # CLI-style command system (7 files)
|
|
types.ts, command-parse.ts, command-registry.ts
|
|
command-runner.ts, command-validate.ts, command-apply.ts, schema-parse.ts, index.ts
|
|
samples/
|
|
tic-tac-toe.ts # Simple sample game
|
|
boop/ # Boop board game
|
|
onitama/ # Onitama card game
|
|
regicide/ # Regicide card game
|
|
slay-the-spire-like/ # Roguelike deckbuilder (see samples/slay-the-spire-like/AGENTS.md)
|
|
tests/ # Mirrors src/ with *.test.ts (20 files)
|
|
core/ # game-host, game, part, part-factory, region
|
|
samples/ # Each sample + slay-the-spire-like submodules
|
|
utils/ # mutable-signal, rng, async-queue, middleware, command
|
|
```
|
|
|
|
`src/global.d.ts` — `*.yarnproject` module declaration.
|
|
|
|
`testsgames/` and `testsrules/` — empty directories (reserved).
|
|
|
|
## Samples Build
|
|
|
|
`tsup.samples.config.ts` auto-discovers entries from `src/samples/` (directories → `index.ts`, files → direct `.ts`). It rewrites `@/core/*`, `@/utils/*`, and `@/index` imports to external `boardgame-core`.
|
|
|
|
## Code Style
|
|
|
|
- Double quotes for local imports, single quotes for npm packages
|
|
- `@/*` alias maps to `src/*` (configured in tsconfig, vitest, and tsup)
|
|
- Group imports: npm packages first, then local `@/` imports, separated by blank line
|
|
- 4-space indentation, semicolons, trailing commas in multi-line
|
|
- Arrow functions for callbacks; `function` keyword for methods needing `this` (e.g. command handlers)
|
|
- Strict TypeScript — no `any`; type aliases for object shapes (not interfaces)
|
|
- Discriminated unions for results: `{ success: true; result: T } | { success: false; error: string }`
|
|
- Derive state types via `ReturnType<typeof createInitialState>`
|
|
- Factory function prefix: `create` or `mutable` — `createGameHost`, `mutableSignal`
|
|
- Validation error messages in Chinese (e.g. `"参数不足"`)
|
|
|
|
## Testing
|
|
|
|
- Vitest with globals enabled, but **test files import explicitly**: `import { describe, it, expect } from 'vitest'`
|
|
- Use `@/` imports in test files (vitest resolve alias)
|
|
- Command/prompt test pattern: `waitForPrompt(ctx)` → `promptEvent.tryCommit(Command)` → returns `null` | error string
|
|
- No mocking — real implementations. Define inline helpers (`createTestContext()`, `waitForPrompt()`) per file.
|
|
- Narrow result types: `if (result.success)` before accessing `result.result` |