Conway's Game of Life · gemini-3-flash-preview
Run Conway's Game of Life on a full-screen canvas grid with click-and-drag to toggle cells, plus play/pause, step, randomize, and clear controls.
transcript · 6 messages
Run Conway's Game of Life on a full-screen canvas grid with click-and-drag to toggle cells, plus play/pause, step, randomize, and clear controls.
thinking
**Building the Game** I'm focusing on creating a self-contained, single-file HTML application for Conway's Game of Life. My current work involves setting up a full-screen canvas and implementing the click-and-drag functionality for user interaction within the grid. **Developing the Application** My focus has shifted to the core mechanics of Conway's Game of Life. I'm now implementing the generation logic and the animation loop, ensuring smooth transitions between states, while also refining the cell toggling interaction.
index.html (9798 bytes)Successfully wrote 9798 bytes to index.html
thinking
**Implementing Full-Screen Game of Life** I've got the full-screen canvas rendering and the grid logic is humming along nicely with `Uint8Array`. Basic controls like play, pause, step, randomize, and clear are functional, and I've even incorporated speed adjustment. I can now click and drag to toggle cells.
{"edits":[{"newText":" let grid = [];\n let nextGrid = [];\n let rows, cols;\n let isPlaying = false;\n lSuccessfully replaced 2 block(s) in index.html.
The Conway's Game of Life app is now ready in `index.html`. It features a full-screen canvas with a toroidal grid (wrapping at edges), interactive cell painting/erasing, and a control bar for simulation speed and state management. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Conway's Game of Life</title> <style> body, html { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background-color: #1a1a1a; color: #efefef; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } canvas { display: block; cursor: crosshair; } .controls { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); background: rgba(40, 40, 40, 0.85); padding: 10px 20px; border-radius: 30px; display: flex; gap: 10px; align-items: center; backdrop-filter: blur(5px); box-shadow: 0 4px 15px rgba(0,0,0,0.5); user-select: none; z-index: 100; } button { background: #444; border: none; color: white; padding: 8px 15px; border-radius: 5px; cursor: pointer; font-size: 14px; transition: background 0.2s; min-width: 60px; } button:hover { background: #666; } button:active { background: #888; } button.primary { background: #007bff; } button.primary:hover { background: #008cff; } .stats { position: absolute; top: 10px; left: 10px; font-size: 12px; color: #888; pointer-events: none; } input[type=range] { cursor: pointer; } </style> </head> <body> <div class="stats" id="stats">Generation: 0 | Population: 0</div> <canvas id="canvas"></canvas> <div class="controls"> <button id="playPauseBtn" class="primary">Play</button> <button id="stepBtn">Step</button> <button id="randomBtn">Randomize</button> <button id="clearBtn">Clear</button> <div style="margin-left: 10px; display: flex; align-items: center; gap: 5px;"> <span style="font-size: 12px; color: #ccc;">Speed</span> <input type="range" id="speedRange" min="1" max="60" value="30" style="width: 80px;"> </div> </div> <script> const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); const statsEl = document.getElementById('stats'); const playPauseBtn = document.getElementById('playPauseBtn'); const stepBtn = document.getElementById('stepBtn'); const randomBtn = document.getElementById('randomBtn'); const clearBtn = document.getElementById('clearBtn'); const speedRange = document.getElementById('speedRange'); const CELL_SIZE = 10; let grid = []; let nextGrid = []; let rows, cols; let isPlaying = false; let generation = 0; let lastTimestamp = 0; // Interaction state let isMouseDown = false; let paintMode = true; // true = paint, false = erase function init() { window.addEventListener('resize', resize); resize(); canvas.addEventListener('mousedown', onMouseDown); window.addEventListener('mousemove', onMouseMove); window.addEventListener('mouseup', onMouseUp); playPauseBtn.addEventListener('click', togglePlay); stepBtn.addEventListener('click', () => { nextGeneration(); draw(); }); randomBtn.addEventListener('click', () => { randomize(); draw(); }); clearBtn.addEventListener('click', () => { clearGrid(); draw(); }); randomize(); requestAnimationFrame(loop); } function resize() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; const newCols = Math.floor(canvas.width / CELL_SIZE); const newRows = Math.floor(canvas.height / CELL_SIZE); const newGrid = Array.from({ length: newRows }, () => new Uint8Array(newCols)); const newNextGrid = Array.from({ length: newRows }, () => new Uint8Array(newCols)); // Preserve existing grid state if (grid.length > 0) { for (let r = 0; r < Math.min(rows, newRows); r++) { for (let c = 0; c < Math.min(cols, newCols); c++) { newGrid[r][c] = grid[r][c]; } } } grid = newGrid; nextGrid = newNextGrid; rows = newRows; cols = newCols; } function randomize() { for (let r = 0; r < rows; r++) { for (let c = 0; c < cols; c++) { grid[r][c] = Math.random() > 0.85 ? 1 : 0; } } generation = 0; } function clearGrid() { for (let r = 0; r < rows; r++) { grid[r].fill(0); } generation = 0; if (isPlaying) togglePlay(); } function togglePlay() { isPlaying = !isPlaying; playPauseBtn.textContent = isPlaying ? 'Pause' : 'Play'; playPauseBtn.classList.toggle('primary', !isPlaying); } function getCellCoords(e) { const x = e.clientX; const y = e.clientY; const c = Math.floor(x / CELL_SIZE); const r = Math.floor(y / CELL_SIZE); return { r, c }; } function onMouseDown(e) { isMouseDown = true; const { r, c } = getCellCoords(e); if (r >= 0 && r < rows && c >= 0 && c < cols) { paintMode = grid[r][c] === 0; grid[r][c] = paintMode ? 1 : 0; draw(); } } function onMouseMove(e) { if (!isMouseDown) return; const { r, c } = getCellCoords(e); if (r >= 0 && r < rows && c >= 0 && c < cols) { grid[r][c] = paintMode ? 1 : 0; draw(); } } function onMouseUp() { isMouseDown = false; } function countNeighbors(r, c) { let count = 0; for (let i = -1; i <= 1; i++) { for (let j = -1; j <= 1; j++) { if (i === 0 && j === 0) continue; const rr = (r + i + rows) % rows; const cc = (c + j + cols) % cols; count += grid[rr][cc]; } } return count; } function nextGeneration() { let population = 0; for (let r = 0; r < rows; r++) { for (let c = 0; c < cols; c++) { const neighbors = countNeighbors(r, c); const state = grid[r][c]; if (state === 1) { nextGrid[r][c] = (neighbors === 2 || neighbors === 3) ? 1 : 0; } else { nextGrid[r][c] = (neighbors === 3) ? 1 : 0; } if (nextGrid[r][c] === 1) population++; } } // Swap grids let temp = grid; grid = nextGrid; nextGrid = temp; generation++; updateStats(population); } function updateStats(population) { statsEl.textContent = `Generation: ${generation} | Population: ${population}`; } function draw() { ctx.fillStyle = '#1a1a1a'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = '#4a9eff'; let population = 0; for (let r = 0; r < rows; r++) { for (let c = 0; c < cols; c++) { if (grid[r][c] === 1) { ctx.fillRect(c * CELL_SIZE + 1, r * CELL_SIZE + 1, CELL_SIZE - 1, CELL_SIZE - 1); population++; } } } if (CELL_SIZE > 5) { ctx.strokeStyle = '#222'; ctx.lineWidth = 0.5; ctx.beginPath(); for (let x = 0; x <= canvas.width; x += CELL_SIZE) { ctx.moveTo(x, 0); ctx.lineTo(x, canvas.height); } for (let y = 0; y <= canvas.height; y += CELL_SIZE) { ctx.moveTo(0, y); ctx.lineTo(canvas.width, y); } ctx.stroke(); } if (!isPlaying) updateStats(population); } function loop(timestamp) { const speed = 1000 / parseInt(speedRange.value); if (isPlaying && timestamp - lastTimestamp > speed) { nextGeneration(); draw(); lastTimestamp = timestamp; } else if (!isPlaying && isMouseDown) { draw(); } requestAnimationFrame(loop); } init(); </script> </body> </html> ```
TAKE IT TO CODE
A single self-contained HTML file — copy it and open in any browser, or reuse the prompt with your own pi-agent run.
RUN
EVALUATION
Game of Life grid stays empty until click randomizes it, then continues evolving with visible generation/population counters and controls.
Vision grade · anthropic/claude-sonnet-5
MEASUREMENTS
Raw readings from the capture, not a verdict — the grade above is the only judgement. Percentages are how much of the screen changed; the measure under-reports sparse motion on a dark background.