// Conway's Game of Life - Interactive simulation page const ConwaysGameOfLife = () => { const { Box, Typography, Paper, Slider, Button, Container, ButtonGroup, Tooltip } = MaterialUI; const CANVAS_WIDTH = 800; const CANVAS_HEIGHT = 500; const RANDOM_FILL_PROBABILITY = 0.3; const canvasRef = React.useRef(null); const cellGridRef = React.useRef(null); const playingRef = React.useRef(false); const rafRef = React.useRef(null); const lastTickRef = React.useRef(0); const [rows, setRows] = React.useState(50); const [cols, setCols] = React.useState(80); const [playing, setPlaying] = React.useState(false); const [generation, setGeneration] = React.useState(0); const [speed, setSpeed] = React.useState(10); // generations per second const [liveCells, setLiveCells] = React.useState(0); // Mouse interaction state const drawingRef = React.useRef(false); const drawModeRef = React.useRef(null); // 'birth' or 'kill' // ---------- Grid helpers ---------- const makeGrid = (r, c) => { const g = new Uint8Array(r * c); return g; }; const countNeighbors = (grid, r, c, numRows, numCols) => { let count = 0; for (let dr = -1; dr <= 1; dr++) { for (let dc = -1; dc <= 1; dc++) { if (dr === 0 && dc === 0) continue; const nr = r + dr; const nc = c + dc; if (nr >= 0 && nr < numRows && nc >= 0 && nc < numCols) { count += grid[nr * numCols + nc]; } } } return count; }; const stepGrid = (grid, numRows, numCols) => { const next = new Uint8Array(numRows * numCols); for (let r = 0; r < numRows; r++) { for (let c = 0; c < numCols; c++) { const idx = r * numCols + c; const n = countNeighbors(grid, r, c, numRows, numCols); if (grid[idx]) { next[idx] = (n === 2 || n === 3) ? 1 : 0; } else { next[idx] = (n === 3) ? 1 : 0; } } } return next; }; const countLive = (grid) => { let count = 0; for (let i = 0; i < grid.length; i++) count += grid[i]; return count; }; // ---------- Rendering ---------- const drawGrid = React.useCallback((grid, numRows, numCols) => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); const cellW = canvas.width / numCols; const cellH = canvas.height / numRows; ctx.clearRect(0, 0, canvas.width, canvas.height); // Draw live cells ctx.fillStyle = '#4caf50'; for (let r = 0; r < numRows; r++) { for (let c = 0; c < numCols; c++) { if (grid[r * numCols + c]) { ctx.fillRect(c * cellW, r * cellH, cellW, cellH); } } } // Draw grid lines ctx.strokeStyle = 'rgba(128,128,128,0.25)'; ctx.lineWidth = 0.5; for (let r = 0; r <= numRows; r++) { ctx.beginPath(); ctx.moveTo(0, r * cellH); ctx.lineTo(canvas.width, r * cellH); ctx.stroke(); } for (let c = 0; c <= numCols; c++) { ctx.beginPath(); ctx.moveTo(c * cellW, 0); ctx.lineTo(c * cellW, canvas.height); ctx.stroke(); } }, []); // ---------- Initialization ---------- const initGrid = React.useCallback((numRows, numCols) => { const grid = makeGrid(numRows, numCols); cellGridRef.current = grid; setGeneration(0); setLiveCells(0); drawGrid(grid, numRows, numCols); }, [drawGrid]); React.useEffect(() => { initGrid(rows, cols); return () => { if (rafRef.current) cancelAnimationFrame(rafRef.current); }; }, [rows, cols, initGrid]); // ---------- Animation loop ---------- React.useEffect(() => { playingRef.current = playing; if (!playing) { if (rafRef.current) { cancelAnimationFrame(rafRef.current); rafRef.current = null; } return; } const tick = (timestamp) => { if (!playingRef.current) return; const interval = 1000 / speed; if (timestamp - lastTickRef.current >= interval) { lastTickRef.current = timestamp; const grid = cellGridRef.current; const next = stepGrid(grid, rows, cols); cellGridRef.current = next; setGeneration(g => g + 1); setLiveCells(countLive(next)); drawGrid(next, rows, cols); } rafRef.current = requestAnimationFrame(tick); }; lastTickRef.current = performance.now(); rafRef.current = requestAnimationFrame(tick); return () => { if (rafRef.current) { cancelAnimationFrame(rafRef.current); rafRef.current = null; } }; }, [playing, speed, rows, cols, drawGrid]); // ---------- Mouse interaction ---------- const getCellFromEvent = (e) => { const canvas = canvasRef.current; if (!canvas) return null; const rect = canvas.getBoundingClientRect(); const scaleX = canvas.width / rect.width; const scaleY = canvas.height / rect.height; const x = (e.clientX - rect.left) * scaleX; const y = (e.clientY - rect.top) * scaleY; const cellW = canvas.width / cols; const cellH = canvas.height / rows; const c = Math.floor(x / cellW); const r = Math.floor(y / cellH); if (r >= 0 && r < rows && c >= 0 && c < cols) return { r, c }; return null; }; const toggleCell = (r, c) => { const grid = cellGridRef.current; if (!grid) return; const idx = r * cols + c; grid[idx] = grid[idx] ? 0 : 1; setLiveCells(countLive(grid)); drawGrid(grid, rows, cols); }; const setCell = (r, c, value) => { const grid = cellGridRef.current; if (!grid) return; const idx = r * cols + c; if (grid[idx] !== value) { grid[idx] = value; setLiveCells(countLive(grid)); drawGrid(grid, rows, cols); } }; const handleMouseDown = (e) => { const cell = getCellFromEvent(e); if (!cell) return; drawingRef.current = true; const idx = cell.r * cols + cell.c; const grid = cellGridRef.current; // Determine draw mode: if clicking a live cell, we kill; otherwise we birth drawModeRef.current = grid[idx] ? 'kill' : 'birth'; toggleCell(cell.r, cell.c); }; const handleMouseMove = (e) => { if (!drawingRef.current) return; const cell = getCellFromEvent(e); if (!cell) return; const value = drawModeRef.current === 'birth' ? 1 : 0; setCell(cell.r, cell.c, value); }; const handleMouseUp = () => { drawingRef.current = false; drawModeRef.current = null; }; // ---------- Control handlers ---------- const handleStep = () => { const grid = cellGridRef.current; const next = stepGrid(grid, rows, cols); cellGridRef.current = next; setGeneration(g => g + 1); setLiveCells(countLive(next)); drawGrid(next, rows, cols); }; const handleClear = () => { setPlaying(false); initGrid(rows, cols); }; const handleRandom = () => { const grid = makeGrid(rows, cols); for (let i = 0; i < grid.length; i++) { grid[i] = Math.random() < RANDOM_FILL_PROBABILITY ? 1 : 0; } cellGridRef.current = grid; setGeneration(0); setLiveCells(countLive(grid)); drawGrid(grid, rows, cols); }; // Preset patterns const placePattern = (pattern) => { const grid = makeGrid(rows, cols); const startR = Math.floor(rows / 2) - Math.floor(pattern.length / 2); const startC = Math.floor(cols / 2) - Math.floor(pattern[0].length / 2); for (let r = 0; r < pattern.length; r++) { for (let c = 0; c < pattern[r].length; c++) { const gr = startR + r; const gc = startC + c; if (gr >= 0 && gr < rows && gc >= 0 && gc < cols) { grid[gr * cols + gc] = pattern[r][c]; } } } cellGridRef.current = grid; setGeneration(0); setLiveCells(countLive(grid)); drawGrid(grid, rows, cols); }; const presets = [ { name: 'Glider', pattern: [ [0,1,0], [0,0,1], [1,1,1] ] }, { name: 'Blinker', pattern: [ [1,1,1] ] }, { name: 'Pulsar', pattern: [ [0,0,1,1,1,0,0,0,1,1,1,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0], [1,0,0,0,0,1,0,1,0,0,0,0,1], [1,0,0,0,0,1,0,1,0,0,0,0,1], [1,0,0,0,0,1,0,1,0,0,0,0,1], [0,0,1,1,1,0,0,0,1,1,1,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,1,1,1,0,0,0,1,1,1,0,0], [1,0,0,0,0,1,0,1,0,0,0,0,1], [1,0,0,0,0,1,0,1,0,0,0,0,1], [1,0,0,0,0,1,0,1,0,0,0,0,1], [0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,1,1,1,0,0,0,1,1,1,0,0] ] }, { name: 'Gosper Glider Gun', pattern: [ [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1], [0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1], [1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [1,1,0,0,0,0,0,0,0,0,1,0,0,0,1,0,1,1,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] ] }, { name: 'Lightweight Spaceship', pattern: [ [0,1,0,0,1], [1,0,0,0,0], [1,0,0,0,1], [1,1,1,1,0] ] } ]; // ---------- Speed marks for slider ---------- const speedMarks = [ { value: 1, label: '1' }, { value: 10, label: '10' }, { value: 30, label: '30' }, { value: 60, label: '60' } ]; // ---------- Render ---------- return ( Conway's Game of Life An interactive cellular automaton devised by mathematician John Conway. Click or drag on the grid to toggle cells. Use the controls to run the simulation, adjust speed, or load preset patterns. {/* Controls */} {/* Play / Pause / Step / Clear / Random */} {/* Preset patterns */} {presets.map((p) => ( ))} {/* Speed slider */} Speed: {speed} generations / sec setSpeed(v)} min={1} max={60} step={1} marks={speedMarks} valueLabelDisplay="auto" size="small" /> {/* Stats */} Generation: {generation} Live cells: {liveCells} Grid: {cols} × {rows} {/* Canvas */} {/* Rules description */} Rules
  1. Any live cell with fewer than two live neighbors dies (underpopulation).
  2. Any live cell with two or three live neighbors lives on to the next generation.
  3. Any live cell with more than three live neighbors dies (overpopulation).
  4. Any dead cell with exactly three live neighbors becomes a live cell (reproduction).
); }; // Export to global scope for browser-based JSX compilation window.ConwaysGameOfLife = ConwaysGameOfLife;