boardgame-core/AGENTS.md

93 lines
4.4 KiB
Markdown
Raw Normal View History

2026-04-02 15:36:38 +08:00
# AGENTS.md - boardgame-core
## Commands
Prefer `bash.exe`(C:\Users\Administrator\AppData\Local\Microsoft\WindowsApps\bash.exe) over Powershell.
```bash
2026-04-03 11:20:35 +08:00
npm run build # Build ESM bundle + declarations to dist/ (via tsup)
2026-04-02 15:36:38 +08:00
npm run test # Run vitest in watch mode
npm run test:run # Run vitest once (no watch)
npm run typecheck # Type-check without emitting (tsc --noEmit)
```
### Running a single test file
```bash
npx vitest run tests/samples/tic-tac-toe.test.ts
```
### Running a single test by name
```bash
npx vitest run -t "should detect horizontal win for X"
```
2026-04-03 11:20:35 +08:00
## Project Structure
```
src/
core/ # Core game primitives (game, part, region)
samples/ # Example games (tic-tac-toe, boop)
utils/ # Shared utilities (mutable-signal, command, rng, async-queue)
2026-04-03 11:20:35 +08:00
index.ts # Single public API barrel export
tests/ # Mirrors src/ structure with *.test.ts files
```
2026-04-02 15:36:38 +08:00
## Code Style
### Imports
- Use **double quotes** for local imports, **single quotes** for npm packages
2026-04-03 11:20:35 +08:00
- Use `@/**/*` for `./src/**/*` import alias (configured in tsconfig.json)
- Use `@/index` when importing from the public API surface in samples
- Group imports: npm packages first, then local `@/` imports, separated by blank line
2026-04-02 15:36:38 +08:00
### Formatting
2026-04-03 11:20:35 +08:00
- **4-space indentation** (no tabs)
2026-04-02 15:36:38 +08:00
- **Semicolons** at statement ends
- **Arrow functions** for callbacks; `function` keyword for methods needing `this` (e.g. command handlers)
- No trailing whitespace
2026-04-03 11:20:35 +08:00
- Trailing commas in multi-line objects/arrays
2026-04-02 15:36:38 +08:00
### Naming Conventions
- **Types/Interfaces**: `PascalCase``Part`, `Region`, `Command`, `IGameContext`
- **Classes**: `PascalCase``MutableSignal`, `AsyncQueue`, `Mulberry32RNG`
2026-04-02 15:36:38 +08:00
- **Functions**: `camelCase`, verb-first — `createGameContext`, `parseCommand`, `isValidMove`
- **Variables**: `camelCase`
- **Constants**: `UPPER_SNAKE_CASE``BOARD_SIZE`, `WINNING_LINES`
- **Test files**: `*.test.ts` mirroring `src/` structure under `tests/`
- **Factory functions**: prefix with `create` or `mutable``createGameContext`, `mutableSignal`
2026-04-02 15:36:38 +08:00
### Types
- **Strict TypeScript** is enabled — no `any`
- Use **generics** heavily: `MutableSignal<T>`, `CommandRunner<TContext, TResult>`
2026-04-02 15:36:38 +08:00
- Use **type aliases** for object shapes (not interfaces)
- Use **discriminated unions** for results: `{ success: true; result: T } | { success: false; error: string }`
- Use `unknown` for untyped values, narrow with type guards or `as`
2026-04-03 11:20:35 +08:00
- Derive state types via `ReturnType<typeof createInitialState>`
2026-04-02 15:36:38 +08:00
### Error Handling
- Prefer **result objects** over throwing: return `{ success, result/error }`
- When catching: `const error = e as Error;` then use `error.message`
- Use `throw new Error(...)` only for truly exceptional cases
- Validation error messages are in Chinese (e.g. `"参数不足"`)
2026-04-03 11:20:35 +08:00
- Prompt validators return `null` for valid input, `string` error message for invalid
2026-04-02 15:36:38 +08:00
### Testing
- **Vitest** with globals enabled (`describe`, `it`, `expect` available without import)
- Use `async/await` for async tests
- Narrow result types with `if (result.success)` before accessing `result.result`
2026-04-03 11:20:35 +08:00
- Define inline helper functions in test files when needed (e.g. `createTestContext()`)
2026-04-02 15:36:38 +08:00
- No mocking — use real implementations
2026-04-03 11:20:35 +08:00
- For command tests: use `waitForPrompt()` + `promptEvent.tryCommit()` pattern
2026-04-02 15:36:38 +08:00
- Test helpers: `createTestContext()`, `createTestRegion()`
## Architecture Notes
- **Reactivity**: `MutableSignal<T>` extends Preact Signal — access state via `.value`, mutate via `.produce(draft => ...)`
2026-04-02 15:36:38 +08:00
- **Command system**: CLI-style parsing with schema validation via `inline-schema`
- **Prompt system**: Commands prompt for input via `PromptEvent` with `resolve`/`reject`; `tryCommit` accepts `Command | string` and validates against schema before custom validator
2026-04-02 15:36:38 +08:00
- **Barrel exports**: `src/index.ts` is the single public API surface
2026-04-03 11:20:35 +08:00
- **Game modules**: export `registry` and `createInitialState` for use with `createGameContextFromModule`
- **Region system**: plain `Region` type with `createRegion()` factory; `parts` are stored as `Record<string, Part>` keyed by ID, with `partMap` in regions mapping position keys to part IDs
- **Part collections**: Game state uses `Record<string, Part<TMeta>>` (not arrays) for O(1) lookup by ID. Use `Object.values(parts)` when iteration is needed, `Object.keys(parts)` for count/IDs
- **Mutative**: used for immutable state updates inside `MutableSignal.produce()`