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> |