Swarm experiments

https://en.wikipedia.org/wiki/Swarm_intelligence

by Anton

HTML

<div id="world"></div>
<div id="stats">Stats</div>
<div id="heading"><div id="arrow"></div></div>

CSS

body {
    background: #20262E;
    font-family: Helvetica;
    padding: 20px;
}

.boid {
    position: absolute;
    display: block;
    width: 5px;
    height: 5px;
    background-color: #EEE;
    border-radius: 50%;
}

#world {
    position: absolute;
    height: calc(95% - 40px);
    width: calc(95% - 40px);
    border: solid 1px #333;
}

#stats {
    position: absolute;
    top: 5px;
    right: 5px;
    color: #555;
    font-size: 14px;
    text-align: right;
}

#heading {
    position: absolute;
    bottom: 30px;
    right: 30px;
    width: 60px;
    height: 60px;
    border: solid 1px #555;
    border-radius: 50%;
}

#arrow {
    position: absolute;
    top: 0;
    right: 30px;
    width: 1px;
    height: 30px;
    transform-origin: bottom center;
    background-color: #555;
}

JavaScript 1.7

/**
 *	Simulate swarm movements using Boids approach, three rules:
 *	1. Steer to avoid crowding flockmates
 *	2. Steer towards average heading of local flockmates
 *	3. Steer towards average position of local flockmates
 */
const count = 50;
const duration = 60; // seconds
const fps = 25;
const headingAdj = 0.5; // heading adjustment rate
const attractionAdj = 0.5; // attraction adjustment rate
const pushAdj = 0.5; // push away from too close neighbours
const bounceAdj = 0.5;
const maxDelta = 5;
const localZone = 100; // Closer boids are "local"
const comfortZone = 20;

const world = $('#world');
const stats = $('#stats');
const maxX = world.width();
const maxY = world.height();

const swarm = [];

class Boid {
    constructor(id) {
    	this.id = id;
        this.x = Math.random() * maxX;
        this.y = Math.random() * maxY;
        this.dx = maxDelta * (2 * Math.random() - 1);
        this.dy = maxDelta * (2 * Math.random() - 1);
        
        this.calcSpeed();
        this.calcHeading();

        this.element = $(`<div class="boid"></div>`);
        world.append(this.element);

        this.draw();
    }
    
    calcSpeed() {
    	this.speed = Math.sqrt(this.dx * this.dx + this.dy * this.dy);
    }
    
    calcHeading() {
    	this.heading = this.speed === 0 ? 0 : Math.asin(this.dx / this.speed);
        if(this.dy < 0) this.heading = Math.PI - this.heading;
    }

    draw() {
        this.element.css({ top: maxY - this.y, left: this.x });
    }
    
    move(avgX, avgY, avgHeading) {
    	this.update(avgX, avgY, avgHeading);
    
    	this.x += this.dx;
        this.y += this.dy;
        
        // Reflect off sides
        if(this.x < 0) { this.x = 0; this.dx *= -bounceAdj; }
        if(this.x > maxX) { this.x = maxX; this.dx *= -bounceAdj; }
        if(this.y < 0) { this.y = 0; this.dy = -bounceAdj; }
        if(this.y > maxY) { this.y = maxY; this.dy = -bounceAdj; }
        
        this.draw();
    }
    
    update() {
    	this.calcSpeed();
  ...