5.6 KiB
5.6 KiB
AGENTS.md - boardgame-phaser
Project Overview
A Phaser 3 framework for building web board games, built on top of boardgame-core (state management with Preact Signals + Mutative). Uses a pnpm monorepo with two packages:
packages/framework(boardgame-phaser) — Reusable library: reactive scenes, signal→Phaser bindings, input/command bridge, Preact UI componentspackages/sample-game— Demo Tic-Tac-Toe game using the framework
boardgame-core Usage
For detailed boardgame-core API documentation and examples, see packages/framework/node_modules/boardgame-core/
Key concepts:
- MutableSignal — Reactive state container with
.valueand.produce() - Command System — CLI-style parsing with schema validation and prompt support
- Region System — Spatial management with
createRegion(),applyAlign(),shuffle(),moveToRegion() - Part System — Game pieces with
createPartsFromTable(),flip(),roll() - RNG — Deterministic PRNG via
createRNG(seed)for reproducible game states
Quick Example
import { createGameCommandRegistry, createRegion, IGameContext } from 'boardgame-core';
type GameState = {
board: Region;
parts: Part<{ player: 'X' | 'O' }>[];
currentPlayer: 'X' | 'O';
};
type Game = IGameContext<GameState>;
const registry = createGameCommandRegistry<GameState>();
registry.register('place <row:number> <col:number>', async function(game: Game, row: number, col: number) {
await game.produceAsync(state => {
state.parts.push({ id: `p-${row}-${col}`, regionId: 'board', position: [row, col], player: state.currentPlayer });
});
return true;
});
Commands
Root level
pnpm dev # Start sample-game dev server (Vite + HMR)
pnpm build # Build framework, then sample-game
pnpm build:framework # Build framework only
pnpm preview # Preview sample-game production build
Framework (packages/framework)
pnpm --filter boardgame-phaser build # tsup → dist/index.js + dist/index.d.ts
pnpm --filter boardgame-phaser typecheck # tsc --noEmit
Sample game (packages/sample-game)
pnpm --filter sample-game dev # Vite dev server with HMR
pnpm --filter sample-game build # tsc && vite build
pnpm --filter sample-game preview # vite preview
pnpm --filter sample-game typecheck # tsc --noEmit (add to scripts first)
Note: Sample game uses Tailwind CSS v4 with @tailwindcss/vite plugin and @preact/preset-vite for JSX transformation.
Dependency setup
# boardgame-core is a local dependency via symlink (link:../../../boardgame-core)
# After changes to boardgame-core, simply rebuild it:
cd ../boardgame-core && npm build
# The symlink automatically resolves to the updated dist/
Testing
No test framework is configured yet. When adding tests, use Vitest (consistent with boardgame-core):
# In packages/framework:
pnpm add -D vitest
# Run all tests:
pnpm vitest run
# Run a single test file:
pnpm vitest run src/bindings/index.test.ts
# Run tests matching a pattern:
pnpm vitest run -t "bindRegion"
# Watch mode:
pnpm vitest
Code Style
Imports
- Use ESM imports only (
import/export) - Group imports: external libraries → workspace packages → relative imports
- Use
typeimports for type-only imports:import type { Foo } from 'bar' - Path alias
@/*maps tosrc/*in both packages
Formatting
- 2-space indentation, no semicolons
- Single quotes for strings
- Trailing commas in multi-line objects/arrays
- Max line length: not enforced, but keep reasonable
TypeScript
- Strict mode enabled (see
tsconfig.base.json) - Prefer explicit types for function return values and public class members
- Use generics with constraints:
<TState extends Record<string, unknown>> - Define local utility types:
type DisposeFn = () => void - Use
as anysparingly; preferas unknown as Record<string, unknown>for type narrowing
Naming conventions
- Classes: PascalCase (
ReactiveScene,InputMapper,PromptHandler) - Interfaces: PascalCase with descriptive names (
ReactiveSceneOptions,BindRegionOptions) - Functions: camelCase (
bindSignal,createInputMapper,createPromptHandler) - Constants: UPPER_SNAKE_CASE (
CELL_SIZE,BOARD_OFFSET) - Type aliases: PascalCase (
DisposeFn,CommandResult) - Factory functions:
create*prefix (createInputMapper,createPromptHandler)
Architecture patterns
- ReactiveScene: Abstract base class extending
Phaser.Scene. Subclasses implementonStateReady()andsetupBindings(). Effects are auto-cleaned on scene shutdown viathis.events.on('shutdown', ...). - Bindings: Use
effect()from@preact/signals-coreto reactively sync signal state to Phaser objects. Always return cleanup functions. - Input/Command bridge: Maps Phaser pointer events to
boardgame-corecommand strings. Usescreate*factory pattern. - UI components: Preact functional components with hooks. Use
className(notclass) for CSS classes.
Error handling
- Command results use
{ success: true, result } | { success: false, error }pattern - Scene effects are cleaned up via the
shutdownevent, not by overridingshutdown() - Use
as anycasts only when Phaser types are incomplete (e.g.,setInteractive) - Prompt handlers return
string | nullfor validation errors
JSX
jsxImportSource: "preact"— no React import needed- Use
h()or JSX syntax interchangeably - Use
classNamefor CSS class attribute