Spaces:
Running
Running
| <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 - Built with anycoder</title> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| :root { | |
| --primary: #4a6fa5; | |
| --secondary: #166088; | |
| --accent: #4fc3f7; | |
| --dark: #1a2a3a; | |
| --light: #f8f9fa; | |
| --success: #4caf50; | |
| --danger: #f44336; | |
| --shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
| --transition: all 0.3s ease; | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background-color: var(--light); | |
| color: var(--dark); | |
| line-height: 1.6; | |
| padding: 20px; | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| } | |
| header { | |
| width: 100%; | |
| max-width: 1200px; | |
| margin-bottom: 20px; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| flex-wrap: wrap; | |
| gap: 15px; | |
| } | |
| .logo { | |
| font-size: 1.8rem; | |
| font-weight: 700; | |
| color: var(--primary); | |
| text-decoration: none; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .logo i { | |
| color: var(--accent); | |
| } | |
| .anycoder-link { | |
| color: var(--secondary); | |
| text-decoration: none; | |
| font-size: 0.9rem; | |
| font-weight: 500; | |
| transition: var(--transition); | |
| } | |
| .anycoder-link:hover { | |
| color: var(--accent); | |
| text-decoration: underline; | |
| } | |
| .controls { | |
| width: 100%; | |
| max-width: 1200px; | |
| background-color: white; | |
| border-radius: 12px; | |
| padding: 20px; | |
| box-shadow: var(--shadow); | |
| margin-bottom: 20px; | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 15px; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| .control-group { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 8px; | |
| } | |
| .control-group label { | |
| font-weight: 600; | |
| font-size: 0.9rem; | |
| color: var(--dark); | |
| } | |
| button, select, input { | |
| padding: 10px 15px; | |
| border-radius: 8px; | |
| border: 1px solid #ddd; | |
| background-color: white; | |
| font-family: inherit; | |
| font-size: 1rem; | |
| cursor: pointer; | |
| transition: var(--transition); | |
| } | |
| button { | |
| background-color: var(--primary); | |
| color: white; | |
| border: none; | |
| font-weight: 600; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| button:hover { | |
| background-color: var(--secondary); | |
| transform: translateY(-2px); | |
| } | |
| button:disabled { | |
| background-color: #cccccc; | |
| cursor: not-allowed; | |
| transform: none; | |
| } | |
| select, input { | |
| width: 100%; | |
| max-width: 200px; | |
| } | |
| select:focus, input:focus { | |
| outline: 2px solid var(--accent); | |
| border-color: var(--accent); | |
| } | |
| .game-container { | |
| width: 100%; | |
| max-width: 1200px; | |
| background-color: white; | |
| border-radius: 12px; | |
| padding: 20px; | |
| box-shadow: var(--shadow); | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| } | |
| .grid-container { | |
| width: 100%; | |
| aspect-ratio: 1 / 1; | |
| max-width: 800px; | |
| display: grid; | |
| grid-template-columns: repeat(var(--cols), 1fr); | |
| gap: 1px; | |
| background-color: #e0e0e0; | |
| border-radius: 8px; | |
| overflow: hidden; | |
| margin: 20px 0; | |
| touch-action: none; | |
| } | |
| .cell { | |
| background-color: white; | |
| aspect-ratio: 1 / 1; | |
| transition: background-color 0.1s ease; | |
| } | |
| .cell.alive { | |
| background-color: var(--primary); | |
| } | |
| .stats { | |
| width: 100%; | |
| display: flex; | |
| justify-content: space-around; | |
| flex-wrap: wrap; | |
| gap: 15px; | |
| margin-top: 10px; | |
| } | |
| .stat { | |
| background-color: rgba(74, 111, 165, 0.1); | |
| padding: 10px 15px; | |
| border-radius: 8px; | |
| font-weight: 600; | |
| color: var(--primary); | |
| } | |
| .stat span { | |
| color: var(--secondary); | |
| margin-left: 5px; | |
| } | |
| .presets { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 10px; | |
| justify-content: center; | |
| margin-top: 15px; | |
| } | |
| .preset-btn { | |
| background-color: var(--light); | |
| color: var(--dark); | |
| border: 1px solid #ddd; | |
| } | |
| .preset-btn:hover { | |
| background-color: var(--accent); | |
| color: white; | |
| border-color: var(--accent); | |
| } | |
| footer { | |
| margin-top: 20px; | |
| text-align: center; | |
| color: #666; | |
| font-size: 0.9rem; | |
| } | |
| footer a { | |
| color: var(--primary); | |
| text-decoration: none; | |
| font-weight: 600; | |
| } | |
| footer a:hover { | |
| text-decoration: underline; | |
| } | |
| @media (max-width: 768px) { | |
| .controls { | |
| flex-direction: column; | |
| align-items: stretch; | |
| } | |
| .control-group { | |
| width: 100%; | |
| } | |
| .grid-container { | |
| max-width: 100%; | |
| } | |
| } | |
| @media (max-width: 480px) { | |
| body { | |
| padding: 10px; | |
| } | |
| header { | |
| flex-direction: column; | |
| align-items: flex-start; | |
| } | |
| .stats { | |
| flex-direction: column; | |
| align-items: center; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <a href="#" class="logo"> | |
| <i class="fas fa-gamepad"></i> | |
| <span>Game of Life</span> | |
| </a> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" class="anycoder-link" target="_blank">Built with anycoder</a> | |
| </header> | |
| <div class="controls"> | |
| <div class="control-group"> | |
| <label for="grid-size">Grid Size</label> | |
| <select id="grid-size"> | |
| <option value="20">20x20</option> | |
| <option value="30" selected>30x30</option> | |
| <option value="40">40x40</option> | |
| <option value="50">50x50</option> | |
| </select> | |
| </div> | |
| <div class="control-group"> | |
| <label for="speed">Speed (ms)</label> | |
| <input type="range" id="speed" min="50" max="1000" value="200"> | |
| </div> | |
| <button id="start-btn"> | |
| <i class="fas fa-play"></i> | |
| <span>Start</span> | |
| </button> | |
| <button id="stop-btn" disabled> | |
| <i class="fas fa-stop"></i> | |
| <span>Stop</span> | |
| </button> | |
| <button id="clear-btn"> | |
| <i class="fas fa-trash"></i> | |
| <span>Clear</span> | |
| </button> | |
| <button id="random-btn"> | |
| <i class="fas fa-random"></i> | |
| <span>Random</span> | |
| </button> | |
| </div> | |
| <div class="game-container"> | |
| <div class="grid-container" id="grid"></div> | |
| <div class="stats"> | |
| <div class="stat">Generation: <span id="generation">0</span></div> | |
| <div class="stat">Population: <span id="population">0</span></div> | |
| <div class="stat">Status: <span id="status">Stopped</span></div> | |
| </div> | |
| <div class="presets"> | |
| <button class="preset-btn" data-preset="glider">Glider</button> | |
| <button class="preset-btn" data-preset="pulsar">Pulsar</button> | |
| <button class="preset-btn" data-preset="spaceship">Spaceship</button> | |
| <button class="preset-btn" data-preset="beacon">Beacon</button> | |
| </div> | |
| </div> | |
| <footer> | |
| <p>Conway's Game of Life - A cellular automaton devised by mathematician John Conway.</p> | |
| <p>Rules: 1. Any live cell with fewer than two live neighbors dies (underpopulation). 2. Any live cell with two or three live neighbors lives on. 3. Any live cell with more than three live neighbors dies (overpopulation). 4. Any dead cell with exactly three live neighbors becomes a live cell (reproduction).</p> | |
| </footer> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', () => { | |
| // DOM Elements | |
| const gridContainer = document.getElementById('grid'); | |
| const startBtn = document.getElementById('start-btn'); | |
| const stopBtn = document.getElementById('stop-btn'); | |
| const clearBtn = document.getElementById('clear-btn'); | |
| const randomBtn = document.getElementById('random-btn'); | |
| const gridSizeSelect = document.getElementById('grid-size'); | |
| const speedInput = document.getElementById('speed'); | |
| const generationSpan = document.getElementById('generation'); | |
| const populationSpan = document.getElementById('population'); | |
| const statusSpan = document.getElementById('status'); | |
| const presetButtons = document.querySelectorAll('.preset-btn'); | |
| // Game state | |
| let grid = []; | |
| let rows = 30; | |
| let cols = 30; | |
| let isRunning = false; | |
| let generation = 0; | |
| let animationId = null; | |
| let speed = 200; | |
| let isDrawing = false; | |
| let isErasing = false; | |
| // Initialize the game | |
| initGame(); | |
| // Event Listeners | |
| startBtn.addEventListener('click', startGame); | |
| stopBtn.addEventListener('click', stopGame); | |
| clearBtn.addEventListener('click', clearGrid); | |
| randomBtn.addEventListener('click', randomizeGrid); | |
| gridSizeSelect.addEventListener('change', changeGridSize); | |
| speedInput.addEventListener('input', updateSpeed); | |
| // Grid interaction | |
| gridContainer.addEventListener('mousedown', startDrawing); | |
| gridContainer.addEventListener('mouseup', stopDrawing); | |
| gridContainer.addEventListener('mouseleave', stopDrawing); | |
| gridContainer.addEventListener('mousemove', drawCell); | |
| // Touch support | |
| gridContainer.addEventListener('touchstart', handleTouchStart); | |
| gridContainer.addEventListener('touchend', stopDrawing); | |
| gridContainer.addEventListener('touchmove', handleTouchMove); | |
| // Preset patterns | |
| presetButtons.forEach(button => { | |
| button.addEventListener('click', () => { | |
| const preset = button.getAttribute('data-preset'); | |
| loadPreset(preset); | |
| }); | |
| }); | |
| // Initialize the game grid | |
| function initGame() { | |
| createGrid(); | |
| updateStats(); | |
| } | |
| // Create the grid | |
| function createGrid() { | |
| gridContainer.style.setProperty('--cols', cols); | |
| gridContainer.innerHTML = ''; | |
| grid = Array(rows).fill().map(() => Array(cols).fill(false)); | |
| for (let row = 0; row < rows; row++) { | |
| for (let col = 0; col < cols; col++) { | |
| const cell = document.createElement('div'); | |
| cell.className = 'cell'; | |
| cell.dataset.row = row; | |
| cell.dataset.col = col; | |
| cell.addEventListener('click', toggleCell); | |
| gridContainer.appendChild(cell); | |
| } | |
| } | |
| } | |
| // Change grid size | |
| function changeGridSize() { | |
| const size = parseInt(gridSizeSelect.value); | |
| rows = size; | |
| cols = size; | |
| generation = 0; | |
| stopGame(); | |
| createGrid(); | |
| updateStats(); | |
| } | |
| // Update simulation speed | |
| function updateSpeed() { | |
| speed = parseInt(speedInput.value); | |
| if (isRunning) { | |
| stopGame(); | |
| startGame(); | |
| } | |
| } | |
| // Start the game | |
| function startGame() { | |
| if (isRunning) return; | |
| isRunning = true; | |
| startBtn.disabled = true; | |
| stopBtn.disabled = false; | |
| statusSpan.textContent = 'Running'; | |
| runSimulation(); | |
| } | |
| // Stop the game | |
| function stopGame() { | |
| isRunning = false; | |
| startBtn.disabled = false; | |
| stopBtn.disabled = true; | |
| statusSpan.textContent = 'Stopped'; | |
| if (animationId) { | |
| cancelAnimationFrame(animationId); | |
| animationId = null; | |
| } | |
| } | |
| // Clear the grid | |
| function clearGrid() { | |
| stopGame(); | |
| grid.forEach(row => row.fill(false)); | |
| updateGridDisplay(); | |
| generation = 0; | |
| updateStats(); | |
| } | |
| // Randomize the grid | |
| function randomizeGrid() { | |
| stopGame(); | |
| grid = grid.map(row => row.map(() => Math.random() > 0.7)); | |
| updateGridDisplay(); | |
| generation = 0; | |
| updateStats(); | |
| } | |
| // Run the simulation | |
| function runSimulation() { | |
| if (!isRunning) return; | |
| const newGrid = JSON.parse(JSON.stringify(grid)); | |
| for (let row = 0; row < rows; row++) { | |
| for (let col = 0; col < cols; col++) { | |
| const neighbors = countNeighbors(row, col); | |
| // Apply Conway's rules | |
| if (grid[row][col]) { | |
| // Cell is alive | |
| if (neighbors < 2 || neighbors > 3) { | |
| newGrid[row][col] = false; // Dies | |
| } | |
| } else { | |
| // Cell is dead | |
| if (neighbors === 3) { | |
| newGrid[row][col] = true; // Becomes alive | |
| } | |
| } | |
| } | |
| } | |
| grid = newGrid; | |
| generation++; | |
| updateGridDisplay(); | |
| updateStats(); | |
| animationId = setTimeout(() => runSimulation(), speed); | |
| } | |
| // Count live neighbors | |
| function countNeighbors(row, col) { | |
| let count = 0; | |
| for (let i = -1; i <= 1; i++) { | |
| for (let j = -1; j <= 1; j++) { | |
| if (i === 0 && j === 0) continue; // Skip self | |
| const newRow = row + i; | |
| const newCol = col + j; | |
| if (newRow >= 0 && newRow < rows && newCol >= 0 && newCol < cols) { | |
| if (grid[newRow][newCol]) { | |
| count++; | |
| } | |
| } | |
| } | |
| } | |
| return count; | |
| } | |
| // Update the grid display | |
| function updateGridDisplay() { | |
| const cells = gridContainer.querySelectorAll('.cell'); | |
| cells.forEach(cell => { | |
| const row = parseInt(cell.dataset.row); | |
| const col = parseInt(cell.dataset.col); | |
| if (grid[row][col]) { | |
| cell.classList.add('alive'); | |
| } else { | |
| cell.classList.remove('alive'); | |
| } | |
| }); | |
| } | |
| // Update statistics | |
| function updateStats() { | |
| generationSpan.textContent = generation; | |
| let population = 0; | |
| grid.forEach(row => { | |
| row.forEach(cell => { | |
| if (cell) population++; | |
| }); | |
| }); | |
| populationSpan.textContent = population; | |
| } | |
| // Toggle cell state | |
| function toggleCell(e) { | |
| if (e.target.classList.contains('cell')) { | |
| const row = parseInt(e.target.dataset.row); | |
| const col = parseInt(e.target.dataset.col); | |
| grid[row][col] = !grid[row][col]; | |
| e.target.classList.toggle('alive'); | |
| updateStats(); | |
| } | |
| } | |
| // Start drawing | |
| function startDrawing(e) { | |
| if (e.target.classList.contains('cell')) { | |
| isDrawing = true; | |
| isErasing = e.target.classList.contains('alive'); | |
| toggleCell(e); | |
| } | |
| } | |
| // Stop drawing | |
| function stopDrawing() { | |
| isDrawing = false; | |
| } | |
| // Draw cells | |
| function drawCell(e) { | |
| if (!isDrawing) return; | |
| e.preventDefault(); | |
| if (e.target.classList.contains('cell')) { | |
| const row = parseInt(e.target.dataset.row); | |
| const col = parseInt(e.target.dataset.col); | |
| if (isErasing) { | |
| if (grid[row][col]) { | |
| grid[row][col] = false; | |
| e.target.classList.remove('alive'); | |
| updateStats(); | |
| } | |
| } else { | |
| if (!grid[row][col]) { | |
| grid[row][col] = true; | |
| e.target.classList.add('alive'); | |
| updateStats(); | |
| } | |
| } | |
| } | |
| } | |
| // Touch support | |
| function handleTouchStart(e) { | |
| e.preventDefault(); | |
| const touch = e.touches[0]; | |
| const element = document.elementFromPoint(touch.clientX, touch.clientY); | |
| if (element && element.classList.contains('cell')) { | |
| isDrawing = true; | |
| isErasing = element.classList.contains('alive'); | |
| toggleCell({ target: element }); | |
| } | |
| } | |
| function handleTouchMove(e) { | |
| e.preventDefault(); | |
| if (!isDrawing) return; | |
| const touch = e.touches[0]; | |
| const element = document.elementFromPoint(touch.clientX, touch.clientY); | |
| if (element && element.classList.contains('cell')) { | |
| drawCell({ target: element, preventDefault: () => {} }); | |
| } | |
| } | |
| // Load preset patterns | |
| function loadPreset(preset) { | |
| stopGame(); | |
| clearGrid(); | |
| switch(preset) { | |
| case 'glider': | |
| // Glider pattern | |
| if (rows >= 3 && cols >= 3) { | |
| grid[1][2] = true; | |
| grid[2][3] = true; | |
| grid[3][1] = true; | |
| grid[3][2] = true; | |
| grid[3][3] = true; | |
| } | |
| break; | |
| case 'pulsar': | |
| // Pulsar pattern (simplified) | |
| if (rows >= 15 && cols >= 15) { | |
| const centerRow = Math.floor(rows / 2); | |
| const centerCol = Math.floor(cols / 2); | |
| // Top left block | |
| grid[centerRow-2][centerCol-4] = true; | |
| grid[centerRow-2][centerCol-3] = true; | |
| grid[centerRow-2][centerCol-2] = true; | |
| grid[centerRow-1][centerCol-6] = true; | |
| grid[centerRow-1][centerCol-1] = true; | |
| grid[centerRow][centerCol-6] = true; | |
| grid[centerRow][centerCol-1] = true; | |
| grid[centerRow+1][centerCol-6] = true; | |
| grid[centerRow+1][centerCol-1] = true; | |
| grid[centerRow+2][centerCol-4] = true; | |
| grid[centerRow+2][centerCol-3] = true; | |
| grid[centerRow+2][centerCol-2] = true; | |
| // Top right block | |
| grid[centerRow-2][centerCol+2] = true; | |
| grid[centerRow-2][centerCol+3] = true; | |
| grid[centerRow-2][centerCol+4] = true; | |
| grid[centerRow-1][centerCol+1] = true; | |
| grid[centerRow-1][centerCol+6] = true; | |
| grid[centerRow][centerCol+1] = true; | |
| grid[centerRow][centerCol+6] = true; | |
| grid[centerRow+1][centerCol+1] = true; | |
| grid[centerRow+1][centerCol+6] = true; | |
| grid[centerRow+2][centerCol+2] = true; | |
| grid[centerRow+2][centerCol+3] = true; | |
| grid[centerRow+2][centerCol+4] = true; | |
| // Bottom left block | |
| grid[centerRow+4][centerCol-4] = true; | |
| grid[centerRow+4][centerCol-3] = true; | |
| grid[centerRow+4][centerCol-2] = true; | |
| grid[centerRow+5][centerCol-6] = true; | |
| grid[centerRow+5][centerCol-1] = true; | |
| grid[centerRow+6][centerCol-6] = true; | |
| grid[centerRow+6][centerCol-1] = true; | |
| grid[centerRow+7][centerCol-6] = true; | |
| grid[centerRow+7][centerCol-1] = true; | |
| grid[centerRow+8][centerCol-4] = true; | |
| grid[centerRow+8][centerCol-3] = true; | |
| grid[centerRow+8][centerCol-2] = true; | |
| // Bottom right block | |
| grid[centerRow+4][centerCol+2] = true; | |
| grid[centerRow+4][centerCol+3] = true; | |
| grid[centerRow+4][centerCol+4] = true; | |
| grid[centerRow+5][centerCol+1] = true; | |
| grid[centerRow+5][centerCol+6] = true; | |
| grid[centerRow+6][centerCol+1] = true; | |
| grid[centerRow+6][centerCol+6] = true; | |
| grid[centerRow+7][centerCol+1] = true; | |
| grid[centerRow+7][centerCol+6] = true; | |
| grid[centerRow+8][centerCol+2] = true; | |
| grid[centerRow+8][centerCol+3] = true; | |
| grid[centerRow+8][centerCol+4] = true; | |
| } | |
| break; | |
| case 'spaceship': | |
| // Lightweight spaceship | |
| if (rows >= 5 && cols >= 5) { | |
| const centerRow = Math.floor(rows / 2); | |
| const centerCol = Math.floor(cols / 2); | |
| grid[centerRow][centerCol+1] = true; | |
| grid[centerRow][centerCol+4] = true; | |
| grid[centerRow+1][centerCol+4] = true; | |
| grid[centerRow+2][centerCol] = true; | |
| grid[centerRow+2][centerCol+4] = true; | |
| grid[centerRow+3][centerCol+1] = true; | |
| grid[centerRow+3][centerCol+2] = true; | |
| grid[centerRow+3][centerCol+3] = true; | |
| grid[centerRow+3][centerCol+4] = true; | |
| } | |
| break; | |
| case 'beacon': | |
| // Beacon pattern | |
| if (rows >= 4 && cols >= 4) { | |
| const centerRow = Math.floor(rows / 2); | |
| const centerCol = Math.floor(cols / 2); | |
| grid[centerRow][centerCol] = true; | |
| grid[centerRow][centerCol+1] = true; | |
| grid[centerRow+1][centerCol] = true; | |
| grid[centerRow+1][centerCol+1] = true; | |
| grid[centerRow+2][centerCol+2] = true; | |
| grid[centerRow+2][centerCol+3] = true; | |
| grid[centerRow+3][centerCol+2] = true; | |
| grid[centerRow+3][centerCol+3] = true; | |
| } | |
| break; | |
| } | |
| updateGridDisplay(); | |
| updateStats(); | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> |