timescroll's picture
Add 3 files
3793114 verified
<!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">
<!-- Control Panel -->
<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>
<!-- Main Grid Area -->
<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>
<!-- Status and Messages -->
<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>
<!-- Pattern Library Modal -->
<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">
<!-- Simplified pulsar pattern -->
<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>
<!-- More rows would complete the pattern -->
</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>
// Game state
const gameState = {
grid: [],
nextGrid: [],
width: 30,
height: 30,
isRunning: false,
generation: 0,
speed: 300,
timer: null,
mutations: 0,
emergenceCount: 0,
rules: {
birth: [3], // Cell is born if it has exactly 3 neighbors
survive: [2, 3], // Cell survives if it has 2 or 3 neighbors
death: [0, 1, 4, 5, 6, 7, 8] // Cell dies from under/overpopulation
},
cellStates: {
dead: 0,
alive: 1,
dying: 2,
emerging: 3,
mutated: 4
},
currentAction: 'stimulus',
selectedPattern: null,
activeCells: 0,
dyingCells: 0,
emergingCells: 0
};
// DOM elements
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')
};
// Initialize the game
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');
}
// Create the grid
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++) {
// Random initialization based on density
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;
// Set initial cell state
updateCellAppearance(cell, initialState);
// Add click handler to toggle cell state
cell.addEventListener('click', () => toggleCellState(cell));
elements.grid.appendChild(cell);
}
}
// Count initial cell states
countCellStates();
}
// Update cell appearance based on state
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:
// Dead cell - no additional class
break;
}
}
// Toggle cell state on click
function toggleCellState(cell) {
const x = parseInt(cell.dataset.x);
const y = parseInt(cell.dataset.y);
// Cycle through states
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);
// Update counts
countCellStates();
updateStats();
}
// Count different cell states
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++;
}
}
}
}
// Start the simulation
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';
}
// Pause the simulation
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';
}
// Step through one generation
function stepSimulation() {
if (gameState.isRunning) {
pauseSimulation();
}
updateSimulation();
updateStatus('Advanced one generation.', 'blue');
}
// Update the simulation state
function updateSimulation() {
// Calculate next state for all cells
for (let y = 0; y < gameState.height; y++) {
for (let x = 0; x < gameState.width; x++) {
calculateNextState(x, y);
}
}
// Apply next state to all cells
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();
// Check for emergence patterns
checkEmergence();
// Check win/lose conditions
checkGameConditions();
}
// Calculate next state for a cell
function calculateNextState(x, y) {
const currentState = gameState.grid[y][x];
const neighbors = countNeighbors(x, y);
let nextState = currentState;
// Apply different rules based on cell state
if (currentState === gameState.cellStates.dead) {
// Dead cell can become alive if it has exactly 3 neighbors
if (gameState.rules.birth.includes(neighbors.total)) {
nextState = gameState.cellStates.alive;
}
}
else if (currentState === gameState.cellStates.alive) {
// Alive cell survives with 2 or 3 neighbors, dies otherwise
if (gameState.rules.survive.includes(neighbors.total)) {
nextState = gameState.cellStates.alive;
} else {
nextState = gameState.cellStates.dying;
}
}
else if (currentState === gameState.cellStates.dying) {
// Dying cell becomes dead in next generation
nextState = gameState.cellStates.dead;
}
else if (currentState === gameState.cellStates.emerging) {
// Emerging cells have special behavior
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) {
// Mutated cells have different rules
if (neighbors.total >= 1 && neighbors.total <= 3) {
nextState = gameState.cellStates.mutated;
} else {
nextState = gameState.cellStates.dying;
}
}
gameState.nextGrid[y][x] = nextState;
}
// Count neighbors for a cell (including Moore neighborhood)
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++) {
// Skip the cell itself
if (dx === 0 && dy === 0) continue;
const nx = x + dx;
const ny = y + dy;
// Check bounds
if (nx >= 0 && nx < gameState.width && ny >= 0 && ny < gameState.height) {
const neighborState = gameState.grid[ny][nx];
// Count all non-dead neighbors
if (neighborState !== gameState.cellStates.dead) {
count.total++;
// Count specific types
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;
}
// Check for emergence patterns
function checkEmergence() {
// Simple pattern detection - in a real implementation this would be more sophisticated
let patternsDetected = 0;
let patternDescription = '';
// Check for gliders (simplified)
for (let y = 0; y < gameState.height - 2; y++) {
for (let x = 0; x < gameState.width - 2; x++) {
// Very simplified glider pattern check
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';
}
}
}
// Check for spiral patterns (simplified)
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);
}
}
// Show emergence message
function showEmergenceMessage(message) {
elements.emergenceText.textContent = message;
elements.emergenceMessage.classList.remove('hidden');
// Hide after 5 seconds
setTimeout(() => {
elements.emergenceMessage.classList.add('hidden');
}, 5000);
}
// Check win/lose conditions
function checkGameConditions() {
// Lose condition - all cells dead
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;
}
// Win condition - stable pattern (no changes for 5 generations)
// This would need more sophisticated tracking in a real implementation
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;
}
}
// Update statistics display
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;
}
// Update status message
function updateStatus(message, color = 'blue') {
elements.statusMessage.textContent = message;
// Add temporary highlight
elements.statusMessage.classList.add(`bg-${color}-900/20`, 'border', `border-${color}-700`, 'p-2', 'rounded');
// Remove highlight after 3 seconds
setTimeout(() => {
elements.statusMessage.classList.remove(`bg-${color}-900/20`, 'border', `border-${color}-700`, 'p-2', 'rounded');
}, 3000);
}
// Apply player action
function applyPlayerAction() {
const actionType = elements.actionType.value;
if (actionType === 'stimulus') {
applyStimulus();
} else if (actionType === 'mutation') {
applyMutation();
} else if (actionType === 'inject') {
showPatternModal();
}
}
// Apply stimulus to random cells
function applyStimulus() {
const stimulusType = elements.stimulusType.value;
const cellsToAffect = Math.floor(gameState.width * gameState.height * 0.1); // 10% of cells
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');
}
// Apply mutation
function applyMutation() {
const mutationType = elements.mutationType.value;
gameState.mutations++;
if (mutationType === 'ruleChange') {
// Randomly modify one of the rules
const ruleToChange = Math.random() < 0.5 ? 'birth' : 'survive';
if (ruleToChange === 'birth') {
gameState.rules.birth = [Math.floor(Math.random() * 5) + 1]; // 1-5
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') {
// Randomly mutate some cells
const cellsToMutate = Math.floor(gameState.width * gameState.height * 0.05); // 5% of cells
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') {
// Change the environment (affects all cells)
const effect = Math.random() < 0.5 ? 'boost' : 'suppress';
if (effect === 'boost') {
// Increase emerging cell count
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 {
// Suppress some cells
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();
}
// Show pattern selection modal
function showPatternModal() {
elements.patternModal.classList.remove('hidden');
}
// Hide pattern selection modal
function hidePatternModal() {
elements.patternModal.classList.add('hidden');
}
// Inject selected pattern
function injectPattern() {
const patternType = elements.patternType.value;
const centerX = Math.floor(gameState.width / 2);
const centerY = Math.floor(gameState.height / 2);
// Clear previous selection
gameState.selectedPattern = null;
// Apply pattern based on type
if (patternType === 'glider') {
// Simple glider pattern
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') {
// Simple pulsar pattern (simplified)
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') {
// Spiral pattern
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();
}
// Set cell state with bounds checking
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);
}
}
}
// Event listeners
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; // Invert so slider left is slow
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);
// Pattern card selection
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;
});
});
// Initialize the game
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>