GlasgowDriving / index.html
awacke1's picture
Update index.html
c136415 verified
<!DOCTYPE html>
<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>