From 680779c03d114414e1d0fd567218f427b00e2e5e Mon Sep 17 00:00:00 2001 From: Sami Date: Sun, 31 Aug 2025 04:05:46 -0400 Subject: [PATCH] conway's commit --- README.md | 1 + index.html | 60 +++++++++++ script.js | 299 +++++++++++++++++++++++++++++++++++++++++++++++++++++ styles.css | 164 +++++++++++++++++++++++++++++ 4 files changed, 524 insertions(+) create mode 100644 README.md create mode 100644 index.html create mode 100644 script.js create mode 100644 styles.css diff --git a/README.md b/README.md new file mode 100644 index 0000000..bb1ebf3 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +just a fun project for my kid, he loves the Conways game of life. diff --git a/index.html b/index.html new file mode 100644 index 0000000..9f88090 --- /dev/null +++ b/index.html @@ -0,0 +1,60 @@ + + + + + + Conway's Game of Life + + + +
+
+

Conway's Game of Life

+

A cellular automaton simulation

+
+ +
+
+ Generation: + 0 +
+
+ Population: + 0 +
+
+ Births: + 0 +
+
+ Deaths: + 0 +
+
+ +
+ + + + +
+ + +
+
+ +
+ +
+ +
+ + + + +
+
+ + + + \ No newline at end of file diff --git a/script.js b/script.js new file mode 100644 index 0000000..1383342 --- /dev/null +++ b/script.js @@ -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(); + } +} \ No newline at end of file diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..5d7dc51 --- /dev/null +++ b/styles.css @@ -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; + } +} \ No newline at end of file