index.html•27.8 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Swarm Intelligence - AgentDB WASM</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
min-height: 100vh;
padding: 2rem;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
header {
text-align: center;
color: white;
margin-bottom: 2rem;
}
h1 {
font-size: 2rem;
margin-bottom: 0.5rem;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.subtitle {
opacity: 0.9;
font-size: 1rem;
}
.main-grid {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 1.5rem;
margin-bottom: 1.5rem;
}
.card {
background: white;
border-radius: 12px;
padding: 1.5rem;
box-shadow: 0 8px 24px rgba(0,0,0,0.2);
}
.card h2 {
color: #333;
margin-bottom: 1rem;
font-size: 1.3rem;
}
#canvas {
width: 100%;
height: 500px;
border: 2px solid #e0e0e0;
border-radius: 8px;
background: #0a0a0a;
cursor: crosshair;
}
.controls {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
margin-bottom: 1rem;
}
.control-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
label {
font-size: 0.9rem;
color: #555;
font-weight: 500;
}
input[type="range"] {
width: 100%;
}
input[type="number"] {
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 0.9rem;
}
.btn {
padding: 0.75rem 1.5rem;
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
color: white;
border: none;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
transition: opacity 0.3s;
}
.btn:hover {
opacity: 0.9;
}
.btn.danger {
background: linear-gradient(135deg, #c62828 0%, #b71c1c 100%);
}
.btn.success {
background: linear-gradient(135deg, #2e7d32 0%, #1b5e20 100%);
}
.stat-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
margin-top: 1rem;
}
.stat-item {
background: #f5f5f5;
padding: 1rem;
border-radius: 8px;
text-align: center;
}
.stat-label {
font-size: 0.85rem;
color: #666;
margin-bottom: 0.5rem;
}
.stat-value {
font-size: 1.5rem;
font-weight: bold;
color: #1e3c72;
}
.behavior-select {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}
.behavior-btn {
flex: 1;
padding: 0.5rem;
background: white;
border: 2px solid #ddd;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s;
}
.behavior-btn.active {
background: #1e3c72;
color: white;
border-color: #1e3c72;
}
#log {
max-height: 200px;
overflow-y: auto;
background: #f9f9f9;
padding: 1rem;
border-radius: 8px;
font-family: 'Courier New', monospace;
font-size: 0.85rem;
line-height: 1.6;
}
.log-entry {
margin-bottom: 0.5rem;
color: #555;
}
.log-entry.success {
color: #2e7d32;
}
.log-entry.info {
color: #1976d2;
}
.description {
background: #e3f2fd;
padding: 1rem;
border-radius: 8px;
margin-bottom: 1rem;
line-height: 1.6;
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>🐝 Swarm Intelligence with Emergent Behavior</h1>
<p class="subtitle">Particle Swarm Optimization + Stigmergy + Vector Memory</p>
</header>
<div class="main-grid">
<div class="card">
<h2>Swarm Visualization</h2>
<canvas id="canvas"></canvas>
<div class="controls">
<button class="btn" onclick="addTarget()">Add Target 🎯</button>
<button class="btn" onclick="addObstacle()">Add Obstacle 🚧</button>
<button class="btn success" onclick="startSwarm()">Start Swarm 🚀</button>
<button class="btn danger" onclick="resetSwarm()">Reset ⟲</button>
</div>
</div>
<div class="card">
<h2>Swarm Parameters</h2>
<div class="description">
Watch agents exhibit emergent collective intelligence through pheromone trails, local interactions, and vector-based memory.
</div>
<div class="control-group">
<label>Agents: <span id="agentCount">50</span></label>
<input type="range" id="agentSlider" min="10" max="200" value="50" oninput="updateAgentCount()">
</div>
<div class="control-group">
<label>Exploration Rate: <span id="exploreRate">0.3</span></label>
<input type="range" id="exploreSlider" min="0" max="1" step="0.1" value="0.3" oninput="updateExploreRate()">
</div>
<div class="control-group">
<label>Pheromone Decay: <span id="decayRate">0.95</span></label>
<input type="range" id="decaySlider" min="0.8" max="0.99" step="0.01" value="0.95" oninput="updateDecayRate()">
</div>
<div class="behavior-select">
<button class="behavior-btn active" onclick="setBehavior('foraging')">🍃 Foraging</button>
<button class="behavior-btn" onclick="setBehavior('flocking')">🐦 Flocking</button>
<button class="behavior-btn" onclick="setBehavior('exploration')">🗺️ Exploration</button>
</div>
<div class="stat-grid">
<div class="stat-item">
<div class="stat-label">Targets Found</div>
<div class="stat-value" id="targetsFound">0</div>
</div>
<div class="stat-item">
<div class="stat-label">Emergent Paths</div>
<div class="stat-value" id="pathsDiscovered">0</div>
</div>
<div class="stat-item">
<div class="stat-label">Collective IQ</div>
<div class="stat-value" id="collectiveIQ">0</div>
</div>
<div class="stat-item">
<div class="stat-label">Pheromone Trails</div>
<div class="stat-value" id="trailCount">0</div>
</div>
</div>
</div>
</div>
<div class="card">
<h2>📊 Swarm Intelligence Log</h2>
<div id="log"></div>
</div>
</div>
<script>
// Simulated AgentDB for vector storage
class SwarmVectorDB {
constructor() {
this.pheromones = [];
this.paths = [];
this.memories = [];
}
storePheromone(position, strength, type) {
const embedding = this.generatePositionEmbedding(position);
this.pheromones.push({
position,
embedding,
strength,
type,
timestamp: Date.now()
});
// Decay old pheromones
this.pheromones = this.pheromones.filter(p => p.strength > 0.01);
}
findNearbyPheromones(position, radius = 50) {
const posEmbedding = this.generatePositionEmbedding(position);
return this.pheromones
.map(p => ({
...p,
distance: Math.hypot(p.position.x - position.x, p.position.y - position.y),
similarity: this.cosineSimilarity(posEmbedding, p.embedding)
}))
.filter(p => p.distance < radius)
.sort((a, b) => b.strength - a.strength);
}
storePath(path, success) {
const embedding = this.generatePathEmbedding(path);
this.paths.push({
path,
embedding,
success,
fitness: success ? 1.0 : 0.1,
timestamp: Date.now()
});
}
getBestPaths(count = 5) {
return this.paths
.sort((a, b) => b.fitness - a.fitness)
.slice(0, count);
}
storeCollectiveMemory(observation, context) {
this.memories.push({
observation,
context,
embedding: this.generateEmbedding(JSON.stringify(observation)),
timestamp: Date.now()
});
}
recallSimilarExperiences(query, k = 5) {
const queryEmbedding = this.generateEmbedding(JSON.stringify(query));
return this.memories
.map(m => ({
...m,
similarity: this.cosineSimilarity(queryEmbedding, m.embedding)
}))
.sort((a, b) => b.similarity - a.similarity)
.slice(0, k);
}
generatePositionEmbedding(pos) {
const embedding = new Array(64).fill(0);
const scale = 0.01;
for (let i = 0; i < 64; i++) {
embedding[i] = Math.sin(pos.x * scale * i) + Math.cos(pos.y * scale * i);
}
return this.normalize(embedding);
}
generatePathEmbedding(path) {
const embedding = new Array(128).fill(0);
path.forEach((pos, idx) => {
const weight = 1 / (idx + 1);
for (let i = 0; i < 64; i++) {
embedding[i] += Math.sin(pos.x * 0.01 * i) * weight;
embedding[i + 64] += Math.cos(pos.y * 0.01 * i) * weight;
}
});
return this.normalize(embedding);
}
generateEmbedding(text) {
const embedding = new Array(256).fill(0);
for (let i = 0; i < text.length; i++) {
const char = text.charCodeAt(i);
embedding[i % 256] += Math.sin(char * (i + 1));
}
return this.normalize(embedding);
}
cosineSimilarity(a, b) {
let dot = 0, magA = 0, magB = 0;
for (let i = 0; i < a.length; i++) {
dot += a[i] * b[i];
magA += a[i] * a[i];
magB += b[i] * b[i];
}
return dot / (Math.sqrt(magA) * Math.sqrt(magB));
}
normalize(vec) {
const mag = Math.sqrt(vec.reduce((sum, val) => sum + val * val, 0));
return vec.map(v => v / (mag || 1));
}
decayPheromones(rate = 0.95) {
this.pheromones.forEach(p => {
p.strength *= rate;
});
}
}
class Agent {
constructor(x, y, id) {
this.id = id;
this.x = x;
this.y = y;
this.vx = (Math.random() - 0.5) * 2;
this.vy = (Math.random() - 0.5) * 2;
this.path = [{x, y}];
this.targetReached = false;
this.color = `hsl(${Math.random() * 360}, 70%, 60%)`;
this.energy = 100;
}
update(agents, targets, obstacles, pheromones, behavior) {
if (this.targetReached || this.energy <= 0) return;
// Behavior-specific movement
switch(behavior) {
case 'foraging':
this.foragingBehavior(targets, pheromones);
break;
case 'flocking':
this.flockingBehavior(agents);
break;
case 'exploration':
this.explorationBehavior(pheromones);
break;
}
// Avoid obstacles
obstacles.forEach(obs => {
const dx = this.x - obs.x;
const dy = this.y - obs.y;
const dist = Math.hypot(dx, dy);
if (dist < obs.radius + 20) {
this.vx += dx / dist * 0.5;
this.vy += dy / dist * 0.5;
}
});
// Apply velocity
this.x += this.vx;
this.y += this.vy;
// Boundary wrapping
if (this.x < 0) this.x = canvas.width;
if (this.x > canvas.width) this.x = 0;
if (this.y < 0) this.y = canvas.height;
if (this.y > canvas.height) this.y = 0;
// Damping
this.vx *= 0.98;
this.vy *= 0.98;
// Max speed
const speed = Math.hypot(this.vx, this.vy);
if (speed > 3) {
this.vx = (this.vx / speed) * 3;
this.vy = (this.vy / speed) * 3;
}
// Energy consumption
this.energy -= 0.1;
// Track path
if (this.path.length < 100) {
this.path.push({x: this.x, y: this.y});
}
// Check target collision
targets.forEach(target => {
const dist = Math.hypot(this.x - target.x, this.y - target.y);
if (dist < target.radius + 5) {
this.targetReached = true;
target.found = true;
db.storePath(this.path, true);
logMessage(`Agent ${this.id} found target! Path efficiency: ${(100/this.path.length).toFixed(2)}`, 'success');
}
});
}
foragingBehavior(targets, pheromones) {
// Follow pheromone trails
const nearby = pheromones.filter(p => {
const dist = Math.hypot(p.position.x - this.x, p.position.y - this.y);
return dist < 60;
});
if (nearby.length > 0 && Math.random() > params.exploration) {
const strongest = nearby.reduce((a, b) => a.strength > b.strength ? a : b);
const dx = strongest.position.x - this.x;
const dy = strongest.position.y - this.y;
const dist = Math.hypot(dx, dy);
this.vx += (dx / dist) * 0.3;
this.vy += (dy / dist) * 0.3;
} else {
// Move toward nearest target
const nearest = targets.reduce((a, b) => {
const distA = Math.hypot(a.x - this.x, a.y - this.y);
const distB = Math.hypot(b.x - this.x, b.y - this.y);
return distA < distB ? a : b;
});
if (nearest) {
const dx = nearest.x - this.x;
const dy = nearest.y - this.y;
const dist = Math.hypot(dx, dy);
this.vx += (dx / dist) * 0.2;
this.vy += (dy / dist) * 0.2;
}
}
}
flockingBehavior(agents) {
// Separation, alignment, cohesion
let sepX = 0, sepY = 0, alignX = 0, alignY = 0, cohX = 0, cohY = 0;
let neighbors = 0;
agents.forEach(other => {
if (other === this) return;
const dist = Math.hypot(other.x - this.x, other.y - this.y);
if (dist < 100) {
neighbors++;
// Separation
if (dist < 30) {
sepX += this.x - other.x;
sepY += this.y - other.y;
}
// Alignment
alignX += other.vx;
alignY += other.vy;
// Cohesion
cohX += other.x;
cohY += other.y;
}
});
if (neighbors > 0) {
this.vx += sepX * 0.05 + (alignX / neighbors) * 0.02;
this.vy += sepY * 0.05 + (alignY / neighbors) * 0.02;
const avgX = cohX / neighbors;
const avgY = cohY / neighbors;
this.vx += (avgX - this.x) * 0.01;
this.vy += (avgY - this.y) * 0.01;
}
}
explorationBehavior(pheromones) {
// Avoid visited areas (anti-pheromone)
const visited = pheromones.filter(p => p.type === 'exploration');
let avoidX = 0, avoidY = 0;
visited.forEach(p => {
const dist = Math.hypot(p.position.x - this.x, p.position.y - this.y);
if (dist < 50) {
avoidX += (this.x - p.position.x) / dist;
avoidY += (this.y - p.position.y) / dist;
}
});
this.vx += avoidX * 0.1 + (Math.random() - 0.5) * 0.5;
this.vy += avoidY * 0.1 + (Math.random() - 0.5) * 0.5;
}
}
// Global state
const db = new SwarmVectorDB();
let agents = [];
let targets = [];
let obstacles = [];
let running = false;
let animationId = null;
let params = {
agentCount: 50,
exploration: 0.3,
pheromoneDecay: 0.95,
behavior: 'foraging'
};
let stats = {
targetsFound: 0,
pathsDiscovered: 0,
collectiveIQ: 0,
generation: 0
};
// Canvas setup
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
// Initialize
function initSwarm() {
agents = [];
for (let i = 0; i < params.agentCount; i++) {
agents.push(new Agent(
Math.random() * canvas.width,
Math.random() * canvas.height,
i
));
}
}
function addTarget() {
targets.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
radius: 15,
found: false
});
logMessage('Target added to environment', 'info');
}
function addObstacle() {
obstacles.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
radius: 30
});
logMessage('Obstacle added to environment', 'info');
}
function startSwarm() {
if (!running) {
initSwarm();
running = true;
animate();
logMessage('Swarm activated! Watch emergent behavior unfold...', 'success');
}
}
function resetSwarm() {
running = false;
if (animationId) cancelAnimationFrame(animationId);
agents = [];
targets = [];
obstacles = [];
db.pheromones = [];
db.paths = [];
stats = { targetsFound: 0, pathsDiscovered: 0, collectiveIQ: 0, generation: 0 };
updateStats();
ctx.clearRect(0, 0, canvas.width, canvas.height);
logMessage('Swarm reset', 'info');
}
function animate() {
if (!running) return;
ctx.fillStyle = 'rgba(10, 10, 10, 0.1)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Decay pheromones
db.decayPheromones(params.pheromoneDecay);
// Draw pheromone trails
db.pheromones.forEach(p => {
ctx.fillStyle = `rgba(100, 200, 255, ${p.strength * 0.3})`;
ctx.beginPath();
ctx.arc(p.position.x, p.position.y, 8, 0, Math.PI * 2);
ctx.fill();
});
// Draw obstacles
obstacles.forEach(obs => {
ctx.fillStyle = '#c62828';
ctx.beginPath();
ctx.arc(obs.x, obs.y, obs.radius, 0, Math.PI * 2);
ctx.fill();
});
// Draw targets
targets.forEach(target => {
ctx.fillStyle = target.found ? '#2e7d32' : '#ffd600';
ctx.beginPath();
ctx.arc(target.x, target.y, target.radius, 0, Math.PI * 2);
ctx.fill();
if (!target.found) {
ctx.strokeStyle = '#ffd600';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(target.x, target.y, target.radius + 5, 0, Math.PI * 2);
ctx.stroke();
}
});
// Update and draw agents
agents.forEach(agent => {
agent.update(agents, targets, obstacles, db.pheromones, params.behavior);
// Leave pheromone trail
if (Math.random() < 0.1) {
db.storePheromone(
{x: agent.x, y: agent.y},
agent.targetReached ? 1.0 : 0.5,
params.behavior
);
}
// Draw agent
ctx.fillStyle = agent.targetReached ? '#00ff00' : agent.color;
ctx.globalAlpha = agent.energy / 100;
ctx.beginPath();
ctx.arc(agent.x, agent.y, 4, 0, Math.PI * 2);
ctx.fill();
ctx.globalAlpha = 1;
// Draw path for successful agents
if (agent.targetReached && agent.path.length > 1) {
ctx.strokeStyle = agent.color;
ctx.lineWidth = 1;
ctx.globalAlpha = 0.3;
ctx.beginPath();
ctx.moveTo(agent.path[0].x, agent.path[0].y);
agent.path.forEach(p => ctx.lineTo(p.x, p.y));
ctx.stroke();
ctx.globalAlpha = 1;
}
});
// Update stats
stats.targetsFound = targets.filter(t => t.found).length;
stats.pathsDiscovered = db.paths.length;
stats.collectiveIQ = Math.floor(
(stats.pathsDiscovered * 10 + db.pheromones.length * 2) / (stats.generation + 1)
);
stats.generation++;
updateStats();
animationId = requestAnimationFrame(animate);
}
function updateStats() {
document.getElementById('targetsFound').textContent = stats.targetsFound;
document.getElementById('pathsDiscovered').textContent = stats.pathsDiscovered;
document.getElementById('collectiveIQ').textContent = stats.collectiveIQ;
document.getElementById('trailCount').textContent = db.pheromones.length;
}
function updateAgentCount() {
params.agentCount = parseInt(document.getElementById('agentSlider').value);
document.getElementById('agentCount').textContent = params.agentCount;
}
function updateExploreRate() {
params.exploration = parseFloat(document.getElementById('exploreSlider').value);
document.getElementById('exploreRate').textContent = params.exploration.toFixed(1);
}
function updateDecayRate() {
params.pheromoneDecay = parseFloat(document.getElementById('decaySlider').value);
document.getElementById('decayRate').textContent = params.pheromoneDecay.toFixed(2);
}
function setBehavior(behavior) {
params.behavior = behavior;
document.querySelectorAll('.behavior-btn').forEach(btn => {
btn.classList.remove('active');
});
event.target.classList.add('active');
logMessage(`Swarm behavior changed to: ${behavior}`, 'info');
}
function logMessage(message, type = '') {
const log = document.getElementById('log');
const entry = document.createElement('div');
entry.className = `log-entry ${type}`;
const timestamp = new Date().toLocaleTimeString();
entry.textContent = `[${timestamp}] ${message}`;
log.insertBefore(entry, log.firstChild);
// Keep only last 20 messages
while (log.children.length > 20) {
log.removeChild(log.lastChild);
}
}
// Canvas click handler
canvas.addEventListener('click', (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
if (e.shiftKey) {
obstacles.push({x, y, radius: 30});
logMessage('Obstacle placed', 'info');
} else {
targets.push({x, y, radius: 15, found: false});
logMessage('Target placed', 'info');
}
});
// Initialize with some targets
addTarget();
addTarget();
addTarget();
logMessage('🐝 Swarm Intelligence initialized', 'success');
logMessage('Click to add targets, Shift+Click for obstacles', 'info');
</script>
</body>
</html>