Week08: Genetic Algorithm

Although there are different sketches using a genetic algorithm, I wanted to make an interactive one rather than the ones that only give you a fixed result or the ones that only allow users(?) watch what they are doing.
Therefore, the most interesting one for me among the genetic algorithm examples was Interactive Selection.
In this sketch, the images look like human faces. They have faces, eyes, noses and mouths.
Yet I wondered what the results would be if I try the same experiment with random abstract images.

In my sketch, I draw a random number of vertices for an image and then draw triangles with them. Those triangles are filled with a random colour.
Over the generation, the images are changing reflecting a user’s choices.
The variables that affect the evolution are the number of vertices, the positions of vertices, and their colours.
In the class of the Nature of Code, we learnt the crossover examples of the way in which the next generations inherit their properties from their parents, fifty-fifty.
Instead, I used an average value of two properties for the numbers and positions of vertices.

Following are the main codes of my sketch:

[sketch.js]

let d_width, d_height, offset;

let tri_min, tri_max;

let population;

let nClick, maxClick;

let gen, likes;

function setup() {
  createCanvas(640, 640);
  
  colorMode(HSB, 360);
  
  noStroke();  

  d_width = 180;
  d_height = 180;
  offset = 40;
  
  tri_min = 5;
  tri_max = 20;
  
  nClick = 0;
  maxClick = 9;
  
  let mutationRate = 0.1;
  let popNum = 9;
  
  population = new Population(mutationRate, popNum);
  
  gen = select("#gen");
  likes = select("#currentLikes");

}

function draw() {
  
  background(330);
  population.display();

}

function mouseReleased() {
  
  population.checkClick(); 
 
  if(nClick >= maxClick) {
    
    population.reproduce();
    nClick = 0;
    likes.html(nClick);

  }
  
}





[population.js]

class Population {
  
  constructor(m, n) {
    this.mutation = m;
    this.popNum = n;
    this.drawings = [];
    this.generation = 1;
    
    for(let i=0; i<this.popNum; i++) {
      let xIdx = i%3;
      let yIdx = floor(i/3);
      let x = d_width * xIdx + 25*(xIdx+1);
      let y = d_height * yIdx + 25*(yIdx+1);
      this.drawings.push(new Drawing(new Triangles(), x, y));
    }
    
  }
  
  display() {
  	for(let i=0; i<this.popNum; i++) {
      push();
      translate(this.drawings[i].x, this.drawings[i].y);
      fill(360);
      rect(0, 0, d_width, d_height); 
      this.drawings[i].display();
      pop();
    }
    
  }
  
  select() {
    
    let r = floor(random(1, 10)); 
    
    for(let i=0; i<this.popNum; i++) {
      if(r <= this.drawings[i].score) {
        return i;
      } else {
        r -= this.drawings[i].score;
      }
    }
    
    
  }
  
  reproduce() {
    
    let children = [];
    
    for(let i=0; i<this.popNum; i++) {
      let childTris;
      let mum = this.select();
      let dad = this.select();
      
      let mumTris = this.drawings[mum].triangles;
      let dadTris = this.drawings[dad].triangles;
      
      childTris = mumTris.crossover(dadTris);
      childTris.mutate(this.mutation);
      
      children.push(childTris);
      
    } // for
    
    for(let i=0; i<this.popNum; i++) {
      let xIdx = i%3;
      let yIdx = floor(i/3);
      let x = d_width * xIdx + 25*(xIdx+1);
      let y = d_height * yIdx + 25*(yIdx+1);

      this.drawings[i] = new Drawing(children[i], x, y);
    } // for
    
    this.generation++;
    gen.html(this.generation);
  } // reproduce
  
  checkClick() {
    for(let i=0; i<this.popNum; i++) {
      this.drawings[i].isClicked();
    }
  }
  
}

[drawing.js]

class Drawing {
  constructor(triangles_, x_, y_) {
    this.triangles = triangles_;
    this.x = x_;
    this.y = y_;
    this.score = 0;
  }
  
  display() {
    for(let i=0; i<this.triangles.num; i++) {
      fill(this.triangles.h[i], 360, this.triangles.b[i], 50);
  
      beginShape(TRIANGLES);
      	vertex(this.triangles.vertices[i].x, this.triangles.vertices[i].y);
      	vertex(this.triangles.vertices[i+1].x, this.triangles.vertices[i+1].y);
      	vertex(this.triangles.vertices[i+2].x, this.triangles.vertices[i+2].y);
      endShape();
    }
    
    textSize(11);
    fill(0, 360, 360);
    text("♥︎", d_width-(offset-5), offset/2);
    fill(0, 50, 50);
    text(this.score, d_width-(offset/2), offset/2);
  }
  
  isClicked() {
    if(mouseX > this.x && mouseX < (this.x+d_width) && mouseY > this.y && mouseY < (this.y + d_height)) {
      this.score++;
      nClick++;
      likes.html(nClick);
    }
  }
  
}

[triangles.js]

class Triangles {
  constructor(v_, h_, b_) {
    if(v_) {
      this.num = h_.length;
      this.vertices = v_;
      this.h = h_;
      this.b = b_;

    } 
    else {
      this.num = floor(random(tri_min, tri_max));
      this.vertices = [];
      this.h = [];
      this.b = [];
      
      for(let i=0; i<3+(this.num-1); i++) {
        let x = floor(random(offset, d_width-offset));
        let y = floor(random(offset, d_height-offset));;
        this.vertices.push(new Vertex(x, y));
      } // for 1
      
      let h = floor(random(0, 361));
      for(let i=0; i<this.num; i++) {
        this.h.push(h);
        this.b.push(floor(random(0, 361)));
      } // for 2
    } // else
  } // constructor
  
  crossover(partner) {
    let newNum = floor((this.num + partner.num)/2);
    let newVertices = new Array(3+(newNum-1));
    let newH = new Array(newNum);
    let newB = new Array(newNum);
   
    for(let i=0; i<newVertices.length; i++) {
      let x1, y1, x2, y2;
      let newX, newY;
      if(i<this.vertices.length) {
        x1 = this.vertices[i].x;
        y1 = this.vertices[i].y;
      } else {
        x1 = partner.vertices[i].x;
        y1 = partner.vertices[i].y;
      }
      if(i<partner.vertices.length) {
        x2 = partner.vertices[i].x;
        y2 = partner.vertices[i].y;
      } else {
        x2 = this.vertices[i].x;
        y2 = this.vertices[i].y;
      }
      
      newX = floor((x1+x2)/2);
      newY = floor((y1+y2)/2);
      
      newVertices[i] = new Vertex(newX, newY);
    } // for
    
    	if(this.num >= partner.num) {
        for(let i=0; i<newNum; i++) {
          if(i<partner.num) {
          	if(i%2) {
              newH[i] = partner.h[i];
              newB[i] = partner.b[i];
            } else {
              newH[i] = this.h[i];
              newB[i] = this.b[i];
            }
          } else {
            newH[i] = this.h[i];
            newB[i] = this.b[i];
          } // else
        } // for
      } // if
      else {
        for(let i=0; i<newNum; i++) {
          if(i < this.num) {
            if(i%2) {
              newH[i] = partner.h[i];
              newB[i] = partner.b[i];
            } else {
              newH[i] = this.h[i];
              newB[i] = this.b[i];
            }
          } else {
            newH[i] = partner.h[i];
            newB[i] = partner.b[i];
          } // else
      } // else   
      
    } // for
      
    let newTri = new Triangles(newVertices, newH, newB);
   
    return newTri;
    
  } // crossover
  
  mutate(mutationRate) {
    
    for(let i=0; i<this.vertices.length; i++) {
      let r = random(1);
      if(r<mutationRate) {
        this.vertices[i].x = floor(random(offset, d_width-offset));
        this.vertices[i].y = floor(random(offset, d_height-offset));
      }
    }
    
    for(let i=0; i<this.num; i++) {
      let r = random(1);
      if(r<mutationRate) {
        this.h[i] = floor(random(0, 361));
      }
      r = random(1);
      if(r<mutationRate) {
        this.b[i] = floor(random(0, 361));
      }
    } // for
    
    
  } // mutate
  
}
  
class Vertex {
  constructor(x_, y_) {
    this.x = x_;
    this.y = y_;
  }
}

The images in the left screenshot below are the first generation, and those in the right one are the 12th generation:

I’ve got to say that the images in the new generation don’t always look more beautiful than those in older generations. I think this is because a lot of things affect consciously and subconsciously when we consider something is beautiful, and therefore, it is hard to figure out exactly what factors they are. In other words, what I think beautiful isn’t simply an average of many other beautiful things. However, what is clear is that the 9 images are getting more and more similar over generations.

Following is my working sketch, and the full code is available on my GitHub.

Leave a Reply

Your email address will not be published. Required fields are marked *