conway's commit
This commit is contained in:
commit
680779c03d
1
README.md
Normal file
1
README.md
Normal file
@ -0,0 +1 @@
|
||||
just a fun project for my kid, he loves the Conways game of life.
|
||||
60
index.html
Normal file
60
index.html
Normal 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
299
script.js
Normal 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
164
styles.css
Normal 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;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user