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 four 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 frameworkpackages/boop-game— Boop sample gamepackages/regicide-game— Regicide game
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
Framework and regicide-game have Vitest configured:
# In packages/framework:
pnpm --filter boardgame-phaser test # Run all tests
pnpm --filter boardgame-phaser test:watch # Watch mode
# In packages/regicide-game:
pnpm --filter regicide-game test # Run all tests
pnpm --filter regicide-game test:watch # Watch mode
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 (
GameHostScene,PhaserGame,GameUI) - Interfaces: PascalCase with descriptive names (
GameHostSceneOptions,PhaserGameProps) - Functions: camelCase (
spawnEffect,bindRegion) - Constants: UPPER_SNAKE_CASE (
CELL_SIZE,BOARD_OFFSET) - Type aliases: PascalCase (
DisposeFn,CommandResult) - Factory functions:
create*prefix (createRegion,createRNG)
Architecture patterns
- GameHostScene: Abstract base class extending
Phaser.Scene. Subclasses implement game-specific logic. ProvidesgameHostproperty for state access andaddInterruption()/addTweenInterruption()for async flow control. Disposables are auto-cleaned on scene shutdown viathis.events.on('shutdown', ...). - Spawner: Use
spawnEffect()for data-driven object creation with signal-based configuration. - UI components: Preact functional components with hooks. Use
className(notclass) for CSS classes. - Phaser Bridge: Use
PhaserGame,PhaserScene, andGameUIcomponents fromboardgame-phaserfor React/Phaser integration.
Error handling
- Use
DisposableBagfor managing multiple disposables in scenes - Scene effects are cleaned up via the
shutdownevent - Use
as anycasts only when Phaser types are incomplete (e.g.,setInteractive) - Game commands return validation errors as
string | null
JSX
jsxImportSource: "preact"— no React import needed- Use
h()or JSX syntax interchangeably - Use
classNamefor CSS class attribute