Skip to content

Instantly share code, notes, and snippets.

@kiritodeveloper
Created July 22, 2020 02:28
Show Gist options
  • Save kiritodeveloper/4d74d542d5a0a7be2a1332cb665ca4ac to your computer and use it in GitHub Desktop.
Save kiritodeveloper/4d74d542d5a0a7be2a1332cb665ca4ac to your computer and use it in GitHub Desktop.
Genetic Algorithm
// 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++;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/p5.min.js"></script>
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