Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Enhanced AI Flocking Evolution Simulator</title> | |
| <style> | |
| body { | |
| margin: 0; | |
| overflow: hidden; | |
| font-family: Arial, sans-serif; | |
| background: #000; | |
| } | |
| #ui { | |
| position: absolute; | |
| top: 10px; | |
| left: 10px; | |
| color: white; | |
| background-color: rgba(0,0,0,0.9); | |
| padding: 15px; | |
| border-radius: 8px; | |
| z-index: 100; | |
| font-size: 14px; | |
| min-width: 200px; | |
| } | |
| #controls { | |
| position: absolute; | |
| top: 10px; | |
| right: 10px; | |
| color: white; | |
| background-color: rgba(0,0,0,0.9); | |
| padding: 15px; | |
| border-radius: 8px; | |
| z-index: 100; | |
| } | |
| button { | |
| background-color: #4CAF50; | |
| border: none; | |
| color: white; | |
| padding: 8px 16px; | |
| margin: 5px; | |
| cursor: pointer; | |
| border-radius: 4px; | |
| font-size: 12px; | |
| } | |
| button:hover { | |
| background-color: #45a049; | |
| } | |
| #stats { | |
| position: absolute; | |
| bottom: 10px; | |
| left: 10px; | |
| color: white; | |
| background-color: rgba(0,0,0,0.9); | |
| padding: 15px; | |
| border-radius: 8px; | |
| z-index: 100; | |
| font-size: 12px; | |
| min-width: 200px; | |
| } | |
| #flockingStats { | |
| position: absolute; | |
| bottom: 10px; | |
| right: 10px; | |
| color: white; | |
| background-color: rgba(0,0,0,0.9); | |
| padding: 15px; | |
| border-radius: 8px; | |
| z-index: 100; | |
| font-size: 12px; | |
| min-width: 180px; | |
| } | |
| #aiStats { | |
| position: absolute; | |
| top: 50%; | |
| right: 10px; | |
| transform: translateY(-50%); | |
| color: white; | |
| background-color: rgba(0,0,0,0.9); | |
| padding: 15px; | |
| border-radius: 8px; | |
| z-index: 100; | |
| font-size: 12px; | |
| min-width: 180px; | |
| } | |
| .highlight { color: #ffcc00; font-weight: bold; } | |
| .success { color: #00ff00; font-weight: bold; } | |
| .flocking { color: #00aaff; } | |
| .solo { color: #ff8800; } | |
| .leader { color: #ff00ff; font-weight: bold; } | |
| .explorer { color: #00ffff; } | |
| .follower { color: #88ff88; } | |
| .species-0 { color: #ff6b6b; } | |
| .species-1 { color: #4ecdc4; } | |
| .species-2 { color: #45b7d1; } | |
| .species-3 { color: #96ceb4; } | |
| .species-4 { color: #ffd93d; } | |
| .progress-bar { | |
| width: 100%; | |
| height: 10px; | |
| background-color: #333; | |
| border-radius: 5px; | |
| overflow: hidden; | |
| margin: 5px 0; | |
| } | |
| .progress-fill { | |
| height: 100%; | |
| background: linear-gradient(90deg, #ff6b6b, #4ecdc4, #45b7d1); | |
| transition: width 0.3s ease; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="ui"> | |
| <div class="highlight">Enhanced AI Evolution Simulator</div> | |
| <div>Epoch: <span id="epoch">1</span></div> | |
| <div>Time: <span id="epochTime">60</span>s</div> | |
| <div class="progress-bar"><div class="progress-fill" id="timeProgress"></div></div> | |
| <div>Population: <span id="population">100</span></div> | |
| <div>Species: <span id="speciesCount">1</span></div> | |
| <div>Best Fitness: <span id="bestFitness">0</span></div> | |
| <div>Avg IQ: <span id="avgIQ">50</span></div> | |
| <div>Innovation: <span id="innovationCount">0</span></div> | |
| </div> | |
| <div id="controls"> | |
| <button id="pauseBtn">Pause</button> | |
| <button id="resetBtn">Reset</button> | |
| <button id="speedBtn">Speed: 1x</button> | |
| <button id="viewBtn">View: Follow</button> | |
| <button id="flockBtn">Flocks: ON</button> | |
| <button id="adaptiveBtn">Adaptive: ON</button> | |
| <button id="challengeBtn">Challenge: Normal</button> | |
| </div> | |
| <div id="stats"> | |
| <div><span class="highlight">Top Performers:</span></div> | |
| <div id="topPerformers"></div> | |
| <div style="margin-top: 10px;"><span class="highlight">Generation Stats:</span></div> | |
| <div>Crashes: <span id="crashCount">0</span></div> | |
| <div>Total Distance: <span id="totalDistance">0</span></div> | |
| <div>Exploration: <span id="explorationBonus">0</span></div> | |
| <div>Cooperation: <span id="cooperationScore">0</span></div> | |
| <div>Road Mastery: <span id="roadMastery">0</span>%</div> | |
| </div> | |
| <div id="flockingStats"> | |
| <div><span class="highlight">Flocking Dynamics:</span></div> | |
| <div><span class="leader">Leaders:</span> <span id="leaderCount">0</span></div> | |
| <div><span class="flocking">Followers:</span> <span id="followerCount">0</span></div> | |
| <div><span class="explorer">Explorers:</span> <span id="explorerCount">0</span></div> | |
| <div><span class="solo">Solo:</span> <span id="soloCount">0</span></div> | |
| <div>Largest Flock: <span id="largestFlock">0</span></div> | |
| <div>Avg Coordination: <span id="avgCoordination">0</span>%</div> | |
| <div>Group Efficiency: <span id="groupEfficiency">0</span>%</div> | |
| </div> | |
| <div id="aiStats"> | |
| <div><span class="highlight">AI Intelligence:</span></div> | |
| <div>Neural Complexity: <span id="neuralComplexity">100</span></div> | |
| <div>Decision Quality: <span id="decisionQuality">50</span>%</div> | |
| <div>Learning Rate: <span id="learningRate">1.0</span></div> | |
| <div>Memory Usage: <span id="memoryUsage">0</span>%</div> | |
| <div style="margin-top: 10px;"><span class="highlight">Behaviors:</span></div> | |
| <div>Predictive: <span id="predictiveBehavior">0</span>%</div> | |
| <div>Adaptive: <span id="adaptiveBehavior">0</span>%</div> | |
| <div>Emergent: <span id="emergentBehavior">0</span>%</div> | |
| </div> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> | |
| <script> | |
| // Global variables | |
| let scene, camera, renderer, clock; | |
| let world = { | |
| roads: [], | |
| intersections: [], | |
| buildings: [], | |
| jumpRamps: [], | |
| flockLines: [], | |
| dynamicObstacles: [], | |
| targets: [] | |
| }; | |
| // Enhanced evolution system | |
| let epoch = 1; | |
| let epochTime = 60; | |
| let timeLeft = 60; | |
| let population = []; | |
| let species = []; | |
| let populationSize = 100; | |
| let bestFitness = 0; | |
| let totalDistance = 0; | |
| let groupDistance = 0; | |
| let crashCount = 0; | |
| let paused = false; | |
| let speedMultiplier = 1; | |
| let cameraMode = 'follow'; | |
| let showFlockLines = true; | |
| let adaptiveEnvironment = true; | |
| let challengeLevel = 'normal'; // normal, hard, extreme | |
| let innovationCounter = 0; | |
| let globalMemory = new Map(); | |
| // Enhanced AI parameters | |
| const NEIGHBOR_RADIUS = 30; | |
| const SEPARATION_RADIUS = 10; | |
| const LEADERSHIP_RADIUS = 40; | |
| const MEMORY_SIZE = 10; | |
| const SPECIES_THRESHOLD = 3.0; | |
| const TARGET_SPECIES = 5; | |
| // Dynamic challenge system | |
| let dynamicChallenges = { | |
| obstacles: [], | |
| targets: [], | |
| weather: 'clear', | |
| timeOfDay: 'day' | |
| }; | |
| // Enhanced Neural Network with memory and multiple layers | |
| class EnhancedNeuralNetwork { | |
| constructor() { | |
| this.inputSize = 24; // Expanded sensory inputs | |
| this.hiddenLayers = [32, 24, 16]; // Multi-layer deep network | |
| this.outputSize = 8; // More nuanced outputs | |
| this.memorySize = MEMORY_SIZE; | |
| // Initialize all weight matrices and biases | |
| this.weights = []; | |
| this.biases = []; | |
| this.memory = new Array(this.memorySize).fill(0); | |
| this.memoryPointer = 0; | |
| // Build network layers | |
| let prevSize = this.inputSize + this.memorySize; | |
| for (let i = 0; i < this.hiddenLayers.length; i++) { | |
| this.weights.push(this.randomMatrix(prevSize, this.hiddenLayers[i])); | |
| this.biases.push(this.randomArray(this.hiddenLayers[i])); | |
| prevSize = this.hiddenLayers[i]; | |
| } | |
| // Output layer | |
| this.weights.push(this.randomMatrix(prevSize, this.outputSize)); | |
| this.biases.push(this.randomArray(this.outputSize)); | |
| // Specialized modules | |
| this.attentionWeights = this.randomArray(this.inputSize); | |
| this.innovationGenes = this.randomArray(10); | |
| this.personalityTraits = { | |
| leadership: Math.random(), | |
| exploration: Math.random(), | |
| cooperation: Math.random(), | |
| caution: Math.random(), | |
| adaptability: Math.random() | |
| }; | |
| } | |
| randomMatrix(rows, cols) { | |
| let matrix = []; | |
| for (let i = 0; i < rows; i++) { | |
| matrix[i] = []; | |
| for (let j = 0; j < cols; j++) { | |
| matrix[i][j] = (Math.random() - 0.5) * 2; | |
| } | |
| } | |
| return matrix; | |
| } | |
| randomArray(size) { | |
| return Array(size).fill().map(() => (Math.random() - 0.5) * 2); | |
| } | |
| // Advanced activation with attention mechanism | |
| activate(inputs) { | |
| // Apply attention mechanism to inputs | |
| const attentionScores = inputs.map((input, i) => | |
| input * this.sigmoid(this.attentionWeights[i]) | |
| ); | |
| // Combine inputs with memory | |
| let currentInput = [...attentionScores, ...this.memory]; | |
| // Forward pass through hidden layers | |
| for (let layer = 0; layer < this.hiddenLayers.length; layer++) { | |
| currentInput = this.forwardLayer(currentInput, this.weights[layer], this.biases[layer]); | |
| } | |
| // Output layer | |
| const outputs = this.forwardLayer(currentInput, | |
| this.weights[this.weights.length - 1], | |
| this.biases[this.biases.length - 1]); | |
| // Update memory with current state | |
| this.updateMemory(inputs, outputs); | |
| return outputs; | |
| } | |
| forwardLayer(inputs, weights, biases) { | |
| const outputs = new Array(weights[0].length).fill(0); | |
| for (let i = 0; i < outputs.length; i++) { | |
| for (let j = 0; j < inputs.length; j++) { | |
| outputs[i] += inputs[j] * weights[j][i]; | |
| } | |
| outputs[i] += biases[i]; | |
| outputs[i] = this.advancedActivation(outputs[i]); | |
| } | |
| return outputs; | |
| } | |
| // Advanced activation function combining sigmoid and tanh | |
| advancedActivation(x) { | |
| const clampedX = Math.max(-10, Math.min(10, x)); | |
| return (this.sigmoid(clampedX) + Math.tanh(clampedX)) / 2; | |
| } | |
| sigmoid(x) { | |
| return 1 / (1 + Math.exp(-Math.max(-500, Math.min(500, x)))); | |
| } | |
| updateMemory(inputs, outputs) { | |
| // Store important environmental information | |
| const importance = Math.max(...inputs.slice(0, 8)); // Obstacle sensor max | |
| this.memory[this.memoryPointer] = importance; | |
| this.memoryPointer = (this.memoryPointer + 1) % this.memorySize; | |
| } | |
| // Advanced mutation with adaptive rates | |
| mutate(baseRate = 0.1, innovation = false) { | |
| const adaptiveRate = baseRate * (1 + this.personalityTraits.adaptability); | |
| // Mutate weights | |
| this.weights.forEach(weightMatrix => { | |
| this.mutateMatrix(weightMatrix, adaptiveRate); | |
| }); | |
| // Mutate biases | |
| this.biases.forEach(biasArray => { | |
| this.mutateArray(biasArray, adaptiveRate); | |
| }); | |
| // Mutate attention weights | |
| this.mutateArray(this.attentionWeights, adaptiveRate * 0.5); | |
| // Mutate personality traits | |
| Object.keys(this.personalityTraits).forEach(trait => { | |
| if (Math.random() < adaptiveRate) { | |
| this.personalityTraits[trait] += (Math.random() - 0.5) * 0.2; | |
| this.personalityTraits[trait] = Math.max(0, Math.min(1, this.personalityTraits[trait])); | |
| } | |
| }); | |
| // Innovation mutations | |
| if (innovation) { | |
| this.mutateArray(this.innovationGenes, adaptiveRate * 2); | |
| innovationCounter++; | |
| } | |
| } | |
| mutateMatrix(matrix, rate) { | |
| for (let i = 0; i < matrix.length; i++) { | |
| for (let j = 0; j < matrix[i].length; j++) { | |
| if (Math.random() < rate) { | |
| const mutationStrength = 0.5 * (1 + Math.random()); | |
| matrix[i][j] += (Math.random() - 0.5) * mutationStrength; | |
| matrix[i][j] = Math.max(-5, Math.min(5, matrix[i][j])); // Clamp weights | |
| } | |
| } | |
| } | |
| } | |
| mutateArray(array, rate) { | |
| for (let i = 0; i < array.length; i++) { | |
| if (Math.random() < rate) { | |
| const mutationStrength = 0.5 * (1 + Math.random()); | |
| array[i] += (Math.random() - 0.5) * mutationStrength; | |
| array[i] = Math.max(-5, Math.min(5, array[i])); // Clamp values | |
| } | |
| } | |
| } | |
| // Crossover with compatibility checking | |
| crossover(other) { | |
| const child = new EnhancedNeuralNetwork(); | |
| // Blend weights and biases | |
| for (let layer = 0; layer < this.weights.length; layer++) { | |
| for (let i = 0; i < this.weights[layer].length; i++) { | |
| for (let j = 0; j < this.weights[layer][i].length; j++) { | |
| child.weights[layer][i][j] = Math.random() < 0.5 ? | |
| this.weights[layer][i][j] : other.weights[layer][i][j]; | |
| } | |
| } | |
| for (let i = 0; i < this.biases[layer].length; i++) { | |
| child.biases[layer][i] = Math.random() < 0.5 ? | |
| this.biases[layer][i] : other.biases[layer][i]; | |
| } | |
| } | |
| // Blend personality traits | |
| Object.keys(this.personalityTraits).forEach(trait => { | |
| child.personalityTraits[trait] = (this.personalityTraits[trait] + other.personalityTraits[trait]) / 2; | |
| }); | |
| return child; | |
| } | |
| copy() { | |
| const newNN = new EnhancedNeuralNetwork(); | |
| // Deep copy all components | |
| newNN.weights = this.weights.map(matrix => | |
| matrix.map(row => [...row]) | |
| ); | |
| newNN.biases = this.biases.map(bias => [...bias]); | |
| newNN.attentionWeights = [...this.attentionWeights]; | |
| newNN.memory = [...this.memory]; | |
| newNN.memoryPointer = this.memoryPointer; | |
| newNN.innovationGenes = [...this.innovationGenes]; | |
| newNN.personalityTraits = {...this.personalityTraits}; | |
| return newNN; | |
| } | |
| // Calculate network complexity for visualization | |
| getComplexity() { | |
| let totalConnections = 0; | |
| this.weights.forEach(matrix => { | |
| totalConnections += matrix.length * matrix[0].length; | |
| }); | |
| return totalConnections; | |
| } | |
| } | |
| // Enhanced AI Car with advanced behaviors | |
| class EnhancedAICar { | |
| constructor(x = 0, z = 0) { | |
| this.brain = new EnhancedNeuralNetwork(); | |
| this.mesh = this.createCarMesh(); | |
| this.mesh.position.set(x, 1, z); | |
| // Enhanced movement properties | |
| this.velocity = new THREE.Vector3( | |
| (Math.random() - 0.5) * 10, 0, (Math.random() - 0.5) * 10 | |
| ); | |
| this.acceleration = new THREE.Vector3(); | |
| this.maxSpeed = 25; | |
| this.minSpeed = 3; | |
| this.accelerationForce = 0.6; | |
| this.turnSpeed = 0.1; | |
| // Advanced flocking and behavior | |
| this.neighbors = []; | |
| this.role = 'follower'; // leader, follower, explorer, scout | |
| this.flockId = -1; | |
| this.speciesId = 0; | |
| this.leadership = this.brain.personalityTraits.leadership; | |
| this.exploration = this.brain.personalityTraits.exploration; | |
| this.cooperation = this.brain.personalityTraits.cooperation; | |
| // Enhanced fitness and metrics | |
| this.fitness = 0; | |
| this.rawFitness = 0; | |
| this.adjustedFitness = 0; | |
| this.distanceTraveled = 0; | |
| this.explorationBonus = 0; | |
| this.cooperationScore = 0; | |
| this.leadershipScore = 0; | |
| this.innovationScore = 0; | |
| this.decisionQuality = 50; | |
| this.predictiveAccuracy = 0; | |
| // State tracking | |
| this.timeAlive = 100; | |
| this.crashed = false; | |
| this.lastPosition = new THREE.Vector3(x, 1, z); | |
| this.visitedAreas = new Set(); | |
| this.decisions = []; | |
| this.predictions = []; | |
| // Enhanced sensors | |
| this.sensors = Array(12).fill(0); // More sensors | |
| this.environmentSensors = Array(4).fill(0); | |
| this.socialSensors = Array(8).fill(0); | |
| this.sensorRays = []; | |
| this.flockLines = []; | |
| this.createSensorRays(); | |
| this.createFlockVisualization(); | |
| this.initializeMovement(); | |
| } | |
| createCarMesh() { | |
| const group = new THREE.Group(); | |
| // Enhanced car body with role-based styling | |
| const bodyGeometry = new THREE.BoxGeometry(1.5, 0.8, 3); | |
| this.bodyMaterial = new THREE.MeshLambertMaterial({ | |
| color: new THREE.Color().setHSL(Math.random(), 0.8, 0.6) | |
| }); | |
| const body = new THREE.Mesh(bodyGeometry, this.bodyMaterial); | |
| body.position.y = 0.4; | |
| body.castShadow = true; | |
| group.add(body); | |
| // Role indicator | |
| const indicatorGeometry = new THREE.SphereGeometry(0.2, 8, 6); | |
| this.roleIndicator = new THREE.Mesh(indicatorGeometry, | |
| new THREE.MeshLambertMaterial({ color: 0xffffff })); | |
| this.roleIndicator.position.set(0, 1.5, 0); | |
| group.add(this.roleIndicator); | |
| // Intelligence indicator (size based on neural complexity) | |
| const complexity = this.brain.getComplexity(); | |
| const brainSize = 0.1 + (complexity / 10000) * 0.4; | |
| const brainGeometry = new THREE.SphereGeometry(brainSize, 6, 4); | |
| this.brainIndicator = new THREE.Mesh(brainGeometry, | |
| new THREE.MeshLambertMaterial({ | |
| color: 0x00ffff, | |
| transparent: true, | |
| opacity: 0.7 | |
| })); | |
| this.brainIndicator.position.set(0, 1.8, 0); | |
| group.add(this.brainIndicator); | |
| // Enhanced wheels with rotation | |
| const wheelGeometry = new THREE.CylinderGeometry(0.3, 0.3, 0.2, 8); | |
| const wheelMaterial = new THREE.MeshLambertMaterial({ color: 0x333333 }); | |
| this.wheels = []; | |
| const wheelPositions = [ | |
| [-0.8, 0, 1.2], [0.8, 0, 1.2], | |
| [-0.8, 0, -1.2], [0.8, 0, -1.2] | |
| ]; | |
| wheelPositions.forEach((pos, i) => { | |
| const wheel = new THREE.Mesh(wheelGeometry, wheelMaterial); | |
| wheel.position.set(...pos); | |
| wheel.rotation.z = Math.PI / 2; | |
| this.wheels.push(wheel); | |
| group.add(wheel); | |
| }); | |
| return group; | |
| } | |
| createSensorRays() { | |
| const sensorMaterial = new THREE.LineBasicMaterial({ | |
| color: 0xff0000, | |
| transparent: true, | |
| opacity: 0.3 | |
| }); | |
| // 12 sensors for comprehensive environment detection | |
| for (let i = 0; i < 12; i++) { | |
| const geometry = new THREE.BufferGeometry().setFromPoints([ | |
| new THREE.Vector3(0, 0, 0), | |
| new THREE.Vector3(0, 0, 8) | |
| ]); | |
| const ray = new THREE.Line(geometry, sensorMaterial); | |
| this.sensorRays.push(ray); | |
| this.mesh.add(ray); | |
| } | |
| } | |
| createFlockVisualization() { | |
| const flockMaterial = new THREE.LineBasicMaterial({ | |
| color: 0x00ff00, | |
| transparent: true, | |
| opacity: 0.3 | |
| }); | |
| for (let i = 0; i < 8; i++) { | |
| const geometry = new THREE.BufferGeometry().setFromPoints([ | |
| new THREE.Vector3(0, 2, 0), | |
| new THREE.Vector3(0, 2, 0) | |
| ]); | |
| const line = new THREE.Line(geometry, flockMaterial); | |
| this.flockLines.push(line); | |
| if (showFlockLines) scene.add(line); | |
| } | |
| } | |
| initializeMovement() { | |
| this.mesh.rotation.y = Math.random() * Math.PI * 2; | |
| this.velocity.set( | |
| Math.sin(this.mesh.rotation.y) * (8 + Math.random() * 7), | |
| 0, | |
| Math.cos(this.mesh.rotation.y) * (8 + Math.random() * 7) | |
| ); | |
| } | |
| updateEnhancedSensors() { | |
| const maxDistance = 8; | |
| const raycaster = new THREE.Raycaster(); | |
| // 12-direction sensor array | |
| const sensorAngles = []; | |
| for (let i = 0; i < 12; i++) { | |
| sensorAngles.push((i * Math.PI * 2) / 12); | |
| } | |
| sensorAngles.forEach((angle, i) => { | |
| const direction = new THREE.Vector3( | |
| Math.sin(angle), 0, Math.cos(angle) | |
| ); | |
| direction.applyQuaternion(this.mesh.quaternion); | |
| raycaster.set(this.mesh.position, direction); | |
| const intersects = raycaster.intersectObjects(this.getObstacles(), true); | |
| if (intersects.length > 0 && intersects[0].distance <= maxDistance) { | |
| this.sensors[i] = 1 - (intersects[0].distance / maxDistance); | |
| } else { | |
| this.sensors[i] = 0; | |
| } | |
| // Update visual ray | |
| const endDistance = intersects.length > 0 ? | |
| Math.min(intersects[0].distance, maxDistance) : maxDistance; | |
| const rayEnd = direction.clone().multiplyScalar(endDistance); | |
| this.sensorRays[i].geometry.setFromPoints([ | |
| new THREE.Vector3(0, 0, 0), rayEnd | |
| ]); | |
| }); | |
| // Environment sensors | |
| this.updateEnvironmentSensors(); | |
| } | |
| updateEnvironmentSensors() { | |
| const pos = this.mesh.position; | |
| // Road detection with direction | |
| this.environmentSensors[0] = this.detectRoadPosition(); | |
| // Obstacle density in area | |
| let nearbyObstacles = 0; | |
| population.forEach(other => { | |
| if (other !== this && !other.crashed) { | |
| const dist = pos.distanceTo(other.mesh.position); | |
| if (dist < 20) nearbyObstacles++; | |
| } | |
| }); | |
| this.environmentSensors[1] = Math.min(nearbyObstacles / 5, 1); | |
| // Target/goal direction (if any targets exist) | |
| this.environmentSensors[2] = this.getTargetDirection(); | |
| // Exploration potential | |
| this.environmentSensors[3] = this.getExplorationPotential(); | |
| } | |
| updateAdvancedFlocking() { | |
| this.neighbors = []; | |
| this.socialSensors.fill(0); | |
| let separation = new THREE.Vector3(); | |
| let alignment = new THREE.Vector3(); | |
| let cohesion = new THREE.Vector3(); | |
| let leadership = new THREE.Vector3(); | |
| let neighborCount = 0; | |
| let leaderInfluence = 0; | |
| population.forEach(other => { | |
| if (other !== this && !other.crashed) { | |
| const distance = this.mesh.position.distanceTo(other.mesh.position); | |
| if (distance < NEIGHBOR_RADIUS && distance > 0) { | |
| this.neighbors.push(other); | |
| // Traditional flocking forces | |
| cohesion.add(other.mesh.position); | |
| alignment.add(other.velocity); | |
| // Leadership dynamics | |
| if (other.role === 'leader' && distance < LEADERSHIP_RADIUS) { | |
| const influence = other.leadership * (1 - distance / LEADERSHIP_RADIUS); | |
| leadership.add(other.velocity.clone().multiplyScalar(influence)); | |
| leaderInfluence += influence; | |
| } | |
| neighborCount++; | |
| } | |
| if (distance < SEPARATION_RADIUS && distance > 0) { | |
| const diff = this.mesh.position.clone().sub(other.mesh.position); | |
| diff.normalize().divideScalar(distance); | |
| separation.add(diff); | |
| } | |
| } | |
| }); | |
| // Finalize flocking forces | |
| if (neighborCount > 0) { | |
| cohesion.divideScalar(neighborCount).sub(this.mesh.position).normalize(); | |
| alignment.divideScalar(neighborCount).normalize(); | |
| this.cooperationScore += neighborCount * 0.1; | |
| } | |
| if (leaderInfluence > 0) { | |
| leadership.normalize(); | |
| } | |
| // Update social sensors | |
| this.socialSensors[0] = Math.min(neighborCount / 10, 1); // Neighbor density | |
| this.socialSensors[1] = separation.length(); // Separation strength | |
| this.socialSensors[2] = alignment.length(); // Alignment strength | |
| this.socialSensors[3] = cohesion.length(); // Cohesion strength | |
| this.socialSensors[4] = leadership.length(); // Leadership influence | |
| this.socialSensors[5] = this.leadership; // Own leadership | |
| this.socialSensors[6] = this.cooperation; // Cooperation tendency | |
| this.socialSensors[7] = this.role === 'leader' ? 1 : 0; // Role indicator | |
| // Store forces for neural network | |
| this.flockingForces = { separation, alignment, cohesion, leadership }; | |
| // Update role based on behavior | |
| this.updateRole(); | |
| } | |
| updateRole() { | |
| const neighborCount = this.neighbors.length; | |
| if (this.leadership > 0.7 && neighborCount > 2) { | |
| this.role = 'leader'; | |
| this.leadershipScore += 1; | |
| } else if (this.exploration > 0.8 && neighborCount < 2) { | |
| this.role = 'explorer'; | |
| this.explorationBonus += this.velocity.length() * 0.1; | |
| } else if (neighborCount > 0) { | |
| this.role = 'follower'; | |
| } else { | |
| this.role = 'scout'; | |
| } | |
| // Update visual indicator | |
| const colors = { | |
| leader: 0xff00ff, | |
| explorer: 0x00ffff, | |
| follower: 0x88ff88, | |
| scout: 0xffff00 | |
| }; | |
| this.roleIndicator.material.color.setHex(colors[this.role]); | |
| } | |
| getEnhancedInputs() { | |
| // Comprehensive input vector | |
| return [ | |
| ...this.sensors, // 12 obstacle sensors | |
| ...this.environmentSensors, // 4 environment sensors | |
| ...this.socialSensors, // 8 social sensors | |
| ]; | |
| } | |
| makeDecision(inputs, outputs) { | |
| // Enhanced decision making with prediction | |
| const decision = { | |
| timestamp: Date.now(), | |
| inputs: [...inputs], | |
| outputs: [...outputs], | |
| prediction: this.makePrediction(inputs), | |
| confidence: this.calculateConfidence(outputs) | |
| }; | |
| this.decisions.push(decision); | |
| if (this.decisions.length > 20) { | |
| this.decisions.shift(); | |
| } | |
| // Update decision quality based on outcomes | |
| this.updateDecisionQuality(); | |
| return outputs; | |
| } | |
| makePrediction(inputs) { | |
| // Simple prediction: where will I be in 5 steps? | |
| const prediction = this.mesh.position.clone().add( | |
| this.velocity.clone().multiplyScalar(5) | |
| ); | |
| this.predictions.push({ | |
| timestamp: Date.now(), | |
| predicted: prediction, | |
| actual: null // Will be filled later | |
| }); | |
| return prediction; | |
| } | |
| calculateConfidence(outputs) { | |
| // Confidence based on output certainty | |
| const variance = outputs.reduce((sum, val) => sum + Math.pow(val - 0.5, 2), 0); | |
| return Math.min(variance * 2, 1); | |
| } | |
| updateDecisionQuality() { | |
| // Evaluate prediction accuracy | |
| let accuracy = 0; | |
| let validPredictions = 0; | |
| this.predictions.forEach(pred => { | |
| if (pred.actual) { | |
| const error = pred.predicted.distanceTo(pred.actual); | |
| accuracy += Math.max(0, 1 - error / 50); // Normalize error | |
| validPredictions++; | |
| } | |
| }); | |
| if (validPredictions > 0) { | |
| this.predictiveAccuracy = accuracy / validPredictions; | |
| this.decisionQuality = this.predictiveAccuracy * 100; | |
| } | |
| } | |
| detectRoadPosition() { | |
| const pos = this.mesh.position; | |
| const roadWidth = 12; | |
| const roadSpacing = 150; | |
| const nearestHorizontalRoad = Math.round(pos.z / roadSpacing) * roadSpacing; | |
| const distToHorizontalRoad = Math.abs(pos.z - nearestHorizontalRoad); | |
| const onHorizontalRoad = distToHorizontalRoad <= roadWidth / 2; | |
| const nearestVerticalRoad = Math.round(pos.x / roadSpacing) * roadSpacing; | |
| const distToVerticalRoad = Math.abs(pos.x - nearestVerticalRoad); | |
| const onVerticalRoad = distToVerticalRoad <= roadWidth / 2; | |
| if (onHorizontalRoad || onVerticalRoad) { | |
| return Math.max( | |
| onHorizontalRoad ? 1 - (distToHorizontalRoad / (roadWidth / 2)) : 0, | |
| onVerticalRoad ? 1 - (distToVerticalRoad / (roadWidth / 2)) : 0 | |
| ); | |
| } | |
| return 0; | |
| } | |
| getTargetDirection() { | |
| // Find nearest unexplored area or target | |
| if (world.targets.length > 0) { | |
| const nearest = world.targets.reduce((closest, target) => { | |
| const dist = this.mesh.position.distanceTo(target.position); | |
| return dist < closest.distance ? { target, distance: dist } : closest; | |
| }, { distance: Infinity }); | |
| if (nearest.distance < 100) { | |
| const direction = nearest.target.position.clone() | |
| .sub(this.mesh.position).normalize(); | |
| return (direction.dot(this.velocity.clone().normalize()) + 1) / 2; | |
| } | |
| } | |
| return 0.5; | |
| } | |
| getExplorationPotential() { | |
| // Calculate exploration potential based on visited areas | |
| const currentArea = `${Math.floor(this.mesh.position.x / 50)},${Math.floor(this.mesh.position.z / 50)}`; | |
| return this.visitedAreas.has(currentArea) ? 0.2 : 0.8; | |
| } | |
| update(deltaTime) { | |
| if (this.crashed) return; | |
| this.timeAlive -= deltaTime; | |
| if (this.timeAlive <= 0) { | |
| this.crashed = true; | |
| return; | |
| } | |
| // Update all sensors and behaviors | |
| this.updateEnhancedSensors(); | |
| this.updateAdvancedFlocking(); | |
| this.updateVisuals(); | |
| // Get comprehensive neural network inputs | |
| const inputs = this.getEnhancedInputs(); | |
| // Get brain decision | |
| const outputs = this.brain.activate(inputs); | |
| // Process decision with prediction | |
| const processedOutputs = this.makeDecision(inputs, outputs); | |
| // Apply enhanced movement | |
| this.applyEnhancedMovement(processedOutputs, deltaTime); | |
| // Update fitness with advanced metrics | |
| this.updateAdvancedFitness(deltaTime); | |
| // Track exploration | |
| this.trackExploration(); | |
| this.lastPosition.copy(this.mesh.position); | |
| this.checkCollisions(); | |
| this.keepInBounds(); | |
| } | |
| applyEnhancedMovement(outputs, deltaTime) { | |
| // Enhanced output interpretation | |
| const [ | |
| forwardForce, turnLeft, turnRight, brake, | |
| emergencyStop, boost, preciseTurn, formation | |
| ] = outputs; | |
| // Turning with precision control | |
| const baseTurn = (turnRight - turnLeft) * this.turnSpeed; | |
| const precisionTurn = (preciseTurn - 0.5) * this.turnSpeed * 0.5; | |
| const totalTurn = (baseTurn + precisionTurn) * deltaTime; | |
| this.mesh.rotation.y += totalTurn; | |
| // Advanced acceleration | |
| const forward = new THREE.Vector3(0, 0, 1); | |
| forward.applyQuaternion(this.mesh.quaternion); | |
| let acceleration = this.accelerationForce; | |
| // Boost behavior | |
| if (boost > 0.7) { | |
| acceleration *= 1.5; | |
| this.maxSpeed = 30; | |
| } else { | |
| this.maxSpeed = 25; | |
| } | |
| // Emergency stop | |
| if (emergencyStop > 0.8) { | |
| this.velocity.multiplyScalar(0.8); | |
| } else if (forwardForce > 0.1) { | |
| this.acceleration.add(forward.multiplyScalar(acceleration * forwardForce * deltaTime)); | |
| } | |
| // Braking | |
| if (brake > 0.5) { | |
| this.velocity.multiplyScalar(1 - brake * deltaTime * 2); | |
| } | |
| // Apply flocking forces | |
| if (this.flockingForces) { | |
| const flockingStrength = formation * 0.5; | |
| this.acceleration.add(this.flockingForces.separation.multiplyScalar(0.3)); | |
| this.acceleration.add(this.flockingForces.alignment.multiplyScalar(0.2 * flockingStrength)); | |
| this.acceleration.add(this.flockingForces.cohesion.multiplyScalar(0.2 * flockingStrength)); | |
| this.acceleration.add(this.flockingForces.leadership.multiplyScalar(0.4 * (1 - this.leadership))); | |
| } | |
| // Apply acceleration and velocity | |
| this.velocity.add(this.acceleration); | |
| this.acceleration.multiplyScalar(0.1); // Decay acceleration | |
| // Speed limits | |
| const currentSpeed = this.velocity.length(); | |
| if (currentSpeed > this.maxSpeed) { | |
| this.velocity.normalize().multiplyScalar(this.maxSpeed); | |
| } else if (currentSpeed < this.minSpeed) { | |
| this.velocity.normalize().multiplyScalar(this.minSpeed); | |
| } | |
| // Apply movement | |
| this.mesh.position.add(this.velocity.clone().multiplyScalar(deltaTime)); | |
| // Wheel rotation animation | |
| this.wheels.forEach(wheel => { | |
| wheel.rotation.x += this.velocity.length() * deltaTime * 0.1; | |
| }); | |
| } | |
| updateAdvancedFitness(deltaTime) { | |
| const distance = this.mesh.position.distanceTo(this.lastPosition); | |
| this.distanceTraveled += distance; | |
| // Multi-objective fitness function | |
| const roadBonus = this.detectRoadPosition() * distance * 3; | |
| const groupBonus = Math.min(this.neighbors.length, 8) * distance * 2; | |
| const roleBonus = this.getRoleBonus() * deltaTime; | |
| const innovationBonus = this.innovationScore * 0.5; | |
| const efficiencyBonus = this.getEfficiencyBonus(); | |
| this.rawFitness = this.distanceTraveled + | |
| roadBonus + | |
| groupBonus + | |
| roleBonus + | |
| this.explorationBonus + | |
| this.cooperationScore + | |
| innovationBonus + | |
| efficiencyBonus; | |
| // Update predictions | |
| this.predictions.forEach(pred => { | |
| if (!pred.actual && Date.now() - pred.timestamp > 5000) { | |
| pred.actual = this.mesh.position.clone(); | |
| } | |
| }); | |
| } | |
| getRoleBonus() { | |
| switch (this.role) { | |
| case 'leader': return this.leadershipScore * 0.5; | |
| case 'explorer': return this.explorationBonus * 0.3; | |
| case 'follower': return this.cooperationScore * 0.2; | |
| case 'scout': return this.distanceTraveled * 0.1; | |
| default: return 0; | |
| } | |
| } | |
| getEfficiencyBonus() { | |
| // Reward efficient decision making | |
| return this.decisionQuality * 0.1 + this.predictiveAccuracy * 50; | |
| } | |
| trackExploration() { | |
| const area = `${Math.floor(this.mesh.position.x / 25)},${Math.floor(this.mesh.position.z / 25)}`; | |
| if (!this.visitedAreas.has(area)) { | |
| this.visitedAreas.add(area); | |
| this.explorationBonus += 10; | |
| } | |
| } | |
| updateVisuals() { | |
| // Update car color based on role and performance | |
| this.updateCarColor(); | |
| this.updateFlockVisualization(); | |
| // Brain indicator pulsing based on activity | |
| const brainActivity = this.brain.memory.reduce((sum, val) => sum + Math.abs(val), 0); | |
| this.brainIndicator.material.opacity = 0.5 + (brainActivity * 0.1); | |
| } | |
| updateCarColor() { | |
| const hue = this.speciesId * 0.2; | |
| let saturation = 0.8; | |
| let lightness = 0.6; | |
| // Role-based color modifications | |
| switch (this.role) { | |
| case 'leader': | |
| saturation = 1.0; | |
| lightness = 0.7; | |
| break; | |
| case 'explorer': | |
| saturation = 0.9; | |
| lightness = 0.8; | |
| break; | |
| case 'follower': | |
| saturation = 0.7; | |
| lightness = 0.5; | |
| break; | |
| } | |
| // Performance-based brightness | |
| const performanceBonus = Math.min(this.rawFitness / 1000, 0.3); | |
| lightness += performanceBonus; | |
| this.bodyMaterial.color.setHSL(hue, saturation, lightness); | |
| } | |
| updateFlockVisualization() { | |
| if (!showFlockLines) return; | |
| const nearestNeighbors = this.neighbors | |
| .sort((a, b) => { | |
| const distA = this.mesh.position.distanceTo(a.mesh.position); | |
| const distB = this.mesh.position.distanceTo(b.mesh.position); | |
| return distA - distB; | |
| }) | |
| .slice(0, 8); | |
| for (let i = 0; i < this.flockLines.length; i++) { | |
| if (i < nearestNeighbors.length) { | |
| const start = this.mesh.position.clone(); | |
| start.y = 2; | |
| const end = nearestNeighbors[i].mesh.position.clone(); | |
| end.y = 2; | |
| this.flockLines[i].geometry.setFromPoints([start, end]); | |
| this.flockLines[i].visible = true; | |
| // Color based on relationship | |
| if (nearestNeighbors[i].role === 'leader') { | |
| this.flockLines[i].material.color.setHex(0xff00ff); | |
| } else { | |
| this.flockLines[i].material.color.setHex(0x00ff00); | |
| } | |
| } else { | |
| this.flockLines[i].visible = false; | |
| } | |
| } | |
| } | |
| getObstacles() { | |
| let obstacles = []; | |
| population.forEach(car => { | |
| if (car !== this && !car.crashed) { | |
| obstacles.push(car.mesh); | |
| } | |
| }); | |
| world.buildings.forEach(building => { | |
| obstacles.push(building.mesh); | |
| }); | |
| world.dynamicObstacles.forEach(obstacle => { | |
| obstacles.push(obstacle.mesh); | |
| }); | |
| return obstacles; | |
| } | |
| checkCollisions() { | |
| const carBox = new THREE.Box3().setFromObject(this.mesh); | |
| // Enhanced collision detection | |
| population.forEach(otherCar => { | |
| if (otherCar !== this && !otherCar.crashed) { | |
| const otherBox = new THREE.Box3().setFromObject(otherCar.mesh); | |
| if (carBox.intersectsBox(otherBox)) { | |
| // Soft collision - reduce speed instead of crash | |
| const collisionForce = new THREE.Vector3() | |
| .subVectors(this.mesh.position, otherCar.mesh.position) | |
| .normalize() | |
| .multiplyScalar(5); | |
| this.velocity.add(collisionForce); | |
| otherCar.velocity.sub(collisionForce); | |
| // Small fitness penalty | |
| this.rawFitness -= 10; | |
| otherCar.rawFitness -= 10; | |
| } | |
| } | |
| }); | |
| // Building collisions | |
| world.buildings.forEach(building => { | |
| const buildingBox = new THREE.Box3().setFromObject(building.mesh); | |
| if (carBox.intersectsBox(buildingBox)) { | |
| this.crashed = true; | |
| crashCount++; | |
| } | |
| }); | |
| } | |
| keepInBounds() { | |
| const bounds = 400; | |
| if (Math.abs(this.mesh.position.x) > bounds || | |
| Math.abs(this.mesh.position.z) > bounds) { | |
| if (Math.abs(this.mesh.position.x) > bounds) { | |
| this.mesh.position.x = Math.sign(this.mesh.position.x) * bounds; | |
| this.velocity.x *= -0.7; | |
| } | |
| if (Math.abs(this.mesh.position.z) > bounds) { | |
| this.mesh.position.z = Math.sign(this.mesh.position.z) * bounds; | |
| this.velocity.z *= -0.7; | |
| } | |
| this.rawFitness -= 5; // Boundary penalty | |
| } | |
| } | |
| destroy() { | |
| this.flockLines.forEach(line => { | |
| if (line.parent) scene.remove(line); | |
| }); | |
| if (this.mesh.parent) { | |
| scene.remove(this.mesh); | |
| } | |
| } | |
| } | |
| // Enhanced speciation system | |
| function calculateCompatibility(brain1, brain2) { | |
| let weightDiff = 0; | |
| let totalWeights = 0; | |
| // Compare all weight matrices | |
| for (let layer = 0; layer < brain1.weights.length; layer++) { | |
| for (let i = 0; i < brain1.weights[layer].length; i++) { | |
| for (let j = 0; j < brain1.weights[layer][i].length; j++) { | |
| weightDiff += Math.abs(brain1.weights[layer][i][j] - brain2.weights[layer][i][j]); | |
| totalWeights++; | |
| } | |
| } | |
| } | |
| // Compare personality traits | |
| let traitDiff = 0; | |
| Object.keys(brain1.personalityTraits).forEach(trait => { | |
| traitDiff += Math.abs(brain1.personalityTraits[trait] - brain2.personalityTraits[trait]); | |
| }); | |
| return (weightDiff / totalWeights) + (traitDiff / 5); | |
| } | |
| function speciate() { | |
| species = []; | |
| population.forEach(individual => { | |
| let foundSpecies = false; | |
| for (let s of species) { | |
| if (s.members.length > 0) { | |
| const representative = s.members[0]; | |
| const compatibility = calculateCompatibility(individual.brain, representative.brain); | |
| if (compatibility < SPECIES_THRESHOLD) { | |
| s.members.push(individual); | |
| individual.speciesId = s.id; | |
| foundSpecies = true; | |
| break; | |
| } | |
| } | |
| } | |
| if (!foundSpecies) { | |
| const newSpecies = { | |
| id: species.length, | |
| members: [individual], | |
| avgFitness: 0, | |
| staleness: 0, | |
| bestFitness: 0 | |
| }; | |
| species.push(newSpecies); | |
| individual.speciesId = newSpecies.id; | |
| } | |
| }); | |
| // Calculate species fitness | |
| species.forEach(s => { | |
| if (s.members.length > 0) { | |
| s.avgFitness = s.members.reduce((sum, ind) => sum + ind.rawFitness, 0) / s.members.length; | |
| s.bestFitness = Math.max(...s.members.map(ind => ind.rawFitness)); | |
| // Adjust individual fitness by species size (fitness sharing) | |
| s.members.forEach(ind => { | |
| ind.adjustedFitness = ind.rawFitness / s.members.length; | |
| }); | |
| } | |
| }); | |
| // Remove empty species | |
| species = species.filter(s => s.members.length > 0); | |
| } | |
| function init() { | |
| // Enhanced scene setup | |
| scene = new THREE.Scene(); | |
| scene.background = new THREE.Color(0x87CEEB); | |
| scene.fog = new THREE.Fog(0x87CEEB, 400, 1200); | |
| camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2000); | |
| camera.position.set(0, 120, 120); | |
| camera.lookAt(0, 0, 0); | |
| renderer = new THREE.WebGLRenderer({ antialias: true }); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| renderer.shadowMap.enabled = true; | |
| renderer.shadowMap.type = THREE.PCFSoftShadowMap; | |
| renderer.setClearColor(0x001122); | |
| document.body.appendChild(renderer.domElement); | |
| // Enhanced lighting | |
| const ambientLight = new THREE.AmbientLight(0x404040, 0.6); | |
| scene.add(ambientLight); | |
| const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); | |
| directionalLight.position.set(100, 100, 50); | |
| directionalLight.castShadow = true; | |
| directionalLight.shadow.mapSize.width = 2048; | |
| directionalLight.shadow.mapSize.height = 2048; | |
| scene.add(directionalLight); | |
| // Create enhanced world | |
| createEnhancedWorld(); | |
| createInitialPopulation(); | |
| clock = new THREE.Clock(); | |
| // Event listeners | |
| window.addEventListener('resize', onWindowResize); | |
| setupEventListeners(); | |
| animate(); | |
| } | |
| function createEnhancedWorld() { | |
| // Enhanced ground with texture variation | |
| const groundGeometry = new THREE.PlaneGeometry(1000, 1000); | |
| const groundMaterial = new THREE.MeshLambertMaterial({ | |
| color: 0x228B22, | |
| transparent: true, | |
| opacity: 0.9 | |
| }); | |
| const ground = new THREE.Mesh(groundGeometry, groundMaterial); | |
| ground.rotation.x = -Math.PI / 2; | |
| ground.receiveShadow = true; | |
| scene.add(ground); | |
| createRoadNetwork(); | |
| createObstacles(); | |
| createDynamicEnvironment(); | |
| } | |
| function createRoadNetwork() { | |
| const roadMaterial = new THREE.MeshLambertMaterial({ color: 0x444444 }); | |
| for (let i = -300; i <= 300; i += 150) { | |
| // Horizontal roads | |
| const hRoadGeometry = new THREE.PlaneGeometry(600, 12); | |
| const hRoad = new THREE.Mesh(hRoadGeometry, roadMaterial); | |
| hRoad.rotation.x = -Math.PI / 2; | |
| hRoad.position.set(0, 0.1, i); | |
| scene.add(hRoad); | |
| // Vertical roads | |
| const vRoadGeometry = new THREE.PlaneGeometry(12, 600); | |
| const vRoad = new THREE.Mesh(vRoadGeometry, roadMaterial); | |
| vRoad.rotation.x = -Math.PI / 2; | |
| vRoad.position.set(i, 0.1, 0); | |
| scene.add(vRoad); | |
| } | |
| } | |
| function createObstacles() { | |
| world.buildings = []; | |
| const buildingMaterial = new THREE.MeshLambertMaterial({ color: 0x666666 }); | |
| for (let i = 0; i < 20; i++) { | |
| const x = (Math.random() - 0.5) * 700; | |
| const z = (Math.random() - 0.5) * 700; | |
| const width = 12 + Math.random() * 25; | |
| const height = 8 + Math.random() * 35; | |
| const depth = 12 + Math.random() * 25; | |
| const buildingGeometry = new THREE.BoxGeometry(width, height, depth); | |
| const building = new THREE.Mesh(buildingGeometry, buildingMaterial); | |
| building.position.set(x, height / 2, z); | |
| building.castShadow = true; | |
| scene.add(building); | |
| world.buildings.push({ mesh: building }); | |
| } | |
| } | |
| function createDynamicEnvironment() { | |
| // Create exploration targets | |
| world.targets = []; | |
| for (let i = 0; i < 8; i++) { | |
| const target = { | |
| position: new THREE.Vector3( | |
| (Math.random() - 0.5) * 600, | |
| 5, | |
| (Math.random() - 0.5) * 600 | |
| ), | |
| discovered: false | |
| }; | |
| // Visual target | |
| const targetGeometry = new THREE.SphereGeometry(3, 8, 6); | |
| const targetMaterial = new THREE.MeshLambertMaterial({ | |
| color: 0x00ff00, | |
| transparent: true, | |
| opacity: 0.7 | |
| }); | |
| target.mesh = new THREE.Mesh(targetGeometry, targetMaterial); | |
| target.mesh.position.copy(target.position); | |
| scene.add(target.mesh); | |
| world.targets.push(target); | |
| } | |
| } | |
| function createInitialPopulation() { | |
| population = []; | |
| for (let i = 0; i < populationSize; i++) { | |
| const angle = (i / populationSize) * Math.PI * 2; | |
| const radius = 40 + Math.random() * 60; | |
| const x = Math.cos(angle) * radius; | |
| const z = Math.sin(angle) * radius; | |
| const car = new EnhancedAICar(x, z); | |
| population.push(car); | |
| scene.add(car.mesh); | |
| } | |
| speciate(); | |
| } | |
| function evolvePopulation() { | |
| speciate(); | |
| // Advanced evolution with speciation | |
| const totalAdjustedFitness = population.reduce((sum, ind) => sum + ind.adjustedFitness, 0); | |
| const newPopulation = []; | |
| // Determine offspring allocation per species | |
| species.forEach(s => { | |
| if (s.members.length === 0) return; | |
| const speciesFitness = s.members.reduce((sum, ind) => sum + ind.adjustedFitness, 0); | |
| const offspringCount = Math.floor((speciesFitness / totalAdjustedFitness) * populationSize); | |
| // Sort species members by fitness | |
| s.members.sort((a, b) => b.adjustedFitness - a.adjustedFitness); | |
| // Elite selection | |
| const eliteCount = Math.max(1, Math.floor(offspringCount * 0.2)); | |
| for (let i = 0; i < eliteCount && i < s.members.length; i++) { | |
| const elite = s.members[i]; | |
| const angle = Math.random() * Math.PI * 2; | |
| const radius = 40 + Math.random() * 60; | |
| const newCar = new EnhancedAICar( | |
| Math.cos(angle) * radius, | |
| Math.sin(angle) * radius | |
| ); | |
| newCar.brain = elite.brain.copy(); | |
| newCar.speciesId = s.id; | |
| newPopulation.push(newCar); | |
| } | |
| // Crossover and mutation | |
| while (newPopulation.filter(car => car.speciesId === s.id).length < offspringCount) { | |
| const parent1 = tournamentSelection(s.members); | |
| const parent2 = tournamentSelection(s.members); | |
| const angle = Math.random() * Math.PI * 2; | |
| const radius = 40 + Math.random() * 60; | |
| const child = new EnhancedAICar( | |
| Math.cos(angle) * radius, | |
| Math.sin(angle) * radius | |
| ); | |
| if (Math.random() < 0.7) { | |
| child.brain = parent1.brain.crossover(parent2.brain); | |
| } else { | |
| child.brain = parent1.brain.copy(); | |
| } | |
| // Adaptive mutation | |
| const mutationRate = 0.05 + (s.staleness * 0.01); | |
| child.brain.mutate(mutationRate, Math.random() < 0.1); | |
| child.speciesId = s.id; | |
| newPopulation.push(child); | |
| } | |
| }); | |
| // Fill any remaining slots | |
| while (newPopulation.length < populationSize) { | |
| const randomSpecies = species[Math.floor(Math.random() * species.length)]; | |
| if (randomSpecies.members.length > 0) { | |
| const parent = randomSpecies.members[0]; | |
| const angle = Math.random() * Math.PI * 2; | |
| const radius = 40 + Math.random() * 60; | |
| const child = new EnhancedAICar( | |
| Math.cos(angle) * radius, | |
| Math.sin(angle) * radius | |
| ); | |
| child.brain = parent.brain.copy(); | |
| child.brain.mutate(0.3, true); // High mutation for diversity | |
| child.speciesId = parent.speciesId; | |
| newPopulation.push(child); | |
| } | |
| } | |
| // Clean up old population | |
| population.forEach(car => car.destroy()); | |
| // Replace population | |
| population = newPopulation; | |
| population.forEach(car => scene.add(car.mesh)); | |
| // Update epoch | |
| epoch++; | |
| timeLeft = epochTime; | |
| bestFitness = Math.max(bestFitness, ...population.map(car => car.rawFitness)); | |
| crashCount = 0; | |
| console.log(`Epoch ${epoch}: ${species.length} species, best fitness: ${bestFitness.toFixed(1)}`); | |
| } | |
| function tournamentSelection(individuals, tournamentSize = 3) { | |
| let best = null; | |
| let bestFitness = -1; | |
| for (let i = 0; i < tournamentSize; i++) { | |
| const candidate = individuals[Math.floor(Math.random() * individuals.length)]; | |
| if (candidate.adjustedFitness > bestFitness) { | |
| best = candidate; | |
| bestFitness = candidate.adjustedFitness; | |
| } | |
| } | |
| return best; | |
| } | |
| function animate() { | |
| requestAnimationFrame(animate); | |
| if (!paused) { | |
| const deltaTime = Math.min(clock.getDelta() * speedMultiplier, 0.1); | |
| // Update timer | |
| timeLeft -= deltaTime; | |
| if (timeLeft <= 0) { | |
| evolvePopulation(); | |
| } | |
| // Update population | |
| updatePopulation(deltaTime); | |
| updateCamera(); | |
| updateUI(); | |
| updateDynamicEnvironment(deltaTime); | |
| } | |
| renderer.render(scene, camera); | |
| } | |
| function updatePopulation(deltaTime) { | |
| let stats = { | |
| alive: 0, | |
| leaders: 0, | |
| followers: 0, | |
| explorers: 0, | |
| scouts: 0, | |
| totalVelocity: 0, | |
| totalCooperation: 0, | |
| totalExploration: 0, | |
| totalDecisionQuality: 0, | |
| totalNeuralComplexity: 0, | |
| maxGroupSize: 0 | |
| }; | |
| population.forEach(car => { | |
| car.update(deltaTime); | |
| if (!car.crashed) { | |
| stats.alive++; | |
| stats.totalVelocity += car.velocity.length(); | |
| stats.totalDecisionQuality += car.decisionQuality; | |
| stats.totalNeuralComplexity += car.brain.getComplexity(); | |
| stats.maxGroupSize = Math.max(stats.maxGroupSize, car.neighbors.length + 1); | |
| switch (car.role) { | |
| case 'leader': stats.leaders++; break; | |
| case 'follower': stats.followers++; break; | |
| case 'explorer': stats.explorers++; break; | |
| case 'scout': stats.scouts++; break; | |
| } | |
| stats.totalCooperation += car.cooperationScore; | |
| stats.totalExploration += car.explorationBonus; | |
| } | |
| }); | |
| // Store stats for UI | |
| window.populationStats = stats; | |
| } | |
| function updateCamera() { | |
| if (cameraMode === 'follow') { | |
| // Follow the best performing car or largest flock | |
| let target = population.reduce((best, car) => { | |
| if (car.crashed) return best; | |
| return !best || car.rawFitness > best.rawFitness ? car : best; | |
| }, null); | |
| if (target) { | |
| const targetPos = target.mesh.position.clone(); | |
| targetPos.y += 50; | |
| targetPos.add(target.velocity.clone().normalize().multiplyScalar(30)); | |
| camera.position.lerp(targetPos, 0.02); | |
| camera.lookAt(target.mesh.position); | |
| } | |
| } else { | |
| camera.position.lerp(new THREE.Vector3(0, 200, 200), 0.02); | |
| camera.lookAt(0, 0, 0); | |
| } | |
| } | |
| function updateUI() { | |
| const stats = window.populationStats || {}; | |
| // Main UI | |
| document.getElementById('epoch').textContent = epoch; | |
| document.getElementById('epochTime').textContent = Math.ceil(timeLeft); | |
| document.getElementById('population').textContent = stats.alive || 0; | |
| document.getElementById('speciesCount').textContent = species.length; | |
| document.getElementById('bestFitness').textContent = Math.round(bestFitness); | |
| document.getElementById('innovationCount').textContent = innovationCounter; | |
| // Progress bar | |
| const progress = ((epochTime - timeLeft) / epochTime) * 100; | |
| document.getElementById('timeProgress').style.width = `${progress}%`; | |
| // AI Stats | |
| if (stats.alive > 0) { | |
| document.getElementById('avgIQ').textContent = Math.round(stats.totalDecisionQuality / stats.alive); | |
| document.getElementById('neuralComplexity').textContent = Math.round(stats.totalNeuralComplexity / stats.alive); | |
| document.getElementById('decisionQuality').textContent = Math.round(stats.totalDecisionQuality / stats.alive); | |
| document.getElementById('avgCoordination').textContent = Math.round((stats.totalCooperation / stats.alive) * 10); | |
| } | |
| // Flocking stats | |
| document.getElementById('leaderCount').textContent = stats.leaders || 0; | |
| document.getElementById('followerCount').textContent = stats.followers || 0; | |
| document.getElementById('explorerCount').textContent = stats.explorers || 0; | |
| document.getElementById('soloCount').textContent = stats.scouts || 0; | |
| document.getElementById('largestFlock').textContent = stats.maxGroupSize || 0; | |
| // Generation stats | |
| const totalDistance = population.reduce((sum, car) => sum + car.distanceTraveled, 0); | |
| const totalExploration = population.reduce((sum, car) => sum + car.explorationBonus, 0); | |
| const totalCooperation = population.reduce((sum, car) => sum + car.cooperationScore, 0); | |
| document.getElementById('totalDistance').textContent = Math.round(totalDistance); | |
| document.getElementById('explorationBonus').textContent = Math.round(totalExploration); | |
| document.getElementById('cooperationScore').textContent = Math.round(totalCooperation); | |
| document.getElementById('crashCount').textContent = crashCount; | |
| // Top performers | |
| updateTopPerformers(); | |
| } | |
| function updateTopPerformers() { | |
| const sorted = [...population] | |
| .filter(car => !car.crashed) | |
| .sort((a, b) => b.rawFitness - a.rawFitness) | |
| .slice(0, 5); | |
| const topPerformersDiv = document.getElementById('topPerformers'); | |
| topPerformersDiv.innerHTML = ''; | |
| sorted.forEach((car, i) => { | |
| const div = document.createElement('div'); | |
| const roleIcon = { | |
| leader: 'π', | |
| explorer: 'π', | |
| follower: 'π€', | |
| scout: 'ποΈ' | |
| }[car.role] || 'π'; | |
| div.innerHTML = `${i + 1}. ${roleIcon} S${car.speciesId} | IQ:${Math.round(car.decisionQuality)} | F:${Math.round(car.rawFitness)}`; | |
| div.className = `species-${car.speciesId % 5}`; | |
| topPerformersDiv.appendChild(div); | |
| }); | |
| } | |
| function updateDynamicEnvironment(deltaTime) { | |
| // Update targets | |
| world.targets.forEach(target => { | |
| // Pulsing animation | |
| target.mesh.scale.setScalar(1 + Math.sin(Date.now() * 0.005) * 0.1); | |
| // Check if discovered | |
| population.forEach(car => { | |
| if (!car.crashed && target.mesh.position.distanceTo(car.mesh.position) < 10) { | |
| if (!target.discovered) { | |
| target.discovered = true; | |
| car.explorationBonus += 50; | |
| target.mesh.material.color.setHex(0xffff00); | |
| } | |
| } | |
| }); | |
| }); | |
| } | |
| function setupEventListeners() { | |
| document.getElementById('pauseBtn').addEventListener('click', togglePause); | |
| document.getElementById('resetBtn').addEventListener('click', resetSimulation); | |
| document.getElementById('speedBtn').addEventListener('click', toggleSpeed); | |
| document.getElementById('viewBtn').addEventListener('click', toggleView); | |
| document.getElementById('flockBtn').addEventListener('click', toggleFlockLines); | |
| document.getElementById('adaptiveBtn').addEventListener('click', toggleAdaptive); | |
| document.getElementById('challengeBtn').addEventListener('click', toggleChallenge); | |
| } | |
| function togglePause() { | |
| paused = !paused; | |
| document.getElementById('pauseBtn').textContent = paused ? 'Resume' : 'Pause'; | |
| if (!paused) clock.start(); | |
| } | |
| function resetSimulation() { | |
| epoch = 1; | |
| timeLeft = epochTime; | |
| bestFitness = 0; | |
| crashCount = 0; | |
| innovationCounter = 0; | |
| population.forEach(car => car.destroy()); | |
| createInitialPopulation(); | |
| } | |
| function toggleSpeed() { | |
| speedMultiplier = speedMultiplier === 1 ? 2 : speedMultiplier === 2 ? 5 : 1; | |
| document.getElementById('speedBtn').textContent = `Speed: ${speedMultiplier}x`; | |
| } | |
| function toggleView() { | |
| cameraMode = cameraMode === 'follow' ? 'overview' : 'follow'; | |
| document.getElementById('viewBtn').textContent = `View: ${cameraMode === 'follow' ? 'Follow' : 'Overview'}`; | |
| } | |
| function toggleFlockLines() { | |
| showFlockLines = !showFlockLines; | |
| document.getElementById('flockBtn').textContent = `Flocks: ${showFlockLines ? 'ON' : 'OFF'}`; | |
| } | |
| function toggleAdaptive() { | |
| adaptiveEnvironment = !adaptiveEnvironment; | |
| document.getElementById('adaptiveBtn').textContent = `Adaptive: ${adaptiveEnvironment ? 'ON' : 'OFF'}`; | |
| } | |
| function toggleChallenge() { | |
| const levels = ['normal', 'hard', 'extreme']; | |
| const currentIndex = levels.indexOf(challengeLevel); | |
| challengeLevel = levels[(currentIndex + 1) % levels.length]; | |
| document.getElementById('challengeBtn').textContent = `Challenge: ${challengeLevel}`; | |
| } | |
| function onWindowResize() { | |
| camera.aspect = window.innerWidth / window.innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| } | |
| init(); | |
| </script> | |
| </body> | |
| </html> |