conway's commit

This commit is contained in:
Sami 2025-08-31 04:05:46 -04:00
commit 680779c03d
4 changed files with 524 additions and 0 deletions

1
README.md Normal file
View File

@ -0,0 +1 @@
just a fun project for my kid, he loves the Conways game of life.

60
index.html Normal file
View File

@ -0,0 +1,60 @@
<!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>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<header>
<h1>Conway's Game of Life</h1>
<p>A cellular automaton simulation</p>
</header>
<div class="stats-panel">
<div class="stat">
<span class="stat-label">Generation:</span>
<span id="generation">0</span>
</div>
<div class="stat">
<span class="stat-label">Population:</span>
<span id="population">0</span>
</div>
<div class="stat">
<span class="stat-label">Births:</span>
<span id="births">0</span>
</div>
<div class="stat">
<span class="stat-label">Deaths:</span>
<span id="deaths">0</span>
</div>
</div>
<div class="controls">
<button id="startBtn">Start</button>
<button id="pauseBtn">Pause</button>
<button id="resetBtn">Reset</button>
<button id="randomBtn">Random</button>
<div class="speed-control">
<label for="speed">Speed:</label>
<input type="range" id="speed" min="1" max="20" value="10">
</div>
</div>
<div class="canvas-container">
<canvas id="gameCanvas"></canvas>
</div>
<div class="presets">
<button data-preset="glider">Glider</button>
<button data-preset="spaceship">Spaceship</button>
<button data-preset="pulsar">Pulsar</button>
<button data-preset="gosper">Gosper Glider Gun</button>
</div>
</div>
<script src="script.js"></script>
</body>
</html>

299
script.js Normal file
View File

@ -0,0 +1,299 @@
// Initialize the game when the page loads
document.addEventListener('DOMContentLoaded', () => {
// Create game with 80x60 grid and 10px cells
const game = new GameOfLife(80, 60, 10);
});
class GameOfLife {
constructor(width, height, cellSize = 10) {
this.width = width;
this.height = height;
this.cellSize = cellSize;
this.grid = this.createGrid();
this.nextGrid = this.createGrid();
this.running = false;
this.generation = 0;
this.births = 0;
this.deaths = 0;
this.animationId = null;
this.speed = 10;
// Canvas setup
this.canvas = document.getElementById('gameCanvas');
this.ctx = this.canvas.getContext('2d');
this.canvas.width = width * cellSize;
this.canvas.height = height * cellSize;
// Event listeners
this.setupEventListeners();
this.setupPresets();
// Initial draw
this.draw();
this.updateStats();
}
createGrid() {
return Array(this.height).fill().map(() => Array(this.width).fill(0));
}
randomize() {
for (let y = 0; y < this.height; y++) {
for (let x = 0; x < this.width; x++) {
this.grid[y][x] = Math.random() > 0.7 ? 1 : 0;
}
}
this.generation = 0;
this.draw();
}
setupEventListeners() {
// Start/Pause/Reset buttons
document.getElementById('startBtn').addEventListener('click', () => this.start());
document.getElementById('pauseBtn').addEventListener('click', () => this.pause());
document.getElementById('resetBtn').addEventListener('click', () => this.reset());
document.getElementById('randomBtn').addEventListener('click', () => this.randomize());
// Speed control
document.getElementById('speed').addEventListener('input', (e) => {
this.speed = parseInt(e.target.value);
});
// Canvas click to toggle cells
this.canvas.addEventListener('click', (e) => {
if (this.running) return;
const rect = this.canvas.getBoundingClientRect();
const x = Math.floor((e.clientX - rect.left) / this.cellSize);
const y = Math.floor((e.clientY - rect.top) / this.cellSize);
if (x >= 0 && x < this.width && y >= 0 && y < this.height) {
this.grid[y][x] = this.grid[y][x] ? 0 : 1;
this.draw();
}
});
}
setupPresets() {
document.querySelector('[data-preset="glider"]').addEventListener('click', () => {
this.reset();
// Glider pattern
this.grid[5][5] = 1;
this.grid[6][6] = 1;
this.grid[6][7] = 1;
this.grid[5][7] = 1;
this.grid[4][7] = 1;
this.draw();
});
document.querySelector('[data-preset="spaceship"]').addEventListener('click', () => {
this.reset();
// Lightweight spaceship
this.grid[10][10] = 1;
this.grid[10][13] = 1;
this.grid[11][9] = 1;
this.grid[11][13] = 1;
this.grid[12][9] = 1;
this.grid[12][10] = 1;
this.grid[12][11] = 1;
this.grid[12][12] = 1;
this.draw();
});
document.querySelector('[data-preset="pulsar"]').addEventListener('click', () => {
this.reset();
// Pulsar pattern
const pulsarPoints = [
[2,4],[2,5],[2,6],[2,10],[2,11],[2,12],
[4,2],[4,7],[4,9],[4,14],
[5,2],[5,7],[5,9],[5,14],
[6,2],[6,7],[6,9],[6,14],
[7,4],[7,5],[7,6],[7,10],[7,11],[7,12],
[9,4],[9,5],[9,6],[9,10],[9,11],[9,12],
[10,2],[10,7],[10,9],[10,14],
[11,2],[11,7],[11,9],[11,14],
[12,2],[12,7],[12,9],[12,14],
[14,4],[14,5],[14,6],[14,10],[14,11],[14,12]
];
pulsarPoints.forEach(([y, x]) => {
this.grid[y][x] = 1;
});
this.draw();
});
document.querySelector('[data-preset="gosper"]').addEventListener('click', () => {
this.reset();
// Gosper glider gun
const gunPoints = [
[5,1],[5,2],[6,1],[6,2],
[5,11],[6,11],[7,11],
[4,12],[8,12],
[3,13],[9,13],
[3,14],[9,14],
[6,15],
[4,16],[8,16],
[5,17],[6,17],[7,17],
[6,18],
[3,21],[4,21],[5,21],
[3,22],[4,22],[5,22],
[2,23],[6,23],
[1,25],[2,25],[6,25],[7,25],
[3,35],[4,35],[3,36],[4,36]
];
gunPoints.forEach(([y, x]) => {
this.grid[y][x] = 1;
});
this.draw();
});
}
countNeighbors(x, y) {
let count = 0;
for (let dy = -1; dy <= 1; dy++) {
for (let dx = -1; dx <= 1; dx++) {
if (dx === 0 && dy === 0) continue;
const nx = (x + dx + this.width) % this.width;
const ny = (y + dy + this.height) % this.height;
count += this.grid[ny][nx];
}
}
return count;
}
update() {
// Reset counters
this.births = 0;
this.deaths = 0;
// Compute next generation
for (let y = 0; y < this.height; y++) {
for (let x = 0; x < this.width; x++) {
const neighbors = this.countNeighbors(x, y);
const cell = this.grid[y][x];
let nextState;
// Apply Conway's Game of Life rules
if (cell === 1) {
nextState = (neighbors === 2 || neighbors === 3) ? 1 : 0;
if (nextState === 0) this.deaths++;
} else {
nextState = (neighbors === 3) ? 1 : 0;
if (nextState === 1) this.births++;
}
this.nextGrid[y][x] = nextState;
}
}
// Swap grids
[this.grid, this.nextGrid] = [this.nextGrid, this.grid];
this.generation++;
// Update stats display
this.updateStats();
}
draw() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// Draw live cells with gradient
const gradient = this.ctx.createLinearGradient(0, 0, this.canvas.width, this.canvas.height);
gradient.addColorStop(0, '#4facfe');
gradient.addColorStop(1, '#00f2fe');
this.ctx.fillStyle = gradient;
for (let y = 0; y < this.height; y++) {
for (let x = 0; x < this.width; x++) {
if (this.grid[y][x] === 1) {
this.ctx.fillRect(
x * this.cellSize,
y * this.cellSize,
this.cellSize - 1,
this.cellSize - 1
);
}
}
}
// Draw grid lines
this.ctx.strokeStyle = 'rgba(255, 255, 255, 0.05)';
this.ctx.lineWidth = 0.5;
// Vertical lines
for (let x = 0; x <= this.width; x++) {
this.ctx.beginPath();
this.ctx.moveTo(x * this.cellSize, 0);
this.ctx.lineTo(x * this.cellSize, this.canvas.height);
this.ctx.stroke();
}
// Horizontal lines
for (let y = 0; y <= this.height; y++) {
this.ctx.beginPath();
this.ctx.moveTo(0, y * this.cellSize);
this.ctx.lineTo(this.canvas.width, y * this.cellSize);
this.ctx.stroke();
}
}
updateStats() {
document.getElementById('generation').textContent = this.generation;
document.getElementById('population').textContent = this.getPopulation();
document.getElementById('births').textContent = this.births;
document.getElementById('deaths').textContent = this.deaths;
}
getPopulation() {
let count = 0;
for (let y = 0; y < this.height; y++) {
for (let x = 0; x < this.width; x++) {
count += this.grid[y][x];
}
}
return count;
}
gameLoop() {
this.update();
this.draw();
// Adjust speed (higher value = faster)
const speedFactor = (21 - this.speed) * 20;
setTimeout(() => {
if (this.running) {
this.animationId = requestAnimationFrame(() => this.gameLoop());
}
}, speedFactor);
}
start() {
if (!this.running) {
this.running = true;
this.gameLoop();
}
}
pause() {
this.running = false;
if (this.animationId) {
cancelAnimationFrame(this.animationId);
this.animationId = null;
}
}
reset() {
this.pause();
this.grid = this.createGrid();
this.nextGrid = this.createGrid();
this.generation = 0;
this.births = 0;
this.deaths = 0;
this.updateStats();
this.draw();
}
}

164
styles.css Normal file
View File

@ -0,0 +1,164 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #1a2a6c, #b21f1f, #1a2a6c);
color: white;
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
text-align: center;
}
header {
margin-bottom: 20px;
padding: 20px;
background: rgba(0, 0, 0, 0.3);
border-radius: 15px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
backdrop-filter: blur(10px);
}
h1 {
font-size: 2.5rem;
margin-bottom: 10px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
}
p {
font-size: 1.1rem;
opacity: 0.9;
}
.stats-panel {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 20px;
margin-bottom: 20px;
padding: 15px;
background: rgba(0, 0, 0, 0.2);
border-radius: 15px;
font-size: 1.1rem;
}
.stat {
display: flex;
flex-direction: column;
align-items: center;
}
.stat-label {
font-weight: 600;
margin-bottom: 5px;
color: #4facfe;
}
.controls {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 15px;
margin-bottom: 30px;
padding: 20px;
background: rgba(0, 0, 0, 0.2);
border-radius: 15px;
}
button {
padding: 12px 24px;
font-size: 1rem;
background: rgba(255, 255, 255, 0.15);
color: white;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 50px;
cursor: pointer;
transition: all 0.3s ease;
backdrop-filter: blur(5px);
}
button:hover {
background: rgba(255, 255, 255, 0.25);
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
button:active {
transform: translateY(0);
}
.speed-control {
display: flex;
align-items: center;
gap: 10px;
background: rgba(255, 255, 255, 0.1);
padding: 10px 20px;
border-radius: 50px;
}
.speed-control label {
font-weight: 500;
}
.speed-control input {
width: 150px;
}
.canvas-container {
display: flex;
justify-content: center;
margin-bottom: 30px;
}
#gameCanvas {
background: rgba(0, 0, 0, 0.2);
border-radius: 8px;
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.4);
}
.presets {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 15px;
padding: 20px;
background: rgba(0, 0, 0, 0.2);
border-radius: 15px;
}
.presets button {
background: rgba(255, 255, 255, 0.1);
}
.presets button:hover {
background: rgba(255, 255, 255, 0.2);
}
@media (max-width: 768px) {
.controls, .presets {
flex-direction: column;
align-items: center;
}
button {
width: 100%;
max-width: 300px;
}
.speed-control {
width: 100%;
max-width: 300px;
}
.stats-panel {
gap: 10px;
}
}