Flocks of RGB Bloops mate and mix their colours.
Created
July 22, 2020 02:28
-
-
Save kiritodeveloper/4d74d542d5a0a7be2a1332cb665ca4ac to your computer and use it in GitHub Desktop.
Genetic Algorithm
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://www.kadenze.com/courses/the-nature-of-code | |
// https://www.kadenze.com/courses/all/gallery/all?&sort=latest&search=kyle%20anderson | |
// Session 5: Genetic Algorithms | |
function DNA(newgenes) { | |
if (newgenes) { | |
// because too dark | |
while(newgenes.hue < 285 && newgenes.hue > 220){ | |
newgenes.hue = random(0, 359); | |
} | |
this.r = (newgenes.r !== undefined) ? newgenes.r : random(10, 12); | |
this.maxspeed = random(6, 8); | |
this.maxforce = (newgenes.maxforce !== undefined) ? newgenes.maxforce : random(0.2, 0.4); | |
this.maxTailLength = (newgenes.maxTailLength !== undefined) ? newgenes.maxTailLength : random(8, 16); | |
this.hue = (newgenes.hue !== undefined) ? newgenes.hue : random(0, 359); | |
this.saturation = 100; | |
this.brightness = 100; | |
} else { | |
// The genetic sequence | |
this.r = random(10, 12); | |
this.maxspeed = random(6, 8); | |
this.maxforce = random(0.2, 0.4); | |
this.maxTailLength = random(8, 16); | |
this.hue = random(0, 359); | |
this.saturation = random(95, 100); | |
this.brightness = random(95, 100); | |
this.genes = new Array(1,0); | |
} | |
} | |
function Bloop(i, x, y, c) { | |
this.idx = i; // index in bloops array | |
this.position = createVector(x, y); | |
this.acceleration = createVector(0, 0); | |
this.velocity = createVector(0, 0); | |
this.fitness = 38; | |
this.alive = true; | |
this.age = 0; | |
this.tail = []; | |
this.tailLength = random(3, 5); | |
this.dna = new DNA(c); | |
this.applyBehaviors = function(bloops) { | |
if(!this.alive){ | |
return; | |
} | |
if(wind = this.isCloseToEdge()){ | |
this.applyForce(wind); // push back to center | |
} | |
this.flock(bloops); | |
} | |
this.applyForce = function(force) { | |
this.acceleration.add(force); | |
} | |
// Are bloops touching | |
this.contains = function(m) { | |
var l = m.position; | |
return l.x > (this.position.x-this.dna.r) && l.x < (this.position.x+this.dna.r) && l.y > (this.position.y-this.dna.r) && l.y < (this.position.y+this.dna.r); | |
}; | |
// We accumulate a new acceleration each time based on three rules | |
this.flock = function(boids) { | |
var sep = this.separate(boids); // Separation | |
var ali = this.align(boids); // Alignment | |
var coh = this.cohesion(boids); // Cohesion | |
// Arbitrarily weight these forces | |
sep.mult(4); | |
ali.mult(.01); | |
coh.mult(1.5); | |
// Add the force vectors to acceleration | |
this.applyForce(sep); | |
this.applyForce(ali); | |
this.applyForce(coh); | |
}; | |
// Separation | |
// Method checks for nearby bloops and steers away | |
this.separate = function(bloops) { | |
var desiredseparation = 20; | |
var sum = createVector(); | |
var count = 0; | |
// For every boid in the system, check if it's too close | |
for (var i = 0; i < bloops.length; i++) { | |
var d = p5.Vector.dist(this.position, bloops[i].position); | |
// If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself) | |
if ((d > 0) && (d < desiredseparation)) { | |
// Calculate vector pointing away from neighbor | |
var diff = p5.Vector.sub(this.position, bloops[i].position); | |
diff.normalize(); | |
diff.div(d); // Weight by distance | |
sum.add(diff); | |
count++; // Keep track of how many | |
} | |
} | |
// Average -- divide by how many | |
if (count > 0) { | |
sum.div(count); | |
// Our desired vector is the average scaled to maximum speed | |
sum.normalize(); | |
sum.mult(this.dna.maxspeed); | |
// Implement Reynolds: Steering = Desired - Velocity | |
sum.sub(this.velocity); | |
sum.limit(this.dna.maxforce); | |
} | |
return sum; | |
} | |
// Alignment | |
// For every nearby boid in the system, calculate the average velocity | |
this.align = function(boids) { | |
var neighbordist = 50; | |
var sum = createVector(0, 0); | |
var count = 0; | |
for (var i = 0; i < boids.length; i++) { | |
var d = p5.Vector.dist(this.position, boids[i].position); | |
if ((d > 0) && (d < neighbordist)) { | |
sum.add(boids[i].velocity); | |
count++; | |
} | |
} | |
if (count > 0) { | |
sum.div(count); | |
sum.normalize(); | |
sum.mult(this.dna.maxspeed); | |
var steer = p5.Vector.sub(sum, this.velocity); | |
steer.limit(this.dna.maxforce); | |
return steer; | |
} else { | |
return createVector(0, 0); | |
} | |
} | |
// Cohesion | |
// For the average location (i.e. center) of all nearby boids, calculate steering vector towards that location | |
this.cohesion = function(boids) { | |
if(flockState === 'none'){ | |
return createVector(0, 0); | |
} | |
var neighbordist = 500; | |
var attraction = 1; | |
var sum = createVector(0, 0); // Start with empty vector to accumulate all locations | |
var count = 0; | |
for (var i = 0; i < boids.length; i++) { | |
attraction = Math.abs(this.dna.hue-boids[i].dna.hue); | |
attraction = map(attraction, 0, 60, 10, 1); | |
if(flockState === 'flock' && attraction < 5){ | |
continue; | |
} | |
else if(flockState === 'mate' && attraction < 8 && this.contains(boids[i])){ | |
this.reproduce(boids[i]); | |
} | |
var d = p5.Vector.dist(this.position, boids[i].position); | |
if ((d > 0) && (d < neighbordist)) { | |
sum.add(boids[i].position); // Add location | |
count++; | |
} | |
} | |
if (count > 0) { | |
sum.div(count); | |
return this.seek(sum); // Steer towards the location | |
} else { | |
return createVector(0, 0); | |
} | |
} | |
// A method that calculates a steering force towards a target | |
// STEER = DESIRED MINUS VELOCITY | |
this.seek = function(target) { | |
var desired = p5.Vector.sub(target,this.position); // A vector pointing from the location to the target | |
// Normalize desired and scale to maximum speed | |
desired.normalize(); | |
desired.mult(this.dna.maxspeed); | |
// Steering = Desired minus velocity | |
var steer = p5.Vector.sub(desired,this.velocity); | |
steer.limit(this.dna.maxforce); // Limit to maximum steering force | |
return steer; | |
} | |
// FITNESS FUNCTION | |
// fitness reduced by increased age | |
// but a long tail makes bloop more attractive | |
this.calcFitness = function(purpose) { | |
if(purpose === 'reproduction'){ | |
return this.fitness - this.age + this.tailLength; | |
} | |
return this.fitness - this.age; | |
} | |
this.checkHealth = function(){ | |
if(random(this.calcFitness('health')*740) < this.age){ | |
this.alive = 'dying'; | |
} | |
if(this.alive === 'dying'){ | |
if(this.dna.brightness < 0){ | |
this.alive = false; | |
} | |
this.dna.saturation -= 3; | |
this.dna.brightness -= 1; | |
} | |
} | |
// update location | |
this.update = function() { | |
if(!this.alive){ | |
return; | |
} | |
if(frameCount%80 === 0){ | |
if(this.tailLength < this.dna.maxTailLength){ | |
this.tailLength++; | |
} | |
this.age++; | |
this.dna.saturation--; | |
} | |
this.tail.push(this.position.copy()); | |
if(this.tail.length > this.tailLength){ | |
this.tail.shift(); | |
} | |
// Update velocity | |
this.velocity.add(this.acceleration); | |
// Limit speed | |
this.velocity.limit(this.dna.maxspeed); | |
this.position.add(this.velocity); | |
// Reset accelertion to 0 each cycle | |
this.acceleration.mult(0); | |
} | |
this.display = function() { | |
if(!this.alive){ | |
return; | |
} | |
var tailWidth = this.tail.length*2; | |
noStroke(); | |
for(var i = 0; i < this.tail.length; i++){ | |
fill(this.dna.hue, this.dna.saturation, this.dna.brightness); | |
ellipse(this.tail[i].x, this.tail[i].y, i+3, i+3); | |
} | |
fill(this.dna.hue, this.dna.saturation, this.dna.brightness); | |
strokeWeight(2); | |
push(); | |
translate(this.position.x, this.position.y); | |
ellipse(0, 0, this.dna.r, this.dna.r); | |
pop(); | |
} | |
this.reproduce = function(otherParent) { | |
// reproduction | |
if (random(1) < this.calcFitness('reproduction')/10000) { | |
// Child has mix of parent DNA | |
var childDNA = new Object(); | |
childDNA.r = (random(1) < 0.0005) ? this.dna.r : otherParent.dna.r; | |
childDNA.maxspeed = 20; | |
childDNA.maxforce = (random(1) < 0.0005) ? this.dna.maxforce : otherParent.dna.maxforce; | |
childDNA.maxTailLength = (random(1) < 0.0005) ? this.dna.maxTailLength : otherParent.dna.maxTailLength; | |
childDNA.hue = Math.abs((this.dna.hue+otherParent.dna.hue)/2); | |
childDNA.saturation = 100; | |
childDNA.brightness = 100; | |
// random mutation | |
if (random(1, 9) < 3) { | |
childDNA.hue = random(360); | |
} | |
bloops.push( new Bloop(bloops.length+1, this.position.x, this.position.y, childDNA) ); | |
this.fitness -= 5; | |
} | |
else { | |
return null; | |
} | |
} | |
// Wraparound | |
this.borders = function() { | |
if (this.position.x < -this.dna.r) this.position.x = width+this.dna.r; | |
if (this.position.y < -this.dna.r) this.position.y = height+this.dna.r; | |
if (this.position.x > width+this.dna.r) this.position.x = -this.dna.r; | |
if (this.position.y > height+this.dna.r) this.position.y = -this.dna.r; | |
} | |
// Is the object close to screen edge? | |
// If so return vector to push it inwards | |
this.isCloseToEdge = function(){ | |
if(this.position.x < 40) { | |
return createVector(2,0); | |
} | |
else if(this.position.x > (width-40)) { | |
return createVector(-2,0); | |
} | |
else if(this.position.y < 40) { | |
return createVector(0,2); | |
} | |
else if(this.position.y > (height-40)) { | |
return createVector(0,-2); | |
} | |
else { | |
return false; | |
} | |
} | |
} | |
var bloops = []; | |
var flockState = 'none'; | |
var counter = 0; | |
function setup() { | |
createCanvas(windowWidth, windowHeight); | |
colorMode(HSB); | |
// We are now making random bloops and storing them in an array | |
for (var i = 0; i < 120; i++) { | |
if(i%3 == 0){ | |
// green | |
bloops.push(new Bloop(i, random(width),random(height), {hue: 77})); | |
} | |
else if(i%2 == 0){ | |
// blue | |
bloops.push(new Bloop(i, random(width),random(height), {hue: 194})); | |
} | |
else { | |
// red | |
bloops.push(new Bloop(i, random(width),random(height), {hue: 346})); | |
} | |
} | |
} | |
function draw() { | |
background(38); | |
if(counter === 10){ | |
// similar colours flock together | |
flockState = 'flock'; | |
} | |
// all bloops flock & mate when they meet | |
// depending on fitness and colour variation | |
else if(counter === 560){ | |
flockState = 'mate'; | |
} | |
else if(counter === 720){ | |
flockState = 'none'; | |
counter = 0; | |
} | |
for (var i = 0; i < bloops.length; i++) { | |
if(!bloops[i]){ | |
return; | |
} | |
bloops[i].checkHealth(); | |
bloops[i].applyBehaviors(bloops); | |
bloops[i].update(); | |
bloops[i].borders(); | |
bloops[i].display(); | |
if(!bloops[i].alive){ | |
bloops.splice(i, 1); | |
} | |
} | |
counter++; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/p5.min.js"></script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
body { | |
padding: 0; | |
margin: 0; | |
} | |
canvas { | |
display: block; | |
margin: auto; | |
vertical-align: top; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment