227 lines
6.5 KiB
Markdown
227 lines
6.5 KiB
Markdown
# Conway Life Demo Implementation Plan
|
|
|
|
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
|
|
**Goal:** Build a direct-open Conway's Game of Life demo page with a modern visualization style, preset patterns, canvas-based simulation, and a clean teaching-plus-exploration flow.
|
|
|
|
**Architecture:** Keep the page build-free and local-file friendly: semantic markup in `index.html`, visual styling in `styles.css`, and a single browser script in `app.js`. Put Conway engine logic and state helpers in testable pure functions exported from `app.js`, while the DOM/controller layer wires those functions to the canvas UI.
|
|
|
|
**Tech Stack:** HTML5, CSS3, vanilla JavaScript, Canvas API, Node.js built-in test runner (`node --test`)
|
|
|
|
---
|
|
|
|
Note: this workspace is not a git repository, so commit steps are intentionally omitted.
|
|
|
|
## File Structure
|
|
|
|
- Create: `index.html` - page structure, content sections, script/style includes
|
|
- Create: `styles.css` - theme variables, layout, cards, control panel, responsive styling, animation polish
|
|
- Create: `app.js` - Conway engine, preset definitions, state helpers, canvas rendering, UI events
|
|
- Create: `tests/life-demo.test.js` - Node tests for pure simulation and state logic
|
|
|
|
## Chunk 1: Build and test the Conway engine
|
|
|
|
### Task 1: Create the failing engine tests
|
|
|
|
**Files:**
|
|
- Create: `tests/life-demo.test.js`
|
|
- Test: `tests/life-demo.test.js`
|
|
|
|
- [ ] **Step 1: Write the failing test**
|
|
|
|
```js
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const {
|
|
createEmptyGrid,
|
|
stepGrid,
|
|
stampPattern,
|
|
PATTERNS,
|
|
} = require('../app.js');
|
|
|
|
test('blinker rotates after one generation', () => {
|
|
const grid = createEmptyGrid(5, 5);
|
|
grid[2][1] = 1;
|
|
grid[2][2] = 1;
|
|
grid[2][3] = 1;
|
|
|
|
const next = stepGrid(grid);
|
|
|
|
assert.deepEqual(next[1], [0, 0, 1, 0, 0]);
|
|
});
|
|
```
|
|
|
|
- [ ] **Step 2: Run test to verify it fails**
|
|
|
|
Run: `node --test tests/life-demo.test.js`
|
|
Expected: FAIL because `app.js` or the exported functions do not exist yet.
|
|
|
|
- [ ] **Step 3: Write minimal implementation**
|
|
|
|
```js
|
|
function createEmptyGrid(rows, cols) {
|
|
return Array.from({ length: rows }, () => Array(cols).fill(0));
|
|
}
|
|
```
|
|
|
|
Implement the smallest export set needed to pass:
|
|
- `createEmptyGrid(rows, cols)`
|
|
- `countLiveNeighbors(grid, row, col)`
|
|
- `stepGrid(grid)`
|
|
- `stampPattern(grid, pattern, offsetRow, offsetCol)`
|
|
- `PATTERNS` for `glider`, `pulsar`, and `gosperGliderGun`
|
|
|
|
- [ ] **Step 4: Run test to verify it passes**
|
|
|
|
Run: `node --test tests/life-demo.test.js`
|
|
Expected: PASS for the engine tests.
|
|
|
|
## Chunk 2: Add state helpers and page shell
|
|
|
|
### Task 2: Add failing tests for state helpers
|
|
|
|
**Files:**
|
|
- Modify: `tests/life-demo.test.js`
|
|
- Modify: `app.js`
|
|
- Create: `index.html`
|
|
- Create: `styles.css`
|
|
|
|
- [ ] **Step 1: Write the failing test**
|
|
|
|
Add tests for:
|
|
|
|
```js
|
|
test('createInitialState seeds the default preset and starts paused', () => {
|
|
const state = createInitialState({
|
|
rows: 20,
|
|
cols: 20,
|
|
defaultPattern: 'pulsar',
|
|
});
|
|
|
|
assert.equal(state.running, false);
|
|
assert.equal(state.selectedPattern, 'pulsar');
|
|
assert.equal(state.generation, 0);
|
|
assert.ok(state.liveCount > 0);
|
|
});
|
|
```
|
|
|
|
- [ ] **Step 2: Run test to verify it fails**
|
|
|
|
Run: `node --test tests/life-demo.test.js`
|
|
Expected: FAIL because `createInitialState` is not implemented yet.
|
|
|
|
- [ ] **Step 3: Write minimal implementation**
|
|
|
|
Implement pure helpers:
|
|
- `cloneGrid(grid)`
|
|
- `countLiveCells(grid)`
|
|
- `createInitialState({ rows, cols, defaultPattern })`
|
|
- `randomizeGrid(grid, probability)`
|
|
- `toggleCell(grid, row, col, forcedValue)`
|
|
|
|
At the same time, scaffold:
|
|
- `index.html` with Hero, Lab, Rules, and Presets sections
|
|
- `styles.css` with the modern visualization theme variables and responsive grid
|
|
|
|
- [ ] **Step 4: Run test to verify it passes**
|
|
|
|
Run: `node --test tests/life-demo.test.js`
|
|
Expected: PASS for the new state-helper tests.
|
|
|
|
## Chunk 3: Wire controls, canvas rendering, and preset switching
|
|
|
|
### Task 3: Add failing tests for UI-facing state transitions
|
|
|
|
**Files:**
|
|
- Modify: `tests/life-demo.test.js`
|
|
- Modify: `app.js`
|
|
- Modify: `index.html`
|
|
- Modify: `styles.css`
|
|
|
|
- [ ] **Step 1: Write the failing test**
|
|
|
|
Add tests for:
|
|
|
|
```js
|
|
test('applyPreset replaces the grid, pauses playback, and resets generation', () => {
|
|
const state = {
|
|
...createInitialState({ rows: 25, cols: 25, defaultPattern: 'glider' }),
|
|
running: true,
|
|
generation: 12,
|
|
};
|
|
|
|
const next = applyPreset(state, 'gosperGliderGun');
|
|
|
|
assert.equal(next.running, false);
|
|
assert.equal(next.generation, 0);
|
|
assert.equal(next.selectedPattern, 'gosperGliderGun');
|
|
assert.ok(next.liveCount > state.liveCount);
|
|
});
|
|
```
|
|
|
|
- [ ] **Step 2: Run test to verify it fails**
|
|
|
|
Run: `node --test tests/life-demo.test.js`
|
|
Expected: FAIL because `applyPreset` is not implemented yet.
|
|
|
|
- [ ] **Step 3: Write minimal implementation**
|
|
|
|
Implement:
|
|
- `applyPreset(state, patternName)`
|
|
- `advanceState(state)`
|
|
- `setSpeed(state, speed)`
|
|
- `getSpeedLabel(speed)`
|
|
|
|
Then wire browser behavior:
|
|
- draw the grid on a `canvas`
|
|
- support play/pause, step, clear, randomize, reset preset, and speed controls
|
|
- support click and drag painting on the canvas
|
|
- update status text, generation count, live-count, and active preset styling
|
|
|
|
- [ ] **Step 4: Run test to verify it passes**
|
|
|
|
Run: `node --test tests/life-demo.test.js`
|
|
Expected: PASS for the state-transition tests.
|
|
|
|
## Chunk 4: Polish visual details and run end-to-end verification
|
|
|
|
### Task 4: Finish presentation details and verify manually
|
|
|
|
**Files:**
|
|
- Modify: `index.html`
|
|
- Modify: `styles.css`
|
|
- Modify: `app.js`
|
|
- Test: `tests/life-demo.test.js`
|
|
|
|
- [ ] **Step 1: Refine the visuals**
|
|
|
|
Complete:
|
|
- luminous background gradients and grid accents
|
|
- polished cards and control panel surfaces
|
|
- hover/focus states
|
|
- responsive stacking for smaller screens
|
|
- subtle canvas/section entrance motion
|
|
|
|
- [ ] **Step 2: Run automated tests**
|
|
|
|
Run: `node --test tests/life-demo.test.js`
|
|
Expected: PASS with zero failures.
|
|
|
|
- [ ] **Step 3: Run manual verification**
|
|
|
|
Open: `index.html`
|
|
|
|
Verify:
|
|
- page opens directly without a dev server
|
|
- pulsar loads by default and the simulation starts paused
|
|
- controls behave correctly
|
|
- each preset loads and pauses correctly
|
|
- editing by click/drag works
|
|
- desktop and narrow layouts both remain usable
|
|
|
|
- [ ] **Step 4: Record any gaps**
|
|
|
|
If a browser-only issue appears, fix it and rerun:
|
|
- `node --test tests/life-demo.test.js`
|
|
- manual browser verification of `index.html`
|