<!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> * { font-size: 1em; } body { margin: 0; padding: 0; background-color: beige; } canvas { border: beige 1px solid; } #controls { margin-top: 10px; } #controls button, #controls select { margin-right: 10px; } button { border: none; padding: 0.25em 1em; } button:hover { cursor: pointer; } .playButton { background-color: rgb(116, 255, 239); color: black; } .pauseButton { background-color: rgb(255, 105, 250); color: black; } #contentContainer { padding: 2em 0; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: left; } canvas { margin-bottom: 3em; } </style> </head> <body> <div id="contentContainer"> <canvas id="gameCanvas" width="400" height="400"></canvas> <form id="controls"> <button id="playButton" class="playButton" type="button">Play</button> <button id="pauseButton" class="pauseButton" type="button">Pause</button> <button id="clearButton" type="button">Clear</button> <label for="speedSlider">Speed:</label> <input type="range" id="speedSlider" min="1" max="1000" value="500"> <span id="speedValue">500</span> ms <br> <br> <label for="patternSelect">Pattern:</label> <select id="patternSelect"> <option value="random">Random</option> <option value="glider">Glider</option> <option value="lwss">Lightweight Spaceship</option> <option value="blinker">Blinker</option> <option value="toad">Toad</option> <option value="beacon">Beacon</option> <option value="block">Block</option> <option value="beehive">Beehive</option> <option value="loaf">Loaf</option> <option value="boat">Boat</option> <option value="tub">Tub</option> <option value="pulsar">Pulsar</option> <option value="pentadecathlon">Pentadecathlon</option> <option value="gosper_glider_gun">Gosper Glider Gun</option> <option value="diehard">Diehard</option> <option value="acorn">Acorn</option> <option value="r_pentomino">R-pentomino</option> <option value="blinker_pair">Blinker-pair</option> </select> </form> </form> </div> <script> class GameOfLife { constructor(rows, cols) { this.rows = rows; this.cols = cols; this.grid = Array.from({ length: rows }, () => Array(cols).fill(0)); } countNeighbors(x, y) { const neighborOffsets = [ [-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1] ]; return neighborOffsets.reduce((count, [dx, dy]) => { const nx = x + dx; const ny = y + dy; if (nx >= 0 && ny >= 0 && nx < this.rows && ny < this.cols && this.grid[nx][ny]) { return count + 1; } return count; }, 0); } step() { const newGrid = this.grid.map((row, x) => row.map((cell, y) => { const neighbors = this.countNeighbors(x, y); return neighbors === 3 || (neighbors === 2 && cell) ? 1 : 0; }) ); this.grid = newGrid; } clear() { this.grid = Array.from({ length: this.rows }, () => Array(this.cols).fill(0)); } toggleCell(x, y) { if (x >= 0 && y >= 0 && x < this.cols && y < this.rows) { this.grid[x][y] = this.grid[x][y] ? 0 : 1; } } setPattern(pattern) { this.clear(); switch(pattern) { case 'glider': this.setGlider(); break; case 'lwss': this.setLWSS(); break; case 'blinker': this.setBlinker(); break; case 'toad': this.setToad(); break; case 'beacon': this.setBeacon(); break; case 'block': this.setBlock(); break; case 'beehive': this.setBeehive(); break; case 'loaf': this.setLoaf(); break; case 'boat': this.setBoat(); break; case 'tub': this.setTub(); break; case 'pulsar': this.setPulsar(); break; case 'pentadecathlon': this.setPentadecathlon(); break; case 'gosper_glider_gun': this.setGosperGliderGun(); break; case 'diehard': this.setDiehard(); break; case 'acorn': this.setAcorn(); break; case 'r_pentomino': this.setRPentomino(); break; case 'blinker_pair': this.setBlinkerPair(); break; case 'random': this.setRandom(); break; } } setGlider() { const pattern = [ [0, 1, 0], [0, 0, 1], [1, 1, 1] ]; this.placePattern(pattern, Math.floor(this.rows / 2), Math.floor(this.cols / 2)); } setLWSS() { const pattern = [ [0, 1, 1, 1, 1], [1, 0, 0, 0, 1], [0, 0, 0, 0, 1], [1, 0, 0, 1, 0] ]; this.placePattern(pattern, Math.floor(this.rows / 2), Math.floor(this.cols / 2)); } setBlinker() { const pattern = [ [1, 1, 1] ]; this.placePattern(pattern, Math.floor(this.rows / 2), Math.floor(this.cols / 2)); } setToad() { const pattern = [ [0, 1, 1, 1], [1, 1, 1, 0] ]; this.placePattern(pattern, Math.floor(this.rows / 2), Math.floor(this.cols / 2)); } setBeacon() { const pattern = [ [1, 1, 0, 0], [1, 1, 0, 0], [0, 0, 1, 1], [0, 0, 1, 1] ]; this.placePattern(pattern, Math.floor(this.rows / 2), Math.floor(this.cols / 2)); } setBlock() { const pattern = [ [1, 1], [1, 1] ]; this.placePattern(pattern, Math.floor(this.rows / 2), Math.floor(this.cols / 2)); } setBeehive() { const pattern = [ [0, 1, 1, 0], [1, 0, 0, 1], [0, 1, 1, 0] ]; this.placePattern(pattern, Math.floor(this.rows / 2), Math.floor(this.cols / 2)); } setLoaf() { const pattern = [ [0, 1, 1, 0], [1, 0, 0, 1], [0, 1, 0, 1], [0, 0, 1, 0] ]; this.placePattern(pattern, Math.floor(this.rows / 2), Math.floor(this.cols / 2)); } setBoat() { const pattern = [ [1, 1, 0], [1, 0, 1], [0, 1, 0] ]; this.placePattern(pattern, Math.floor(this.rows / 2), Math.floor(this.cols / 2)); } setTub() { const pattern = [ [0, 1, 0], [1, 0, 1], [0, 1, 0] ]; this.placePattern(pattern, Math.floor(this.rows / 2), Math.floor(this.cols / 2)); } setPulsar() { const pattern = [ [0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 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, 1, 0, 0, 0, 1], [1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1], [1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1], [0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0], [1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1], [1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1], [1, 0, 0, 0, 1, 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, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0] ]; this.placePattern(pattern, Math.floor(this.rows / 2) - 6, Math.floor(this.cols / 2) - 6); } setPentadecathlon() { const pattern = [ [0, 1, 0, 0, 0, 1, 0], [1, 0, 1, 1, 1, 0, 1], [0, 1, 0, 0, 0, 1, 0] ]; this.placePattern(pattern, Math.floor(this.rows / 2), Math.floor(this.cols / 2) - 3); } setGosperGliderGun() { const 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] ]; this.placePattern(pattern, Math.floor(this.rows / 2) - 9, Math.floor(this.cols / 2) - 17); } setDiehard() { const pattern = [ [0, 0, 0, 0, 0, 0, 1], [1, 1, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 1, 1], [0, 0, 1, 1, 0, 0, 0] ]; this.placePattern(pattern, Math.floor(this.rows / 2), Math.floor(this.cols / 2) - 3); } setAcorn() { const pattern = [ [0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0], [1, 1, 0, 0, 1, 1, 1] ]; this.placePattern(pattern, Math.floor(this.rows / 2), Math.floor(this.cols / 2) - 3); } setRPentomino() { const pattern = [ [0, 1, 1], [1, 1, 0], [0, 1, 0] ]; this.placePattern(pattern, Math.floor(this.rows / 2), Math.floor(this.cols / 2)); } setBlinkerPair() { const pattern = [ [1, 1, 1], [0, 0, 0], [1, 1, 1] ]; this.placePattern(pattern, Math.floor(this.rows / 2), Math.floor(this.cols / 2) - 1); } setRandom() { for (let x = 0; x < this.rows; x++) { for (let y = 0; y < this.cols; y++) { this.grid[x][y] = Math.random() < 0.3 ? 1 : 0; } } } placePattern(pattern, offsetX, offsetY) { for (let x = 0; x < pattern.length; x++) { for (let y = 0; y < pattern[x].length; y++) { if (offsetX + x < this.rows && offsetY + y < this.cols) { this.grid[offsetX + x][offsetY + y] = pattern[x][y]; } } } } } const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); const cellSize = 20; const rows = Math.floor(canvas.height / cellSize); const cols = Math.floor(canvas.width / cellSize); const game = new GameOfLife(rows, cols); let speed = 500; let lastRenderTime = 0; let isPlaying = false; canvas.addEventListener('mousemove', handleMouseMove); canvas.addEventListener('mouseout', handleMouseOut); let hoveredCell = null; // stores what cell is hovered over // Find the cell being hovered hover and and redraw the grid function handleMouseMove(event) { const rect = canvas.getBoundingClientRect(); const x = event.clientX - rect.left; const y = event.clientY - rect.top; const gridX = Math.floor(x / cellSize); const gridY = Math.floor(y / cellSize); hoveredCell = { x: gridX, y: gridY }; drawGrid(hoveredCell); // Redraw grid with highlighted cell } // remove highlight when mouse leaves the cell function handleMouseOut() { hoveredCell = null; drawGrid(); } function drawGrid(hoveredCell = null) { ctx.clearRect(0, 0, canvas.width, canvas.height); for (let x = 0; x < game.rows; x++) { for (let y = 0; y < game.cols; y++) { if (hoveredCell && hoveredCell.x === y && hoveredCell.y === x) { ctx.fillStyle = '#74FFEF'; } else { ctx.fillStyle = game.grid[x][y] ? '#2d2d2d' : 'beige'; } ctx.fillRect(y * cellSize, x * cellSize, cellSize, cellSize); ctx.strokeStyle = 'lightgrey'; ctx.strokeRect(y * cellSize, x * cellSize, cellSize, cellSize); } } } function simulate(currentTime) { if (!isPlaying) { requestAnimationFrame(simulate); return; } if (currentTime - lastRenderTime >= speed) { game.step(); drawGrid(); lastRenderTime = currentTime; } requestAnimationFrame(simulate); } function getMousePosition(event) { const rect = canvas.getBoundingClientRect(); const x = Math.floor((event.clientY - rect.top) / cellSize); const y = Math.floor((event.clientX - rect.left) / cellSize); return { x, y }; } function toggleCell(event) { const { x, y } = getMousePosition(event); game.toggleCell(x, y); drawGrid(); } document.getElementById('speedSlider').addEventListener('input', (event) => { speed = event.target.value; document.getElementById('speedValue').textContent = speed; }); document.getElementById('clearButton').addEventListener('click', () => { game.clear(); drawGrid(); }); document.getElementById('playButton').addEventListener('click', function() { isPlaying = true; this.classList.add('playButton'); document.getElementById('pauseButton').classList.remove('pauseButton'); }); document.getElementById('pauseButton').addEventListener('click', function() { isPlaying = false; this.classList.add('pauseButton'); document.getElementById('playButton').classList.remove('playButton'); }); document.getElementById('patternSelect').addEventListener('change', (event) => { game.setPattern(event.target.value); drawGrid(); }); canvas.addEventListener('click', toggleCell); canvas.addEventListener('contextmenu', (event) => { event.preventDefault(); toggleCell(event); }); drawGrid(); requestAnimationFrame(simulate); </script> </body> </html>