mirror of
https://github.com/tiennm99/ai-coding-workflow-labs.git
synced 2026-09-18 18:25:55 +00:00
docs(01): create Phase 1 Core Foundation plans
This commit is contained in:
@@ -30,12 +30,12 @@ Decimal phases appear between their surrounding integers in numeric order.
|
||||
2. Game loop runs at 60fps using requestAnimationFrame with delta time tracking
|
||||
3. Event system allows components to subscribe to and emit typed events
|
||||
4. Basic Tile model exists with properties for id, type, position, and cleared state
|
||||
**Plans**: TBD
|
||||
**Plans**: 3 plans
|
||||
|
||||
Plans:
|
||||
- [ ] 01-01: Project scaffolding with Vite + TypeScript + Canvas
|
||||
- [ ] 01-02: Game loop with delta time and event emitter
|
||||
- [ ] 01-03: Tile model and game configuration constants
|
||||
- [ ] 01-01-PLAN.md — Project scaffolding with Vite + TypeScript + Canvas, config constants, and shared types
|
||||
- [ ] 01-02-PLAN.md — Game loop with delta time, typed event emitter, and Tile model class
|
||||
- [ ] 01-03-PLAN.md — Game orchestrator class, main entry point, and human verification
|
||||
|
||||
### Phase 2: Grid and Input
|
||||
**Goal**: Players can see a grid of Pokemon tiles and interact with them via mouse and touch
|
||||
@@ -126,7 +126,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6
|
||||
|
||||
| Phase | Plans Complete | Status | Completed |
|
||||
|-------|----------------|--------|-----------|
|
||||
| 1. Core Foundation | 0/3 | Not started | - |
|
||||
| 1. Core Foundation | 0/3 | Planning complete | - |
|
||||
| 2. Grid and Input | 0/3 | Not started | - |
|
||||
| 3. Core Matching Mechanics | 0/3 | Not started | - |
|
||||
| 4. Game State Management | 0/3 | Not started | - |
|
||||
@@ -136,3 +136,4 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6
|
||||
---
|
||||
*Roadmap created: 2026-03-10*
|
||||
*Granularity: standard*
|
||||
*Last updated: 2026-03-10 after Phase 1 planning*
|
||||
|
||||
@@ -0,0 +1,289 @@
|
||||
---
|
||||
phase: 01-core-foundation
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- package.json
|
||||
- tsconfig.json
|
||||
- vite.config.ts
|
||||
- vitest.config.ts
|
||||
- index.html
|
||||
- src/main.ts
|
||||
- src/config.ts
|
||||
- src/types/index.ts
|
||||
- src/vite-env.d.ts
|
||||
autonomous: true
|
||||
requirements: [CORE-01]
|
||||
user_setup: []
|
||||
must_haves:
|
||||
truths:
|
||||
- "Developer can run npm run dev without errors"
|
||||
- "Developer sees a Canvas element rendered in the browser"
|
||||
- "Configuration constants are defined and typed"
|
||||
- "Shared TypeScript types exist for Tile, Position, and Events"
|
||||
artifacts:
|
||||
- path: "package.json"
|
||||
provides: "Project dependencies and scripts"
|
||||
contains: '"vite", "typescript", "vitest"'
|
||||
- path: "index.html"
|
||||
provides: "HTML entry with Canvas element"
|
||||
contains: '<canvas id="game">'
|
||||
- path: "src/config.ts"
|
||||
provides: "Game constants"
|
||||
min_lines: 20
|
||||
- path: "src/types/index.ts"
|
||||
provides: "Shared type definitions"
|
||||
exports: ["TilePosition", "Tile", "GameEvents"]
|
||||
key_links:
|
||||
- from: "src/main.ts"
|
||||
to: "index.html"
|
||||
via: "DOM canvas element reference"
|
||||
pattern: "getElementById.*game"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Create the project scaffolding with Vite + TypeScript + Canvas, establish game configuration constants, and define shared type interfaces.
|
||||
|
||||
Purpose: Establish the foundational project structure that all subsequent code builds upon. Without this, no game code can run.
|
||||
Output: Working Vite dev server with Canvas, typed configuration, and shared type definitions.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@./.claude/get-shit-done/workflows/execute-plan.md
|
||||
@./.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/01-core-foundation/01-CONTEXT.md
|
||||
@.planning/phases/01-core-foundation/01-RESEARCH.md
|
||||
</context>
|
||||
|
||||
<interfaces>
|
||||
<!-- Contracts defined in this plan - executors implement against these -->
|
||||
|
||||
```typescript
|
||||
// src/types/index.ts - Type definitions that downstream plans will use
|
||||
|
||||
export interface TilePosition {
|
||||
row: number;
|
||||
col: number;
|
||||
}
|
||||
|
||||
export interface Tile {
|
||||
id: string;
|
||||
type: number; // 0-15 for emoji index
|
||||
position: TilePosition;
|
||||
cleared: boolean;
|
||||
}
|
||||
|
||||
export interface GameEvents {
|
||||
'game:start': void;
|
||||
'game:tick': { deltaTime: number };
|
||||
'tile:selected': { tile: Tile; row: number; col: number };
|
||||
'tile:cleared': { tile: Tile };
|
||||
'game:score': { points: number };
|
||||
'game:over': { won: boolean };
|
||||
'error': Error;
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
// src/config.ts - Configuration constants
|
||||
|
||||
export const CONFIG = {
|
||||
grid: {
|
||||
rows: 10,
|
||||
cols: 16,
|
||||
totalTiles: 160,
|
||||
pairsPerType: 10,
|
||||
},
|
||||
tile: {
|
||||
size: 48,
|
||||
gap: 4,
|
||||
cornerRadius: 8,
|
||||
},
|
||||
emojis: [
|
||||
'🌟', '⭐', '💫', '✨', '🌙', '☀️', '🔥', '💧',
|
||||
'🌿', '⚡', '🧊', '🪨', '🌸', '🍃', '🌊', '🍄'
|
||||
],
|
||||
colors: {
|
||||
background: '#1a1a2e',
|
||||
tile: '#16213e',
|
||||
tileHover: '#0f3460',
|
||||
selection: '#e94560',
|
||||
text: '#eaeaea',
|
||||
},
|
||||
} as const;
|
||||
```
|
||||
</interfaces>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Create Vite project with TypeScript and Canvas</name>
|
||||
<files>package.json, tsconfig.json, vite.config.ts, vitest.config.ts, index.html, src/main.ts, src/vite-env.d.ts</files>
|
||||
<behavior>
|
||||
- Test 1: npm run dev starts without errors
|
||||
- Test 2: Browser displays Canvas element with colored background
|
||||
- Test 3: npm run test runs vitest successfully (even with no tests)
|
||||
</behavior>
|
||||
<action>
|
||||
1. Initialize Vite project with vanilla-ts template:
|
||||
- Run: `npm create vite@latest . -- --template vanilla-ts` (use current directory)
|
||||
- If prompted about non-empty directory, proceed with overwrite
|
||||
|
||||
2. Install dependencies:
|
||||
- Run: `npm install`
|
||||
- Run: `npm install -D vitest @vitest/coverage-v8 @types/node`
|
||||
|
||||
3. Create vite.config.ts with basic configuration:
|
||||
- No plugins needed for vanilla TS
|
||||
- Add test configuration for vitest
|
||||
|
||||
4. Create vitest.config.ts:
|
||||
```typescript
|
||||
import { defineConfig } from 'vitest/config';
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'node',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
5. Update package.json scripts:
|
||||
- Add: `"test": "vitest"`, `"test:run": "vitest run"`
|
||||
|
||||
6. Modify index.html:
|
||||
- Replace default Vite content with:
|
||||
- A single `<canvas id="game"></canvas>` element
|
||||
- Basic centered styling
|
||||
- Keep the `<script type="module" src="/src/main.ts"></script>`
|
||||
|
||||
7. Update src/main.ts as temporary placeholder:
|
||||
- Get canvas element by id 'game'
|
||||
- Get 2D context
|
||||
- Set canvas size (use CONFIG.grid dimensions when available)
|
||||
- Fill with background color from CONFIG.colors when available
|
||||
- For now, use hardcoded: canvas 832x528 (16 cols * 52, 10 rows * 52), background '#1a1a2e'
|
||||
|
||||
8. Ensure tsconfig.json has strict mode enabled.
|
||||
|
||||
DO NOT use Phaser, PixiJS, or any game framework - native Canvas only.
|
||||
DO NOT add complex build configurations - keep Vite defaults.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>npm run test -- --run 2>&1 | head -20</automated>
|
||||
</verify>
|
||||
<done>
|
||||
- `npm run dev` starts Vite dev server without errors
|
||||
- Browser at localhost:5173 shows Canvas with dark blue background
|
||||
- `npm run test` runs without configuration errors
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: Create game configuration constants</name>
|
||||
<files>src/config.ts</files>
|
||||
<behavior>
|
||||
- Test 1: CONFIG.grid.rows equals 10
|
||||
- Test 2: CONFIG.grid.cols equals 16
|
||||
- Test 3: CONFIG.emojis has exactly 16 emojis
|
||||
- Test 4: CONFIG.tile.size and gap are positive numbers
|
||||
</behavior>
|
||||
<action>
|
||||
Create src/config.ts with all game constants as a single exported CONFIG object.
|
||||
|
||||
Include these sections (use `as const` for type inference):
|
||||
- grid: { rows: 10, cols: 16, totalTiles: 160, pairsPerType: 10 }
|
||||
- tile: { size: 48, gap: 4, cornerRadius: 8 }
|
||||
- emojis: array of 16 nature emojis (from RESEARCH.md)
|
||||
- colors: { background, tile, tileHover, selection, text }
|
||||
|
||||
Emoji set (exactly these 16):
|
||||
🌟 ⭐ 💫 ✨ 🌙 ☀️ 🔥 💧 🌿 ⚡ 🧊 🪨 🌸 🍃 🌊 🍄
|
||||
|
||||
Color values:
|
||||
- background: '#1a1a2e'
|
||||
- tile: '#16213e'
|
||||
- tileHover: '#0f3460'
|
||||
- selection: '#e94560'
|
||||
- text: '#eaeaea'
|
||||
|
||||
DO NOT add runtime validation - these are compile-time constants.
|
||||
DO NOT export individual constants - use the CONFIG namespace.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>npm run test -- --run src/__tests__/config.test.ts 2>&1 || echo "Test file will be created in task"</automated>
|
||||
</verify>
|
||||
<done>
|
||||
- src/config.ts exports CONFIG object
|
||||
- CONFIG.emojis.length === 16
|
||||
- CONFIG.grid.rows * CONFIG.grid.cols === CONFIG.grid.totalTiles
|
||||
- All color values are valid hex strings
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 3: Create shared type definitions</name>
|
||||
<files>src/types/index.ts</files>
|
||||
<behavior>
|
||||
- Test 1: TilePosition type has row and col as numbers
|
||||
- Test 2: Tile interface has id, type, position, cleared
|
||||
- Test 3: GameEvents type defines event names and payloads
|
||||
</behavior>
|
||||
<action>
|
||||
Create src/types/index.ts with TypeScript interfaces and types.
|
||||
|
||||
Define these exports:
|
||||
1. TilePosition interface: { row: number; col: number }
|
||||
2. Tile interface: { id: string; type: number; position: TilePosition; cleared: boolean }
|
||||
3. GameEvents type mapping event names to payload types:
|
||||
- 'game:start': void
|
||||
- 'game:tick': { deltaTime: number }
|
||||
- 'tile:selected': { tile: Tile; row: number; col: number }
|
||||
- 'tile:cleared': { tile: Tile }
|
||||
- 'game:score': { points: number }
|
||||
- 'game:over': { won: boolean }
|
||||
- 'error': Error
|
||||
|
||||
DO NOT add implementation code - this file is types only.
|
||||
DO NOT import from other modules - types should be self-contained.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>npx tsc --noEmit src/types/index.ts 2>&1 | head -10</automated>
|
||||
</verify>
|
||||
<done>
|
||||
- src/types/index.ts exports TilePosition, Tile, GameEvents
|
||||
- TypeScript compiles without errors
|
||||
- Types are properly exported for use in other modules
|
||||
</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
After all tasks complete:
|
||||
1. Run `npm run dev` - should start without errors
|
||||
2. Open browser to localhost:5173 - should see Canvas with dark background
|
||||
3. Run `npm run test` - should pass (even if empty test suite)
|
||||
4. Run `npx tsc --noEmit` - should compile without errors
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- [ ] Vite dev server starts with `npm run dev`
|
||||
- [ ] Canvas element renders in browser with colored background
|
||||
- [ ] CONFIG object exports all required constants
|
||||
- [ ] TilePosition, Tile, and GameEvents types are defined
|
||||
- [ ] TypeScript compiles without errors
|
||||
- [ ] Vitest runs without configuration errors
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/01-core-foundation/01-01-SUMMARY.md`
|
||||
</output>
|
||||
@@ -0,0 +1,334 @@
|
||||
---
|
||||
phase: 01-core-foundation
|
||||
plan: 02
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on: [01-01]
|
||||
files_modified:
|
||||
- src/game/GameLoop.ts
|
||||
- src/game/EventEmitter.ts
|
||||
- src/models/Tile.ts
|
||||
- src/__tests__/GameLoop.test.ts
|
||||
- src/__tests__/EventEmitter.test.ts
|
||||
- src/__tests__/Tile.test.ts
|
||||
autonomous: true
|
||||
requirements: [CORE-01]
|
||||
user_setup: []
|
||||
must_haves:
|
||||
truths:
|
||||
- "Game loop runs at 60fps using requestAnimationFrame"
|
||||
- "Event system allows typed subscribe and emit operations"
|
||||
- "Tile model has id, type, position, and cleared properties"
|
||||
- "All core classes have passing unit tests"
|
||||
artifacts:
|
||||
- path: "src/game/GameLoop.ts"
|
||||
provides: "requestAnimationFrame game loop"
|
||||
exports: ["GameLoop"]
|
||||
- path: "src/game/EventEmitter.ts"
|
||||
provides: "Typed event emitter"
|
||||
exports: ["TypedEventEmitter"]
|
||||
- path: "src/models/Tile.ts"
|
||||
provides: "Tile data model"
|
||||
exports: ["Tile"]
|
||||
- path: "src/__tests__/GameLoop.test.ts"
|
||||
provides: "GameLoop unit tests"
|
||||
min_lines: 20
|
||||
- path: "src/__tests__/EventEmitter.test.ts"
|
||||
provides: "EventEmitter unit tests"
|
||||
min_lines: 20
|
||||
- path: "src/__tests__/Tile.test.ts"
|
||||
provides: "Tile unit tests"
|
||||
min_lines: 20
|
||||
key_links:
|
||||
- from: "src/game/GameLoop.ts"
|
||||
to: "requestAnimationFrame"
|
||||
via: "browser API"
|
||||
pattern: "requestAnimationFrame"
|
||||
- from: "src/game/EventEmitter.ts"
|
||||
to: "src/types/index.ts"
|
||||
via: "GameEvents type"
|
||||
pattern: "GameEvents"
|
||||
- from: "src/models/Tile.ts"
|
||||
to: "src/config.ts"
|
||||
via: "emoji lookup"
|
||||
pattern: "CONFIG.emojis"
|
||||
- from: "src/models/Tile.ts"
|
||||
to: "src/types/index.ts"
|
||||
via: "TilePosition, Tile types"
|
||||
pattern: "TilePosition"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Implement the core game infrastructure: game loop with delta time tracking, typed event emitter for component communication, and Tile model class.
|
||||
|
||||
Purpose: These are the foundational systems that all game logic builds upon. The game loop drives frame updates, the event system decouples components, and the Tile model represents game state.
|
||||
Output: Working GameLoop, TypedEventEmitter, and Tile classes with unit tests.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@./.claude/get-shit-done/workflows/execute-plan.md
|
||||
@./.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/01-core-foundation/01-CONTEXT.md
|
||||
@.planning/phases/01-core-foundation/01-RESEARCH.md
|
||||
@.planning/phases/01-core-foundation/01-01-PLAN.md
|
||||
</context>
|
||||
|
||||
<interfaces>
|
||||
<!-- Types from Plan 01 that this plan uses -->
|
||||
|
||||
From src/types/index.ts:
|
||||
```typescript
|
||||
export interface TilePosition {
|
||||
row: number;
|
||||
col: number;
|
||||
}
|
||||
|
||||
export interface Tile {
|
||||
id: string;
|
||||
type: number;
|
||||
position: TilePosition;
|
||||
cleared: boolean;
|
||||
}
|
||||
|
||||
export interface GameEvents {
|
||||
'game:start': void;
|
||||
'game:tick': { deltaTime: number };
|
||||
'tile:selected': { tile: Tile; row: number; col: number };
|
||||
'tile:cleared': { tile: Tile };
|
||||
'game:score': { points: number };
|
||||
'game:over': { won: boolean };
|
||||
'error': Error;
|
||||
}
|
||||
```
|
||||
|
||||
From src/config.ts:
|
||||
```typescript
|
||||
export const CONFIG = {
|
||||
emojis: [
|
||||
'🌟', '⭐', '💫', '✨', '🌙', '☀️', '🔥', '💧',
|
||||
'🌿', '⚡', '🧊', '🪨', '🌸', '🍃', '🌊', '🍄'
|
||||
],
|
||||
// ... other config
|
||||
} as const;
|
||||
```
|
||||
</interfaces>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Implement GameLoop class</name>
|
||||
<files>src/game/GameLoop.ts, src/__tests__/GameLoop.test.ts</files>
|
||||
<behavior>
|
||||
- Test 1: start() begins the loop and calls update callback
|
||||
- Test 2: stop() cancels the animation frame
|
||||
- Test 3: Delta time is calculated and passed to update callback
|
||||
- Test 4: Multiple ticks accumulate correct time
|
||||
</behavior>
|
||||
<action>
|
||||
Create src/game/GameLoop.ts and src/__tests__/GameLoop.test.ts following TDD pattern.
|
||||
|
||||
GameLoop class requirements:
|
||||
1. Constructor accepts an update callback: `(deltaTime: number) => void`
|
||||
2. `start()` method begins the loop using requestAnimationFrame
|
||||
3. `stop()` method cancels the animation frame
|
||||
4. Target 60fps (tickLength = 1000/60 ms)
|
||||
5. Accumulate delta time and call update with correct deltaTime value
|
||||
6. Store the rAF ID to enable stopping (prevent memory leak)
|
||||
|
||||
Implementation pattern (from RESEARCH.md):
|
||||
```typescript
|
||||
export class GameLoop {
|
||||
private readonly tickLength: number = 1000 / 60;
|
||||
private lastTick: number = 0;
|
||||
private rafId: number = 0;
|
||||
private running: boolean = false;
|
||||
|
||||
constructor(private update: (deltaTime: number) => void) {}
|
||||
|
||||
start(): void { /* start loop */ }
|
||||
stop(): void { /* cancel rAF */ }
|
||||
private main = (timestamp: number): void => { /* rAF callback */ }
|
||||
}
|
||||
```
|
||||
|
||||
Test file should cover:
|
||||
- start() sets running to true
|
||||
- stop() sets running to false and cancels rAF
|
||||
- update callback is called with deltaTime
|
||||
- Loop can be started and stopped multiple times
|
||||
|
||||
DO NOT use setInterval - must use requestAnimationFrame.
|
||||
DO NOT forget to store rAF ID for cleanup.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>npm run test -- --run src/__tests__/GameLoop.test.ts</automated>
|
||||
</verify>
|
||||
<done>
|
||||
- GameLoop class exports from src/game/GameLoop.ts
|
||||
- All tests in GameLoop.test.ts pass
|
||||
- start() begins loop, stop() cancels loop
|
||||
- Delta time is correctly calculated
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: Implement TypedEventEmitter class</name>
|
||||
<files>src/game/EventEmitter.ts, src/__tests__/EventEmitter.test.ts</files>
|
||||
<behavior>
|
||||
- Test 1: on() registers a listener for an event
|
||||
- Test 2: emit() calls all registered listeners with payload
|
||||
- Test 3: off() removes a specific listener
|
||||
- Test 4: Listeners receive correctly typed payloads
|
||||
- Test 5: 'error' event is handled without throwing
|
||||
</behavior>
|
||||
<action>
|
||||
Create src/game/EventEmitter.ts and src/__tests__/EventEmitter.test.ts following TDD pattern.
|
||||
|
||||
TypedEventEmitter class requirements:
|
||||
1. Generic class: `TypedEventEmitter<T extends Record<string, unknown>>`
|
||||
2. Uses Node's EventEmitter internally (import from 'events')
|
||||
3. `on<K extends keyof T>(event: K, listener: (data: T[K]) => void): this`
|
||||
4. `emit<K extends keyof T>(event: K, data: T[K]): boolean`
|
||||
5. `off<K extends keyof T>(event: K, listener: (data: T[K]) => void): this`
|
||||
6. Type-safe: TypeScript infers payload types from event name
|
||||
|
||||
Implementation pattern (from RESEARCH.md):
|
||||
```typescript
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
export class TypedEventEmitter<T extends Record<string, unknown>> {
|
||||
private emitter = new EventEmitter();
|
||||
|
||||
on<K extends keyof T>(event: K, listener: (data: T[K]) => void): this {
|
||||
this.emitter.on(event as string, listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
emit<K extends keyof T>(event: K, data: T[K]): boolean {
|
||||
return this.emitter.emit(event as string, data);
|
||||
}
|
||||
|
||||
off<K extends keyof T>(event: K, listener: (data: T[K]) => void): this {
|
||||
this.emitter.off(event as string, listener);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Test file should:
|
||||
- Create emitter with a test event map
|
||||
- Verify on/emit/off work correctly
|
||||
- Test multiple listeners for same event
|
||||
- Verify type inference works
|
||||
|
||||
DO NOT implement custom event system - wrap Node's EventEmitter.
|
||||
DO NOT allow untyped event names - use keyof T constraint.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>npm run test -- --run src/__tests__/EventEmitter.test.ts</automated>
|
||||
</verify>
|
||||
<done>
|
||||
- TypedEventEmitter class exports from src/game/EventEmitter.ts
|
||||
- All tests in EventEmitter.test.ts pass
|
||||
- on(), emit(), off() methods work with type safety
|
||||
- Uses Node's EventEmitter internally
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 3: Implement Tile model class</name>
|
||||
<files>src/models/Tile.ts, src/__tests__/Tile.test.ts</files>
|
||||
<behavior>
|
||||
- Test 1: Tile constructor sets id, type, position, cleared
|
||||
- Test 2: emoji getter returns correct emoji from CONFIG
|
||||
- Test 3: isAdjacent() returns true for adjacent tiles, false otherwise
|
||||
- Test 4: cleared property defaults to false
|
||||
</behavior>
|
||||
<action>
|
||||
Create src/models/Tile.ts and src/__tests__/Tile.test.ts following TDD pattern.
|
||||
|
||||
Tile class requirements:
|
||||
1. Implements the Tile interface from src/types/index.ts
|
||||
2. Constructor: `constructor(id: string, type: number, position: TilePosition)`
|
||||
3. `cleared` property defaults to false
|
||||
4. Getter `emoji` returns `CONFIG.emojis[this.type]`
|
||||
5. Method `isAdjacent(other: Tile): boolean` - returns true if tiles are orthogonally adjacent (not diagonal)
|
||||
|
||||
Implementation pattern (from RESEARCH.md):
|
||||
```typescript
|
||||
import { TilePosition } from '../types';
|
||||
import { CONFIG } from '../config';
|
||||
|
||||
export class Tile {
|
||||
public cleared: boolean = false;
|
||||
|
||||
constructor(
|
||||
public readonly id: string,
|
||||
public readonly type: number,
|
||||
public readonly position: TilePosition
|
||||
) {}
|
||||
|
||||
get emoji(): string {
|
||||
return CONFIG.emojis[this.type];
|
||||
}
|
||||
|
||||
isAdjacent(other: Tile): boolean {
|
||||
const rowDiff = Math.abs(this.position.row - other.position.row);
|
||||
const colDiff = Math.abs(this.position.col - other.position.col);
|
||||
return (rowDiff === 1 && colDiff === 0) || (rowDiff === 0 && colDiff === 1);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Test file should cover:
|
||||
- Constructor assigns all properties correctly
|
||||
- emoji getter returns correct emoji for each type 0-15
|
||||
- isAdjacent returns true for (0,0) and (0,1)
|
||||
- isAdjacent returns false for (0,0) and (1,1) (diagonal)
|
||||
- isAdjacent returns false for (0,0) and (0,2) (not adjacent)
|
||||
- cleared defaults to false
|
||||
|
||||
DO NOT add methods not specified - keep Tile model minimal.
|
||||
DO NOT mutate position after construction - it's readonly.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>npm run test -- --run src/__tests__/Tile.test.ts</automated>
|
||||
</verify>
|
||||
<done>
|
||||
- Tile class exports from src/models/Tile.ts
|
||||
- All tests in Tile.test.ts pass
|
||||
- Tile implements Tile interface correctly
|
||||
- emoji getter returns correct emoji
|
||||
- isAdjacent() correctly identifies adjacent tiles
|
||||
</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
After all tasks complete:
|
||||
1. Run `npm run test -- --run` - all 3 test files pass
|
||||
2. Run `npx tsc --noEmit` - TypeScript compiles without errors
|
||||
3. Verify all classes export correctly from their files
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- [ ] GameLoop class with start/stop methods exists
|
||||
- [ ] GameLoop uses requestAnimationFrame (not setInterval)
|
||||
- [ ] TypedEventEmitter class with on/emit/off methods exists
|
||||
- [ ] TypedEventEmitter provides type-safe event handling
|
||||
- [ ] Tile class with id, type, position, cleared exists
|
||||
- [ ] Tile.emoji getter returns correct emoji
|
||||
- [ ] Tile.isAdjacent() correctly identifies orthogonal neighbors
|
||||
- [ ] All unit tests pass
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/01-core-foundation/01-02-SUMMARY.md`
|
||||
</output>
|
||||
@@ -0,0 +1,426 @@
|
||||
---
|
||||
phase: 01-core-foundation
|
||||
plan: 03
|
||||
type: execute
|
||||
wave: 3
|
||||
depends_on: [01-02]
|
||||
files_modified:
|
||||
- src/game/Game.ts
|
||||
- src/main.ts
|
||||
- src/__tests__/config.test.ts
|
||||
autonomous: false
|
||||
requirements: [CORE-01]
|
||||
user_setup: []
|
||||
must_haves:
|
||||
truths:
|
||||
- "Developer runs npm run dev and sees a Canvas with colored background"
|
||||
- "Game loop is running at 60fps (verifiable via console)"
|
||||
- "Event system is wired and functional"
|
||||
- "All tests pass including new config tests"
|
||||
artifacts:
|
||||
- path: "src/game/Game.ts"
|
||||
provides: "Main game orchestrator class"
|
||||
exports: ["Game"]
|
||||
- path: "src/main.ts"
|
||||
provides: "Application entry point"
|
||||
min_lines: 15
|
||||
- path: "src/__tests__/config.test.ts"
|
||||
provides: "Configuration validation tests"
|
||||
min_lines: 20
|
||||
key_links:
|
||||
- from: "src/main.ts"
|
||||
to: "src/game/Game.ts"
|
||||
via: "import and instantiation"
|
||||
pattern: "new Game"
|
||||
- from: "src/game/Game.ts"
|
||||
to: "src/game/GameLoop.ts"
|
||||
via: "composition"
|
||||
pattern: "new GameLoop"
|
||||
- from: "src/game/Game.ts"
|
||||
to: "src/game/EventEmitter.ts"
|
||||
via: "composition"
|
||||
pattern: "TypedEventEmitter<GameEvents>"
|
||||
- from: "src/game/Game.ts"
|
||||
to: "index.html canvas"
|
||||
via: "CanvasRenderingContext2D"
|
||||
pattern: "getElementById.*game"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Create the Game class that orchestrates all components and wire everything together in main.ts. Add configuration tests and perform human verification of the complete foundation.
|
||||
|
||||
Purpose: Integrate all Phase 1 components into a working application that can be verified visually and via tests.
|
||||
Output: Working game foundation with Canvas rendering, game loop, and event system - ready for Phase 2.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@./.claude/get-shit-done/workflows/execute-plan.md
|
||||
@./.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/01-core-foundation/01-CONTEXT.md
|
||||
@.planning/phases/01-core-foundation/01-RESEARCH.md
|
||||
@.planning/phases/01-core-foundation/01-02-PLAN.md
|
||||
</context>
|
||||
|
||||
<interfaces>
|
||||
<!-- Types and classes from prior plans that this plan uses -->
|
||||
|
||||
From src/types/index.ts:
|
||||
```typescript
|
||||
export interface GameEvents {
|
||||
'game:start': void;
|
||||
'game:tick': { deltaTime: number };
|
||||
'tile:selected': { tile: Tile; row: number; col: number };
|
||||
'tile:cleared': { tile: Tile };
|
||||
'game:score': { points: number };
|
||||
'game:over': { won: boolean };
|
||||
'error': Error;
|
||||
}
|
||||
```
|
||||
|
||||
From src/game/GameLoop.ts:
|
||||
```typescript
|
||||
export class GameLoop {
|
||||
constructor(update: (deltaTime: number) => void);
|
||||
start(): void;
|
||||
stop(): void;
|
||||
}
|
||||
```
|
||||
|
||||
From src/game/EventEmitter.ts:
|
||||
```typescript
|
||||
export class TypedEventEmitter<T extends Record<string, unknown>> {
|
||||
on<K extends keyof T>(event: K, listener: (data: T[K]) => void): this;
|
||||
emit<K extends keyof T>(event: K, data: T[K]): boolean;
|
||||
off<K extends keyof T>(event: K, listener: (data: T[K]) => void): this;
|
||||
}
|
||||
```
|
||||
|
||||
From src/config.ts:
|
||||
```typescript
|
||||
export const CONFIG = {
|
||||
grid: { rows: 10, cols: 16, totalTiles: 160, pairsPerType: 10 },
|
||||
tile: { size: 48, gap: 4, cornerRadius: 8 },
|
||||
emojis: string[], // 16 emojis
|
||||
colors: { background: '#1a1a2e', tile: '#16213e', ... },
|
||||
} as const;
|
||||
```
|
||||
</interfaces>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Create Game class orchestrator</name>
|
||||
<files>src/game/Game.ts</files>
|
||||
<behavior>
|
||||
- Test 1: Game constructor initializes canvas, context, loop, and events
|
||||
- Test 2: Game.start() starts the game loop
|
||||
- Test 3: Game.render() clears canvas with background color
|
||||
- Test 4: Game emits 'game:tick' events during update
|
||||
</behavior>
|
||||
<action>
|
||||
Create src/game/Game.ts that orchestrates all components.
|
||||
|
||||
Game class requirements:
|
||||
1. Constructor:
|
||||
- Gets canvas element by id 'game'
|
||||
- Gets 2D rendering context
|
||||
- Sets up canvas size based on CONFIG.grid and CONFIG.tile
|
||||
- Creates GameLoop instance with this.update as callback
|
||||
- Creates TypedEventEmitter<GameEvents> instance
|
||||
- Handles device pixel ratio for sharp rendering
|
||||
|
||||
2. Properties:
|
||||
- `canvas: HTMLCanvasElement`
|
||||
- `ctx: CanvasRenderingContext2D`
|
||||
- `loop: GameLoop`
|
||||
- `events: TypedEventEmitter<GameEvents>`
|
||||
|
||||
3. Methods:
|
||||
- `start()`: Starts the game loop, emits 'game:start'
|
||||
- `stop()`: Stops the game loop
|
||||
- `update(deltaTime: number)`: Called by loop, emits 'game:tick'
|
||||
- `render()`: Clears canvas with CONFIG.colors.background
|
||||
|
||||
4. Canvas setup:
|
||||
- Calculate width: cols * (size + gap) + gap
|
||||
- Calculate height: rows * (size + gap) + gap
|
||||
- Handle devicePixelRatio for sharp rendering
|
||||
- Scale context by dpr
|
||||
|
||||
Implementation skeleton:
|
||||
```typescript
|
||||
import { GameLoop } from './GameLoop';
|
||||
import { TypedEventEmitter } from './EventEmitter';
|
||||
import { GameEvents } from '../types';
|
||||
import { CONFIG } from '../config';
|
||||
|
||||
export class Game {
|
||||
readonly canvas: HTMLCanvasElement;
|
||||
readonly ctx: CanvasRenderingContext2D;
|
||||
readonly loop: GameLoop;
|
||||
readonly events: TypedEventEmitter<GameEvents>;
|
||||
|
||||
constructor() {
|
||||
this.canvas = document.getElementById('game') as HTMLCanvasElement;
|
||||
this.ctx = this.canvas.getContext('2d')!;
|
||||
this.events = new TypedEventEmitter<GameEvents>();
|
||||
this.setupCanvas();
|
||||
this.loop = new GameLoop(this.update.bind(this));
|
||||
}
|
||||
|
||||
private setupCanvas(): void {
|
||||
const { cols, rows } = CONFIG.grid;
|
||||
const { size, gap } = CONFIG.tile;
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
|
||||
const width = cols * (size + gap) + gap;
|
||||
const height = rows * (size + gap) + gap;
|
||||
|
||||
this.canvas.width = width * dpr;
|
||||
this.canvas.height = height * dpr;
|
||||
this.canvas.style.width = `${width}px`;
|
||||
this.canvas.style.height = `${height}px`;
|
||||
this.ctx.scale(dpr, dpr);
|
||||
}
|
||||
|
||||
start(): void {
|
||||
this.events.emit('game:start', undefined as never);
|
||||
this.loop.start();
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
this.loop.stop();
|
||||
}
|
||||
|
||||
private update(deltaTime: number): void {
|
||||
this.events.emit('game:tick', { deltaTime });
|
||||
this.render();
|
||||
}
|
||||
|
||||
private render(): void {
|
||||
this.ctx.fillStyle = CONFIG.colors.background;
|
||||
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
DO NOT add tile rendering - that's Phase 2.
|
||||
DO NOT add input handling - that's Phase 2.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>npm run test -- --run 2>&1 | tail -20</automated>
|
||||
</verify>
|
||||
<done>
|
||||
- Game class exports from src/game/Game.ts
|
||||
- Game initializes canvas, loop, and events
|
||||
- Game.start() begins the loop
|
||||
- Game.render() clears canvas with background color
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: Wire main.ts entry point</name>
|
||||
<files>src/main.ts</files>
|
||||
<behavior>
|
||||
- Test 1: main.ts imports and instantiates Game
|
||||
- Test 2: Game.start() is called on DOMContentLoaded
|
||||
- Test 3: Error handling logs to console
|
||||
</behavior>
|
||||
<action>
|
||||
Update src/main.ts to be the application entry point.
|
||||
|
||||
Requirements:
|
||||
1. Import Game class
|
||||
2. Wait for DOMContentLoaded event
|
||||
3. Create Game instance
|
||||
4. Call game.start()
|
||||
5. Add error event listener on game.events
|
||||
6. Log initialization to console (helps verify 60fps in dev tools)
|
||||
|
||||
Implementation:
|
||||
```typescript
|
||||
import { Game } from './game/Game';
|
||||
|
||||
let game: Game | null = null;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
try {
|
||||
game = new Game();
|
||||
|
||||
// Handle errors
|
||||
game.events.on('error', (err) => {
|
||||
console.error('Game error:', err);
|
||||
});
|
||||
|
||||
// Log tick events in development (comment out in production)
|
||||
game.events.on('game:tick', ({ deltaTime }) => {
|
||||
// Uncomment for debugging: console.log('Tick:', deltaTime.toFixed(2), 'ms');
|
||||
});
|
||||
|
||||
game.start();
|
||||
console.log('Game initialized - Canvas should show with background color');
|
||||
} catch (err) {
|
||||
console.error('Failed to initialize game:', err);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
DO NOT add complex initialization logic.
|
||||
DO NOT add hot module replacement handling.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>npm run test -- --run 2>&1 | tail -10</automated>
|
||||
</verify>
|
||||
<done>
|
||||
- src/main.ts imports and instantiates Game
|
||||
- Game starts on DOMContentLoaded
|
||||
- Error events are logged to console
|
||||
- Application runs without errors
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 3: Add configuration tests</name>
|
||||
<files>src/__tests__/config.test.ts</files>
|
||||
<behavior>
|
||||
- Test 1: CONFIG.grid.rows equals 10
|
||||
- Test 2: CONFIG.grid.cols equals 16
|
||||
- Test 3: CONFIG.grid.totalTiles equals rows * cols (160)
|
||||
- Test 4: CONFIG.emojis has exactly 16 unique emojis
|
||||
- Test 5: CONFIG.tile.size and gap are positive
|
||||
- Test 6: All colors are valid hex strings
|
||||
</behavior>
|
||||
<action>
|
||||
Create src/__tests__/config.test.ts to validate configuration constants.
|
||||
|
||||
Test cases:
|
||||
```typescript
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { CONFIG } from '../config';
|
||||
|
||||
describe('CONFIG', () => {
|
||||
describe('grid', () => {
|
||||
it('should have 10 rows', () => {
|
||||
expect(CONFIG.grid.rows).toBe(10);
|
||||
});
|
||||
|
||||
it('should have 16 columns', () => {
|
||||
expect(CONFIG.grid.cols).toBe(16);
|
||||
});
|
||||
|
||||
it('should have correct total tiles', () => {
|
||||
expect(CONFIG.grid.totalTiles).toBe(CONFIG.grid.rows * CONFIG.grid.cols);
|
||||
});
|
||||
|
||||
it('should have 10 pairs per type', () => {
|
||||
expect(CONFIG.grid.pairsPerType).toBe(10);
|
||||
});
|
||||
});
|
||||
|
||||
describe('tile', () => {
|
||||
it('should have positive size', () => {
|
||||
expect(CONFIG.tile.size).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should have positive gap', () => {
|
||||
expect(CONFIG.tile.gap).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should have positive corner radius', () => {
|
||||
expect(CONFIG.tile.cornerRadius).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('emojis', () => {
|
||||
it('should have exactly 16 emojis', () => {
|
||||
expect(CONFIG.emojis).toHaveLength(16);
|
||||
});
|
||||
|
||||
it('should have all unique emojis', () => {
|
||||
const unique = new Set(CONFIG.emojis);
|
||||
expect(unique.size).toBe(16);
|
||||
});
|
||||
});
|
||||
|
||||
describe('colors', () => {
|
||||
it('should have valid hex background color', () => {
|
||||
expect(CONFIG.colors.background).toMatch(/^#[0-9a-fA-F]{6}$/);
|
||||
});
|
||||
|
||||
it('should have valid hex tile color', () => {
|
||||
expect(CONFIG.colors.tile).toMatch(/^#[0-9a-fA-F]{6}$/);
|
||||
});
|
||||
|
||||
it('should have valid hex selection color', () => {
|
||||
expect(CONFIG.colors.selection).toMatch(/^#[0-9a-fA-F]{6}$/);
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
DO NOT add tests for values that may change during tuning.
|
||||
DO test invariants (counts, formats, positivity).
|
||||
</action>
|
||||
<verify>
|
||||
<automated>npm run test -- --run src/__tests__/config.test.ts</automated>
|
||||
</verify>
|
||||
<done>
|
||||
- src/__tests__/config.test.ts exists with all tests
|
||||
- All configuration tests pass
|
||||
- Grid dimensions are validated
|
||||
- Emoji count and uniqueness verified
|
||||
- Color formats validated
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<name>Task 4: Human verification of Phase 1 foundation</name>
|
||||
<files></files>
|
||||
<action>Human verification checkpoint</action>
|
||||
<what-built>Complete Phase 1 foundation: Vite project with Canvas, game loop at 60fps, typed event system, Tile model, and all tests passing.</what-built>
|
||||
<how-to-verify>
|
||||
1. Run `npm run dev` in terminal
|
||||
2. Open browser to http://localhost:5173
|
||||
3. Verify you see a Canvas with dark blue background (#1a1a2e)
|
||||
4. Open browser DevTools (F12) -> Console
|
||||
5. Verify "Game initialized" message appears
|
||||
6. Run `npm run test -- --run` in a new terminal
|
||||
7. Verify all tests pass (config, Tile, GameLoop, EventEmitter)
|
||||
</how-to-verify>
|
||||
<verify>
|
||||
<automated>echo "Manual verification required - see how-to-verify steps"</automated>
|
||||
</verify>
|
||||
<done>Human has verified Canvas rendering, console output, and all tests pass</done>
|
||||
<resume-signal>Type "approved" if all verifications pass, or describe any issues found.</resume-signal>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
After all automated tasks complete:
|
||||
1. Run `npm run test -- --run` - all tests pass
|
||||
2. Run `npx tsc --noEmit` - TypeScript compiles without errors
|
||||
3. Run `npm run dev` - dev server starts
|
||||
4. Open browser - Canvas with background color visible
|
||||
5. Console shows "Game initialized" message
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- [ ] Game class orchestrates loop, events, and canvas
|
||||
- [ ] main.ts entry point initializes and starts game
|
||||
- [ ] All configuration tests pass
|
||||
- [ ] All prior tests still pass (GameLoop, EventEmitter, Tile)
|
||||
- [ ] `npm run dev` shows Canvas with colored background
|
||||
- [ ] Console shows game initialization message
|
||||
- [ ] Human verification checkpoint approved
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/01-core-foundation/01-03-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user