<!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>