| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Spiral Colony Simulation</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script src="https://kit.fontawesome.com/a076d05399.js" crossorigin="anonymous"></script> |
| <style> |
| .cell { |
| width: 20px; |
| height: 20px; |
| border: 1px solid #e5e7eb; |
| transition: all 0.2s ease; |
| } |
| |
| .cell:hover { |
| transform: scale(1.2); |
| z-index: 10; |
| } |
| |
| .cell-alive { |
| background-color: #3b82f6; |
| box-shadow: 0 0 5px rgba(59, 130, 246, 0.7); |
| } |
| |
| .cell-dying { |
| background-color: #ef4444; |
| box-shadow: 0 0 5px rgba(239, 68, 68, 0.7); |
| } |
| |
| .cell-emerging { |
| background-color: #10b981; |
| box-shadow: 0 0 5px rgba(16, 185, 129, 0.7); |
| } |
| |
| .cell-mutated { |
| background-color: #8b5cf6; |
| box-shadow: 0 0 5px rgba(139, 92, 246, 0.7); |
| } |
| |
| .spiral-pattern { |
| background: conic-gradient( |
| from 0deg, |
| #3b82f6 0% 20%, |
| #10b981 20% 40%, |
| #8b5cf6 40% 60%, |
| #ef4444 60% 80%, |
| #f59e0b 80% 100% |
| ); |
| border-radius: 50%; |
| } |
| |
| .modal { |
| transition: opacity 0.3s ease; |
| } |
| |
| .grid-container { |
| perspective: 1000px; |
| } |
| |
| .grid { |
| transform-style: preserve-3d; |
| transition: transform 0.5s ease; |
| } |
| |
| @keyframes pulse { |
| 0%, 100% { transform: scale(1); } |
| 50% { transform: scale(1.05); } |
| } |
| |
| .pulse-animation { |
| animation: pulse 2s infinite; |
| } |
| </style> |
| </head> |
| <body class="bg-gray-900 text-white min-h-screen"> |
| <div class="container mx-auto px-4 py-8"> |
| <header class="mb-8 text-center"> |
| <h1 class="text-4xl font-bold mb-2 bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-emerald-400"> |
| Spiral Colony Simulation |
| </h1> |
| <p class="text-gray-300">A cellular automaton with emergent patterns and player interactions</p> |
| </header> |
| |
| <div class="flex flex-col lg:flex-row gap-8"> |
| |
| <div class="w-full lg:w-1/4 bg-gray-800 p-6 rounded-lg shadow-lg"> |
| <div class="mb-6"> |
| <h2 class="text-xl font-semibold mb-4 flex items-center"> |
| <i class="fas fa-sliders-h mr-2 text-blue-400"></i> Simulation Controls |
| </h2> |
| |
| <div class="space-y-4"> |
| <div> |
| <label class="block text-sm font-medium text-gray-300 mb-1">Grid Size</label> |
| <div class="flex gap-2"> |
| <input type="number" id="gridWidth" min="10" max="50" value="30" |
| class="w-1/2 bg-gray-700 border border-gray-600 rounded px-3 py-2 text-sm"> |
| <input type="number" id="gridHeight" min="10" max="50" value="30" |
| class="w-1/2 bg-gray-700 border border-gray-600 rounded px-3 py-2 text-sm"> |
| </div> |
| </div> |
| |
| <div> |
| <label class="block text-sm font-medium text-gray-300 mb-1">Initial Density</label> |
| <input type="range" id="initialDensity" min="1" max="50" value="15" |
| class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer"> |
| <div class="flex justify-between text-xs text-gray-400"> |
| <span>Sparse</span> |
| <span>Dense</span> |
| </div> |
| </div> |
| |
| <div class="pt-2"> |
| <button id="initGrid" class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded flex items-center justify-center"> |
| <i class="fas fa-redo mr-2"></i> Initialize Grid |
| </button> |
| </div> |
| </div> |
| </div> |
| |
| <div class="mb-6"> |
| <h2 class="text-xl font-semibold mb-4 flex items-center"> |
| <i class="fas fa-gamepad mr-2 text-emerald-400"></i> Game Controls |
| </h2> |
| |
| <div class="space-y-3"> |
| <div class="flex gap-2"> |
| <button id="startSim" class="flex-1 bg-green-600 hover:bg-green-700 text-white py-2 px-4 rounded flex items-center justify-center"> |
| <i class="fas fa-play mr-2"></i> Start |
| </button> |
| <button id="pauseSim" class="flex-1 bg-yellow-600 hover:bg-yellow-700 text-white py-2 px-4 rounded flex items-center justify-center"> |
| <i class="fas fa-pause mr-2"></i> Pause |
| </button> |
| </div> |
| |
| <button id="stepSim" class="w-full bg-purple-600 hover:bg-purple-700 text-white py-2 px-4 rounded flex items-center justify-center"> |
| <i class="fas fa-step-forward mr-2"></i> Step |
| </button> |
| |
| <div class="pt-2"> |
| <label class="block text-sm font-medium text-gray-300 mb-1">Speed</label> |
| <input type="range" id="simSpeed" min="50" max="1000" value="300" |
| class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer"> |
| <div class="flex justify-between text-xs text-gray-400"> |
| <span>Slow</span> |
| <span>Fast</span> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| <div class="mb-6"> |
| <h2 class="text-xl font-semibold mb-4 flex items-center"> |
| <i class="fas fa-magic mr-2 text-purple-400"></i> Player Actions |
| </h2> |
| |
| <div class="space-y-3"> |
| <div> |
| <label class="block text-sm font-medium text-gray-300 mb-1">Action Type</label> |
| <select id="actionType" class="w-full bg-gray-700 border border-gray-600 rounded px-3 py-2 text-sm"> |
| <option value="stimulus">Apply Stimulus</option> |
| <option value="mutation">Apply Mutation</option> |
| <option value="inject">Inject Pattern</option> |
| </select> |
| </div> |
| |
| <div id="stimulusOptions"> |
| <label class="block text-sm font-medium text-gray-300 mb-1">Stimulus Type</label> |
| <select id="stimulusType" class="w-full bg-gray-700 border border-gray-600 rounded px-3 py-2 text-sm"> |
| <option value="activate">Activate Cells</option> |
| <option value="kill">Kill Cells</option> |
| <option value="energize">Energize Cells</option> |
| </select> |
| </div> |
| |
| <div id="mutationOptions" class="hidden"> |
| <label class="block text-sm font-medium text-gray-300 mb-1">Mutation Type</label> |
| <select id="mutationType" class="w-full bg-gray-700 border border-gray-600 rounded px-3 py-2 text-sm"> |
| <option value="ruleChange">Change Rules</option> |
| <option value="cellProperty">Alter Cell Properties</option> |
| <option value="environment">Change Environment</option> |
| </select> |
| </div> |
| |
| <div id="patternOptions" class="hidden"> |
| <label class="block text-sm font-medium text-gray-300 mb-1">Pattern Type</label> |
| <select id="patternType" class="w-full bg-gray-700 border border-gray-600 rounded px-3 py-2 text-sm"> |
| <option value="glider">Glider</option> |
| <option value="pulsar">Pulsar</option> |
| <option value="spiral">Spiral</option> |
| </select> |
| </div> |
| |
| <div class="pt-2"> |
| <button id="applyAction" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white py-2 px-4 rounded flex items-center justify-center"> |
| <i class="fas fa-bolt mr-2"></i> Apply Action |
| </button> |
| </div> |
| </div> |
| </div> |
| |
| <div> |
| <h2 class="text-xl font-semibold mb-4 flex items-center"> |
| <i class="fas fa-chart-line mr-2 text-red-400"></i> Simulation Stats |
| </h2> |
| |
| <div class="space-y-2 text-sm"> |
| <div class="flex justify-between"> |
| <span class="text-gray-300">Generation:</span> |
| <span id="generationCount" class="font-mono">0</span> |
| </div> |
| <div class="flex justify-between"> |
| <span class="text-gray-300">Alive Cells:</span> |
| <span id="aliveCount" class="font-mono">0</span> |
| </div> |
| <div class="flex justify-between"> |
| <span class="text-gray-300">Dying Cells:</span> |
| <span id="dyingCount" class="font-mono">0</span> |
| </div> |
| <div class="flex justify-between"> |
| <span class="text-gray-300">Emergent Patterns:</span> |
| <span id="patternCount" class="font-mono">0</span> |
| </div> |
| <div class="flex justify-between"> |
| <span class="text-gray-300">Mutations:</span> |
| <span id="mutationCount" class="font-mono">0</span> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="w-full lg:w-3/4"> |
| <div class="bg-gray-800 p-6 rounded-lg shadow-lg mb-6"> |
| <div class="flex justify-between items-center mb-4"> |
| <h2 class="text-xl font-semibold flex items-center"> |
| <i class="fas fa-border-all mr-2 text-blue-400"></i> Colony Grid |
| </h2> |
| <div class="flex items-center space-x-2"> |
| <span class="text-sm text-gray-300">Zoom:</span> |
| <input type="range" id="gridZoom" min="50" max="150" value="100" |
| class="w-24 h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer"> |
| </div> |
| </div> |
| |
| <div class="grid-container overflow-auto max-h-[70vh] border border-gray-700 rounded"> |
| <div id="grid" class="grid mx-auto"></div> |
| </div> |
| </div> |
| |
| |
| <div class="bg-gray-800 p-4 rounded-lg shadow-lg"> |
| <div class="flex items-center justify-between mb-2"> |
| <h3 class="font-medium flex items-center"> |
| <i class="fas fa-info-circle mr-2 text-yellow-400"></i> Simulation Status |
| </h3> |
| <span id="simStatus" class="px-3 py-1 rounded-full text-xs font-semibold bg-gray-700">Ready</span> |
| </div> |
| <div id="statusMessage" class="text-sm text-gray-300"> |
| Initialize the grid to begin the simulation. Click on cells to toggle their state. |
| </div> |
| <div id="emergenceMessage" class="hidden mt-2 p-2 bg-emerald-900/50 text-emerald-100 text-sm rounded border border-emerald-700"> |
| <i class="fas fa-star mr-1"></i> <span class="font-medium">Emergence Detected:</span> <span id="emergenceText"></span> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div id="patternModal" class="modal fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50"> |
| <div class="bg-gray-800 rounded-lg shadow-xl w-full max-w-2xl max-h-[90vh] overflow-y-auto"> |
| <div class="p-6"> |
| <div class="flex justify-between items-center mb-4"> |
| <h3 class="text-xl font-semibold"> |
| <i class="fas fa-shapes mr-2 text-purple-400"></i> Pattern Library |
| </h3> |
| <button id="closePatternModal" class="text-gray-400 hover:text-white"> |
| <i class="fas fa-times"></i> |
| </button> |
| </div> |
| |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> |
| <div class="pattern-card bg-gray-700 p-4 rounded-lg cursor-pointer hover:bg-gray-600 transition" data-pattern="glider"> |
| <div class="flex justify-center mb-2"> |
| <div class="grid grid-cols-3 gap-1 w-24"> |
| <div class="cell bg-gray-800 h-6 w-6"></div> |
| <div class="cell bg-blue-500 h-6 w-6"></div> |
| <div class="cell bg-gray-800 h-6 w-6"></div> |
| <div class="cell bg-gray-800 h-6 w-6"></div> |
| <div class="cell bg-gray-800 h-6 w-6"></div> |
| <div class="cell bg-blue-500 h-6 w-6"></div> |
| <div class="cell bg-blue-500 h-6 w-6"></div> |
| <div class="cell bg-blue-500 h-6 w-6"></div> |
| <div class="cell bg-blue-500 h-6 w-6"></div> |
| </div> |
| </div> |
| <h4 class="font-medium text-center">Glider</h4> |
| <p class="text-xs text-gray-400 text-center mt-1">Moves diagonally</p> |
| </div> |
| |
| <div class="pattern-card bg-gray-700 p-4 rounded-lg cursor-pointer hover:bg-gray-600 transition" data-pattern="pulsar"> |
| <div class="flex justify-center mb-2"> |
| <div class="grid grid-cols-7 gap-1 w-28"> |
| |
| <div class="cell bg-gray-800 h-4 w-4"></div> |
| <div class="cell bg-gray-800 h-4 w-4"></div> |
| <div class="cell bg-blue-500 h-4 w-4"></div> |
| <div class="cell bg-gray-800 h-4 w-4"></div> |
| <div class="cell bg-blue-500 h-4 w-4"></div> |
| <div class="cell bg-gray-800 h-4 w-4"></div> |
| <div class="cell bg-gray-800 h-4 w-4"></div> |
| |
| <div class="cell bg-gray-800 h-4 w-4"></div> |
| <div class="cell bg-gray-800 h-4 w-4"></div> |
| <div class="cell bg-gray-800 h-4 w-4"></div> |
| <div class="cell bg-blue-500 h-4 w-4"></div> |
| <div class="cell bg-gray-800 h-4 w-4"></div> |
| <div class="cell bg-gray-800 h-4 w-4"></div> |
| <div class="cell bg-gray-800 h-4 w-4"></div> |
| |
| <div class="cell bg-blue-500 h-4 w-4"></div> |
| <div class="cell bg-gray-800 h-4 w-4"></div> |
| <div class="cell bg-blue-500 h-4 w-4"></div> |
| <div class="cell bg-gray-800 h-4 w-4"></div> |
| <div class="cell bg-blue-500 h-4 w-4"></div> |
| <div class="cell bg-gray-800 h-4 w-4"></div> |
| <div class="cell bg-blue-500 h-4 w-4"></div> |
| |
| |
| </div> |
| </div> |
| <h4 class="font-medium text-center">Pulsar</h4> |
| <p class="text-xs text-gray-400 text-center mt-1">Oscillates every 3 gens</p> |
| </div> |
| |
| <div class="pattern-card bg-gray-700 p-4 rounded-lg cursor-pointer hover:bg-gray-600 transition" data-pattern="spiral"> |
| <div class="flex justify-center mb-2"> |
| <div class="h-16 w-16 rounded-full spiral-pattern"></div> |
| </div> |
| <h4 class="font-medium text-center">Spiral</h4> |
| <p class="text-xs text-gray-400 text-center mt-1">Creates rotating patterns</p> |
| </div> |
| |
| <div class="pattern-card bg-gray-700 p-4 rounded-lg cursor-pointer hover:bg-gray-600 transition" data-pattern="random"> |
| <div class="flex justify-center mb-2"> |
| <div class="grid grid-cols-3 gap-1 w-24"> |
| <div class="cell bg-blue-500 h-6 w-6"></div> |
| <div class="cell bg-gray-800 h-6 w-6"></div> |
| <div class="cell bg-blue-500 h-6 w-6"></div> |
| <div class="cell bg-gray-800 h-6 w-6"></div> |
| <div class="cell bg-blue-500 h-6 w-6"></div> |
| <div class="cell bg-gray-800 h-6 w-6"></div> |
| <div class="cell bg-blue-500 h-6 w-6"></div> |
| <div class="cell bg-gray-800 h-6 w-6"></div> |
| <div class="cell bg-blue-500 h-6 w-6"></div> |
| </div> |
| </div> |
| <h4 class="font-medium text-center">Random</h4> |
| <p class="text-xs text-gray-400 text-center mt-1">Random pattern</p> |
| </div> |
| </div> |
| |
| <div class="mt-6 flex justify-end"> |
| <button id="cancelPattern" class="px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded mr-2"> |
| Cancel |
| </button> |
| <button id="confirmPattern" class="px-4 py-2 bg-purple-600 hover:bg-purple-700 rounded"> |
| Inject Pattern |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| <script> |
| |
| const gameState = { |
| grid: [], |
| nextGrid: [], |
| width: 30, |
| height: 30, |
| isRunning: false, |
| generation: 0, |
| speed: 300, |
| timer: null, |
| mutations: 0, |
| emergenceCount: 0, |
| rules: { |
| birth: [3], |
| survive: [2, 3], |
| death: [0, 1, 4, 5, 6, 7, 8] |
| }, |
| cellStates: { |
| dead: 0, |
| alive: 1, |
| dying: 2, |
| emerging: 3, |
| mutated: 4 |
| }, |
| currentAction: 'stimulus', |
| selectedPattern: null, |
| activeCells: 0, |
| dyingCells: 0, |
| emergingCells: 0 |
| }; |
| |
| |
| const elements = { |
| grid: document.getElementById('grid'), |
| initGrid: document.getElementById('initGrid'), |
| startSim: document.getElementById('startSim'), |
| pauseSim: document.getElementById('pauseSim'), |
| stepSim: document.getElementById('stepSim'), |
| simSpeed: document.getElementById('simSpeed'), |
| gridWidth: document.getElementById('gridWidth'), |
| gridHeight: document.getElementById('gridHeight'), |
| initialDensity: document.getElementById('initialDensity'), |
| actionType: document.getElementById('actionType'), |
| stimulusOptions: document.getElementById('stimulusOptions'), |
| mutationOptions: document.getElementById('mutationOptions'), |
| patternOptions: document.getElementById('patternOptions'), |
| applyAction: document.getElementById('applyAction'), |
| generationCount: document.getElementById('generationCount'), |
| aliveCount: document.getElementById('aliveCount'), |
| dyingCount: document.getElementById('dyingCount'), |
| patternCount: document.getElementById('patternCount'), |
| mutationCount: document.getElementById('mutationCount'), |
| simStatus: document.getElementById('simStatus'), |
| statusMessage: document.getElementById('statusMessage'), |
| emergenceMessage: document.getElementById('emergenceMessage'), |
| emergenceText: document.getElementById('emergenceText'), |
| gridZoom: document.getElementById('gridZoom'), |
| patternModal: document.getElementById('patternModal'), |
| closePatternModal: document.getElementById('closePatternModal'), |
| confirmPattern: document.getElementById('confirmPattern'), |
| cancelPattern: document.getElementById('cancelPattern'), |
| patternType: document.getElementById('patternType') |
| }; |
| |
| |
| function initGame() { |
| gameState.width = parseInt(elements.gridWidth.value); |
| gameState.height = parseInt(elements.gridHeight.value); |
| gameState.generation = 0; |
| gameState.mutations = 0; |
| gameState.emergenceCount = 0; |
| |
| createGrid(); |
| updateStats(); |
| updateStatus('Grid initialized. Ready to start simulation.', 'blue'); |
| } |
| |
| |
| function createGrid() { |
| elements.grid.innerHTML = ''; |
| elements.grid.style.gridTemplateColumns = `repeat(${gameState.width}, 20px)`; |
| |
| gameState.grid = []; |
| gameState.nextGrid = []; |
| |
| const density = parseInt(elements.initialDensity.value) / 100; |
| |
| for (let y = 0; y < gameState.height; y++) { |
| gameState.grid[y] = []; |
| gameState.nextGrid[y] = []; |
| |
| for (let x = 0; x < gameState.width; x++) { |
| |
| const initialState = Math.random() < density ? |
| (Math.random() < 0.1 ? gameState.cellStates.emerging : gameState.cellStates.alive) : |
| gameState.cellStates.dead; |
| |
| gameState.grid[y][x] = initialState; |
| gameState.nextGrid[y][x] = initialState; |
| |
| const cell = document.createElement('div'); |
| cell.className = 'cell'; |
| cell.dataset.x = x; |
| cell.dataset.y = y; |
| |
| |
| updateCellAppearance(cell, initialState); |
| |
| |
| cell.addEventListener('click', () => toggleCellState(cell)); |
| |
| elements.grid.appendChild(cell); |
| } |
| } |
| |
| |
| countCellStates(); |
| } |
| |
| |
| function updateCellAppearance(cell, state) { |
| cell.className = 'cell'; |
| |
| switch (state) { |
| case gameState.cellStates.alive: |
| cell.classList.add('cell-alive'); |
| break; |
| case gameState.cellStates.dying: |
| cell.classList.add('cell-dying'); |
| break; |
| case gameState.cellStates.emerging: |
| cell.classList.add('cell-emerging'); |
| break; |
| case gameState.cellStates.mutated: |
| cell.classList.add('cell-mutated'); |
| break; |
| default: |
| |
| break; |
| } |
| } |
| |
| |
| function toggleCellState(cell) { |
| const x = parseInt(cell.dataset.x); |
| const y = parseInt(cell.dataset.y); |
| |
| |
| const currentState = gameState.grid[y][x]; |
| let newState; |
| |
| if (currentState === gameState.cellStates.dead) { |
| newState = gameState.cellStates.alive; |
| } else if (currentState === gameState.cellStates.alive) { |
| newState = gameState.cellStates.dying; |
| } else if (currentState === gameState.cellStates.dying) { |
| newState = gameState.cellStates.emerging; |
| } else if (currentState === gameState.cellStates.emerging) { |
| newState = gameState.cellStates.mutated; |
| } else { |
| newState = gameState.cellStates.dead; |
| } |
| |
| gameState.grid[y][x] = newState; |
| updateCellAppearance(cell, newState); |
| |
| |
| countCellStates(); |
| updateStats(); |
| } |
| |
| |
| function countCellStates() { |
| gameState.activeCells = 0; |
| gameState.dyingCells = 0; |
| gameState.emergingCells = 0; |
| |
| for (let y = 0; y < gameState.height; y++) { |
| for (let x = 0; x < gameState.width; x++) { |
| const state = gameState.grid[y][x]; |
| |
| if (state === gameState.cellStates.alive || state === gameState.cellStates.mutated) { |
| gameState.activeCells++; |
| } else if (state === gameState.cellStates.dying) { |
| gameState.dyingCells++; |
| } else if (state === gameState.cellStates.emerging) { |
| gameState.emergingCells++; |
| } |
| } |
| } |
| } |
| |
| |
| function startSimulation() { |
| if (gameState.isRunning) return; |
| |
| gameState.isRunning = true; |
| gameState.timer = setInterval(updateSimulation, gameState.speed); |
| |
| updateStatus('Simulation running...', 'green'); |
| elements.simStatus.textContent = 'Running'; |
| elements.simStatus.className = 'px-3 py-1 rounded-full text-xs font-semibold bg-green-600'; |
| } |
| |
| |
| function pauseSimulation() { |
| if (!gameState.isRunning) return; |
| |
| gameState.isRunning = false; |
| clearInterval(gameState.timer); |
| |
| updateStatus('Simulation paused.', 'yellow'); |
| elements.simStatus.textContent = 'Paused'; |
| elements.simStatus.className = 'px-3 py-1 rounded-full text-xs font-semibold bg-yellow-600'; |
| } |
| |
| |
| function stepSimulation() { |
| if (gameState.isRunning) { |
| pauseSimulation(); |
| } |
| |
| updateSimulation(); |
| updateStatus('Advanced one generation.', 'blue'); |
| } |
| |
| |
| function updateSimulation() { |
| |
| for (let y = 0; y < gameState.height; y++) { |
| for (let x = 0; x < gameState.width; x++) { |
| calculateNextState(x, y); |
| } |
| } |
| |
| |
| for (let y = 0; y < gameState.height; y++) { |
| for (let x = 0; x < gameState.width; x++) { |
| gameState.grid[y][x] = gameState.nextGrid[y][x]; |
| |
| const cell = document.querySelector(`.cell[data-x="${x}"][data-y="${y}"]`); |
| if (cell) { |
| updateCellAppearance(cell, gameState.grid[y][x]); |
| } |
| } |
| } |
| |
| gameState.generation++; |
| countCellStates(); |
| updateStats(); |
| |
| |
| checkEmergence(); |
| |
| |
| checkGameConditions(); |
| } |
| |
| |
| function calculateNextState(x, y) { |
| const currentState = gameState.grid[y][x]; |
| const neighbors = countNeighbors(x, y); |
| let nextState = currentState; |
| |
| |
| if (currentState === gameState.cellStates.dead) { |
| |
| if (gameState.rules.birth.includes(neighbors.total)) { |
| nextState = gameState.cellStates.alive; |
| } |
| } |
| else if (currentState === gameState.cellStates.alive) { |
| |
| if (gameState.rules.survive.includes(neighbors.total)) { |
| nextState = gameState.cellStates.alive; |
| } else { |
| nextState = gameState.cellStates.dying; |
| } |
| } |
| else if (currentState === gameState.cellStates.dying) { |
| |
| nextState = gameState.cellStates.dead; |
| } |
| else if (currentState === gameState.cellStates.emerging) { |
| |
| if (neighbors.total >= 2 && neighbors.total <= 4) { |
| nextState = gameState.cellStates.emerging; |
| } else if (neighbors.total === 0 || neighbors.total >= 5) { |
| nextState = gameState.cellStates.dying; |
| } else { |
| nextState = gameState.cellStates.alive; |
| } |
| } |
| else if (currentState === gameState.cellStates.mutated) { |
| |
| if (neighbors.total >= 1 && neighbors.total <= 3) { |
| nextState = gameState.cellStates.mutated; |
| } else { |
| nextState = gameState.cellStates.dying; |
| } |
| } |
| |
| gameState.nextGrid[y][x] = nextState; |
| } |
| |
| |
| function countNeighbors(x, y) { |
| let count = { |
| total: 0, |
| alive: 0, |
| dying: 0, |
| emerging: 0, |
| mutated: 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; |
| const ny = y + dy; |
| |
| |
| if (nx >= 0 && nx < gameState.width && ny >= 0 && ny < gameState.height) { |
| const neighborState = gameState.grid[ny][nx]; |
| |
| |
| if (neighborState !== gameState.cellStates.dead) { |
| count.total++; |
| |
| |
| if (neighborState === gameState.cellStates.alive) count.alive++; |
| else if (neighborState === gameState.cellStates.dying) count.dying++; |
| else if (neighborState === gameState.cellStates.emerging) count.emerging++; |
| else if (neighborState === gameState.cellStates.mutated) count.mutated++; |
| } |
| } |
| } |
| } |
| |
| return count; |
| } |
| |
| |
| function checkEmergence() { |
| |
| let patternsDetected = 0; |
| let patternDescription = ''; |
| |
| |
| for (let y = 0; y < gameState.height - 2; y++) { |
| for (let x = 0; x < gameState.width - 2; x++) { |
| |
| const c1 = gameState.grid[y][x+1] !== gameState.cellStates.dead; |
| const c2 = gameState.grid[y+1][x+2] !== gameState.cellStates.dead; |
| const c3 = gameState.grid[y+2][x] !== gameState.cellStates.dead; |
| const c4 = gameState.grid[y+2][x+1] !== gameState.cellStates.dead; |
| const c5 = gameState.grid[y+2][x+2] !== gameState.cellStates.dead; |
| |
| if (c1 && c2 && c3 && c4 && c5) { |
| patternsDetected++; |
| patternDescription = 'Glider pattern detected'; |
| } |
| } |
| } |
| |
| |
| if (gameState.generation % 10 === 0 && gameState.activeCells > gameState.width * gameState.height * 0.3) { |
| patternsDetected++; |
| patternDescription = 'Spiral formation emerging'; |
| } |
| |
| if (patternsDetected > 0) { |
| gameState.emergenceCount += patternsDetected; |
| showEmergenceMessage(patternDescription); |
| } |
| } |
| |
| |
| function showEmergenceMessage(message) { |
| elements.emergenceText.textContent = message; |
| elements.emergenceMessage.classList.remove('hidden'); |
| |
| |
| setTimeout(() => { |
| elements.emergenceMessage.classList.add('hidden'); |
| }, 5000); |
| } |
| |
| |
| function checkGameConditions() { |
| |
| if (gameState.activeCells === 0 && gameState.emergingCells === 0) { |
| pauseSimulation(); |
| updateStatus('All cells have died. Simulation ended.', 'red'); |
| elements.simStatus.textContent = 'Ended'; |
| elements.simStatus.className = 'px-3 py-1 rounded-full text-xs font-semibold bg-red-600'; |
| return; |
| } |
| |
| |
| |
| if (gameState.generation > 10 && gameState.activeCells > 0 && |
| gameState.activeCells === gameState.nextActiveCells) { |
| pauseSimulation(); |
| updateStatus('Stable pattern achieved! Simulation complete.', 'green'); |
| elements.simStatus.textContent = 'Complete'; |
| elements.simStatus.className = 'px-3 py-1 rounded-full text-xs font-semibold bg-green-600'; |
| return; |
| } |
| } |
| |
| |
| function updateStats() { |
| elements.generationCount.textContent = gameState.generation; |
| elements.aliveCount.textContent = gameState.activeCells; |
| elements.dyingCount.textContent = gameState.dyingCells; |
| elements.patternCount.textContent = gameState.emergenceCount; |
| elements.mutationCount.textContent = gameState.mutations; |
| } |
| |
| |
| function updateStatus(message, color = 'blue') { |
| elements.statusMessage.textContent = message; |
| |
| |
| elements.statusMessage.classList.add(`bg-${color}-900/20`, 'border', `border-${color}-700`, 'p-2', 'rounded'); |
| |
| |
| setTimeout(() => { |
| elements.statusMessage.classList.remove(`bg-${color}-900/20`, 'border', `border-${color}-700`, 'p-2', 'rounded'); |
| }, 3000); |
| } |
| |
| |
| function applyPlayerAction() { |
| const actionType = elements.actionType.value; |
| |
| if (actionType === 'stimulus') { |
| applyStimulus(); |
| } else if (actionType === 'mutation') { |
| applyMutation(); |
| } else if (actionType === 'inject') { |
| showPatternModal(); |
| } |
| } |
| |
| |
| function applyStimulus() { |
| const stimulusType = elements.stimulusType.value; |
| const cellsToAffect = Math.floor(gameState.width * gameState.height * 0.1); |
| |
| for (let i = 0; i < cellsToAffect; i++) { |
| const x = Math.floor(Math.random() * gameState.width); |
| const y = Math.floor(Math.random() * gameState.height); |
| |
| let newState; |
| |
| if (stimulusType === 'activate') { |
| newState = gameState.cellStates.alive; |
| } else if (stimulusType === 'kill') { |
| newState = gameState.cellStates.dying; |
| } else if (stimulusType === 'energize') { |
| newState = gameState.cellStates.emerging; |
| } |
| |
| gameState.grid[y][x] = newState; |
| |
| const cell = document.querySelector(`.cell[data-x="${x}"][data-y="${y}"]`); |
| if (cell) { |
| updateCellAppearance(cell, newState); |
| } |
| } |
| |
| countCellStates(); |
| updateStats(); |
| updateStatus(`Applied ${stimulusType} stimulus to ${cellsToAffect} cells.`, 'purple'); |
| } |
| |
| |
| function applyMutation() { |
| const mutationType = elements.mutationType.value; |
| gameState.mutations++; |
| |
| if (mutationType === 'ruleChange') { |
| |
| const ruleToChange = Math.random() < 0.5 ? 'birth' : 'survive'; |
| |
| if (ruleToChange === 'birth') { |
| gameState.rules.birth = [Math.floor(Math.random() * 5) + 1]; |
| updateStatus(`Mutation: Birth rule changed to ${gameState.rules.birth[0]} neighbors.`, 'pink'); |
| } else { |
| const min = Math.floor(Math.random() * 3); |
| gameState.rules.survive = [min, min + 1, min + 2].filter(n => n <= 8); |
| updateStatus(`Mutation: Survival rules changed to ${gameState.rules.survive.join(', ')} neighbors.`, 'pink'); |
| } |
| } |
| else if (mutationType === 'cellProperty') { |
| |
| const cellsToMutate = Math.floor(gameState.width * gameState.height * 0.05); |
| |
| for (let i = 0; i < cellsToMutate; i++) { |
| const x = Math.floor(Math.random() * gameState.width); |
| const y = Math.floor(Math.random() * gameState.height); |
| |
| gameState.grid[y][x] = gameState.cellStates.mutated; |
| |
| const cell = document.querySelector(`.cell[data-x="${x}"][data-y="${y}"]`); |
| if (cell) { |
| updateCellAppearance(cell, gameState.cellStates.mutated); |
| } |
| } |
| |
| updateStatus(`Mutation: ${cellsToMutate} cells mutated with new properties.`, 'pink'); |
| } |
| else if (mutationType === 'environment') { |
| |
| const effect = Math.random() < 0.5 ? 'boost' : 'suppress'; |
| |
| if (effect === 'boost') { |
| |
| for (let y = 0; y < gameState.height; y++) { |
| for (let x = 0; x < gameState.width; x++) { |
| if (gameState.grid[y][x] === gameState.cellStates.alive && Math.random() < 0.3) { |
| gameState.grid[y][x] = gameState.cellStates.emerging; |
| |
| const cell = document.querySelector(`.cell[data-x="${x}"][data-y="${y}"]`); |
| if (cell) { |
| updateCellAppearance(cell, gameState.cellStates.emerging); |
| } |
| } |
| } |
| } |
| |
| updateStatus('Mutation: Environment boosted emerging cells.', 'pink'); |
| } else { |
| |
| for (let y = 0; y < gameState.height; y++) { |
| for (let x = 0; x < gameState.width; x++) { |
| if (gameState.grid[y][x] !== gameState.cellStates.dead && Math.random() < 0.2) { |
| gameState.grid[y][x] = gameState.cellStates.dying; |
| |
| const cell = document.querySelector(`.cell[data-x="${x}"][data-y="${y}"]`); |
| if (cell) { |
| updateCellAppearance(cell, gameState.cellStates.dying); |
| } |
| } |
| } |
| } |
| |
| updateStatus('Mutation: Environment suppressing cell activity.', 'pink'); |
| } |
| } |
| |
| countCellStates(); |
| updateStats(); |
| } |
| |
| |
| function showPatternModal() { |
| elements.patternModal.classList.remove('hidden'); |
| } |
| |
| |
| function hidePatternModal() { |
| elements.patternModal.classList.add('hidden'); |
| } |
| |
| |
| function injectPattern() { |
| const patternType = elements.patternType.value; |
| const centerX = Math.floor(gameState.width / 2); |
| const centerY = Math.floor(gameState.height / 2); |
| |
| |
| gameState.selectedPattern = null; |
| |
| |
| if (patternType === 'glider') { |
| |
| setCellState(centerX, centerY, gameState.cellStates.dead); |
| setCellState(centerX + 1, centerY, gameState.cellStates.alive); |
| setCellState(centerX + 2, centerY, gameState.cellStates.dead); |
| setCellState(centerX, centerY + 1, gameState.cellStates.dead); |
| setCellState(centerX + 1, centerY + 1, gameState.cellStates.dead); |
| setCellState(centerX + 2, centerY + 1, gameState.cellStates.alive); |
| setCellState(centerX, centerY + 2, gameState.cellStates.alive); |
| setCellState(centerX + 1, centerY + 2, gameState.cellStates.alive); |
| setCellState(centerX + 2, centerY + 2, gameState.cellStates.alive); |
| |
| updateStatus('Glider pattern injected at center.', 'blue'); |
| } |
| else if (patternType === 'pulsar') { |
| |
| for (let dy = -1; dy <= 1; dy++) { |
| for (let dx = -1; dx <= 1; dx++) { |
| if (Math.abs(dx) === 1 || Math.abs(dy) === 1) { |
| setCellState(centerX + dx * 3, centerY + dy * 3, gameState.cellStates.alive); |
| } |
| } |
| } |
| |
| updateStatus('Pulsar pattern injected at center.', 'blue'); |
| } |
| else if (patternType === 'spiral') { |
| |
| const radius = Math.min(centerX, centerY) - 2; |
| |
| for (let r = 0; r < radius; r++) { |
| const angle = (r / radius) * Math.PI * 4; |
| const x = centerX + Math.floor(Math.cos(angle) * r); |
| const y = centerY + Math.floor(Math.sin(angle) * r); |
| |
| if (x >= 0 && x < gameState.width && y >= 0 && y < gameState.height) { |
| setCellState(x, y, gameState.cellStates.emerging); |
| } |
| } |
| |
| updateStatus('Spiral pattern injected at center.', 'blue'); |
| } |
| |
| countCellStates(); |
| updateStats(); |
| hidePatternModal(); |
| } |
| |
| |
| function setCellState(x, y, state) { |
| if (x >= 0 && x < gameState.width && y >= 0 && y < gameState.height) { |
| gameState.grid[y][x] = state; |
| |
| const cell = document.querySelector(`.cell[data-x="${x}"][data-y="${y}"]`); |
| if (cell) { |
| updateCellAppearance(cell, state); |
| } |
| } |
| } |
| |
| |
| elements.initGrid.addEventListener('click', initGame); |
| elements.startSim.addEventListener('click', startSimulation); |
| elements.pauseSim.addEventListener('click', pauseSimulation); |
| elements.stepSim.addEventListener('click', stepSimulation); |
| elements.applyAction.addEventListener('click', applyPlayerAction); |
| |
| elements.simSpeed.addEventListener('input', function() { |
| gameState.speed = 1050 - this.value; |
| |
| if (gameState.isRunning) { |
| clearInterval(gameState.timer); |
| gameState.timer = setInterval(updateSimulation, gameState.speed); |
| } |
| }); |
| |
| elements.gridZoom.addEventListener('input', function() { |
| const zoom = this.value / 100; |
| elements.grid.style.transform = `scale(${zoom})`; |
| }); |
| |
| elements.actionType.addEventListener('change', function() { |
| gameState.currentAction = this.value; |
| |
| elements.stimulusOptions.classList.add('hidden'); |
| elements.mutationOptions.classList.add('hidden'); |
| elements.patternOptions.classList.add('hidden'); |
| |
| if (this.value === 'stimulus') { |
| elements.stimulusOptions.classList.remove('hidden'); |
| } else if (this.value === 'mutation') { |
| elements.mutationOptions.classList.remove('hidden'); |
| } else if (this.value === 'inject') { |
| elements.patternOptions.classList.remove('hidden'); |
| } |
| }); |
| |
| elements.closePatternModal.addEventListener('click', hidePatternModal); |
| elements.cancelPattern.addEventListener('click', hidePatternModal); |
| elements.confirmPattern.addEventListener('click', injectPattern); |
| |
| |
| document.querySelectorAll('.pattern-card').forEach(card => { |
| card.addEventListener('click', function() { |
| document.querySelectorAll('.pattern-card').forEach(c => { |
| c.classList.remove('ring-2', 'ring-purple-500'); |
| }); |
| |
| this.classList.add('ring-2', 'ring-purple-500'); |
| gameState.selectedPattern = this.dataset.pattern; |
| elements.patternType.value = this.dataset.pattern; |
| }); |
| }); |
| |
| |
| initGame(); |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=timescroll/spiral-colony-simulation" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |