For best viewing experience, please open in full screen here, and let it run for a while in the background. Check on your simulation occasionally to see how they’re evolving.
You can click to add producers, but this is highly unrecommended as it will alter the carrying capacity of the system. I recommend simply letting the software simulation run in the background.
//
// Supawat Vitoorapakorn
// Svitoora@andrew.cmu.edu
// Section E
// EVOLUTION: An overview of genetic algorithms
//
// Evoltuion is a software that simulates evolution over time
// of a specie of consumers. The three main conceps are:
// VARIABILITY
// At setup, a diverse pool of genetic material is randomly created.
// HERERDITY
// Every time two producers mate, it's geneteics are combined and passed down.
// SELECTION
// The consumer's speed is determine by its darkness.
// Over time natural selection will probabilistically occur.
// CHARACTERS
//
// PRODUCERS:
// A certain amount of producers is randomly created every time interval.
// The producers gathers sunlight, grows, and if it exceeds a certain amount
// of calories it splits into two. Producers' calories can be consumed
// by consumers, and if producers' calories is negative it dies.
// CONSUMERS:
// A certain amount of consumers is randomly generated at setup.
// Consumers move around randomly searching for food (prdoucers).
// Each movement of a consumer consumes a certain amount of calories, and
// If a consumer's calorie is below zero, it dies. If a consumer's calorie
// reaches a certain threshold, it will stop searching for food and begin
// searching for a potential mate nearby to reproduce with.
//
// Reproduction consumes energy, and the the consumer may die shortly after.
var time = 1; // Time variable
var w = 480; // width
var h = 480; // height
var turtles_AI = []; // array of consumers
var INDEX = 0; // ID for each turtle
var debug_mode = false; // true to see calorie transfer.
//------------------------------------------
// Creates Turtle
// Each turtle has fitness level based on its color's darkness
// The darker it is the faster it moves to food.
// Each turtle also has a certain amount of energy (calorie).
// Input: x,y,energy, color
// Output: Object turtle
function makeTurtle(tx, ty, energy = random(25, 300),
c = color(random(0, 255), random(0, 255), random(0, 255))) {
var turtle = {
x: tx,
y: ty,
life_index: random(-1, 1), //random seed for perlin bheavior
life_index2: random(-1, 1), //random seed for perlin bheavior
x_dir: 1,
y_dir: 1,
c: c,
energy: energy,
index: INDEX,
mating: false
};
turtle.fitness = fitness(turtle); //0 to 100
turtle.diameter = map(turtle.energy, 0, 100, 1, 20);
INDEX += 1;
return turtle;
}
// Determines fitness of Turtle from 0 to 100%
// The darker the color the faster it is.
// Input: Color
// Out: fitness
function fitness(turtle) {
R = red(turtle.c);
G = green(turtle.c);
B = blue(turtle.c);
avg = (R + G + B) / 3 // 0-255
return map(avg, 0, 255, 100, 0); //Darker the fitter
}
// Moves Turtle with noise
function turtle_move_AI() {
speed = .045;
for (i in turtles_AI) {
life = turtles_AI[i].life_index;
life2 = turtles_AI[i].life_index2;
x_dir = turtles_AI[i].x_dir;
y_dir = turtles_AI[i].y_dir;
// Moves turtle with Perlin Noise
SIZE = 10; //Radius of displacement
turtles_AI[i].x += x_dir * (noise(time * life) - .5) * SIZE;
turtles_AI[i].y += y_dir * (noise(time * life2) - .5) * SIZE;
turtles_AI[i].energy -= .01; //Caloric Cost of movement
}
}
// Contains any group of objects within the screen
// If object touch boundary of screen reverse direction
// Input: group
function contain(group) {
for (i in group) {
// Min
if (group[i].x < 0) {
group[i].x = 0
group[i].x_dir *= -1;
group[i].dx *= -1;
}
if (group[i].y < 0) {
group[i].y = 0
group[i].y_dir *= -1;
group[i].dy *= -1;
}
// Max
if (group[i].x > w) {
group[i].x = w
group[i].x_dir *= -1;
group[i].dx *= -1;
}
if (group[i].y > h) {
group[i].y = h
group[i].y_dir *= -1;
group[i].dy *= -1;
}
}
}
// Draws Turtle
function drawTurtle() {
for (var i = 0; i < turtles_AI.length; i++) {
d = map(turtles_AI[i].energy, 0, 100, 1, 20);
fill(turtles_AI[i].c);
ellipse(turtles_AI[i].x, turtles_AI[i].y, d, d)
fill(255);
textSize(10);
if (debug_mode == true) {
text(floor(turtles_AI[i].energy), turtles_AI[i].x, turtles_AI[i].y)
}
}
}
// Removes dead turtles
// If turtle has less than zero energy left it dies.
function turtle_update() {
for (var i = 0; i < turtles_AI.length; i++) {
if (turtles_AI[i].energy > 250) {
turtles_AI[i].mating = true;
}
if (turtles_AI[i].energy < 0) {
turtles_AI.splice(i, 1);
return
}
}
}
//------------------------------------------
// Hunt Food
// Input: individuall turtle
// Output: individiual turtle's target
function hunt(turtle) {
x = turtle.x;
y = turtle.y;
// If no food return false
if (Object.keys(PRODUCERS) == 0) {
return false
} else {
// Else target is the closest food
target = {
x: null,
y: null,
d: null
};
// Search all food
for (j in PRODUCERS) {
// Distance to food
d_food = dist(x, y, PRODUCERS[j].x, PRODUCERS[j].y);
// If target is null or closer than previous
// Reassign target to closest producers
if (target.d == null || d_food < target.d) {
target.x = PRODUCERS[j].x;
target.y = PRODUCERS[j].y;
target.d = d_food;
target.i = j
}
}
return PRODUCERS[target.i];
}
}
// Make all turtle hunt for Food
function HUNT() {
for (var i = 0; i < turtles_AI.length; i++) {
target = hunt(turtles_AI[i]);
if (target != false & turtles_AI[i].mating == false) {
moveToward(turtles_AI[i], target, turtles_AI[i].fitness);
eat(turtles_AI[i], target)
}
}
}
// Makes Object X moves towards Object Y with V velocity
function moveToward(X, Y, V) {
v = map(V, 0, 100, 0, .1);
X.x = lerp(X.x, Y.x, v / 2);
X.y = lerp(X.y, Y.y, v / 2);
}
// Eat food if food is inisde circle
// Input: individual food, FOOD array
function eat(turtle, target) {
d = dist(turtle.x, turtle.y, target.x, target.y)
// If food is inside turtle
if (d < (turtle.diameter / 2)) {
target.energy -= .2;
turtle.energy += 1;
}
}
//------------------------------------------
// Mating function of turtles
// Turtle becomes mature at 200 calories and seeks to reproduce
// Input: individual turtle
// Ouput: closest mateable target
function mate(turtle) {
x = turtle.x;
y = turtle.y;
target = {
x: null,
y: null,
d: null
};
// Search all potential mate
mate_count = 0;
for (var j = 0; j < turtles_AI.length; j++) {
// If Mate-able and not self
if (turtles_AI[j].mating == true & turtles_AI[j] != turtle) {
mate_count += 1;
d = dist(turtles_AI[j].x, turtles_AI[j].y, turtle.x, turtle.y)
if (target.d == null || d < target.d) {
target.x = turtles_AI[j].x;
target.y = turtles_AI[j].y;
target.d = d;
target.i = j
}
}
}
// If there is no mate return false.
if (mate_count == 0) {
return false
}
// If there is mate return target.
else {;
return turtles_AI[target.i];
}
}
// Makes turtles have sex.
// If mateable turtles touch one another they both lose 100 calorie
// and creates 1 baby. Turtle bcomes mateable at 200 calories.
function sex(turtle, target) {
d = dist(turtle.x, turtle.y, target.x, target.y)
if (d < turtle.diameter / 2) {
turtle.energy -= 100;
target.energy -= 100;
// Genetic Averaging and Mutation
c = lerpColor(turtle.c, target.c, random(.3, .7));
x = (turtle.x + target.x) / 2;
y = (turtle.y + target.y) / 2;
turtles_AI.push(makeTurtle(x, y, 66, c))
}
}
// Loop through turtles to and make them mate
function MATE() {
for (var i = 0; i < turtles_AI.length; i++) {
target = mate(turtles_AI[i]);
if (target != false & turtles_AI[i].mating == true) {
moveToward(turtles_AI[i], target, turtles_AI[i].fitness);
sex(turtles_AI[i], target);
}
}
}
//------------------------------------------
// Control
// Adds producers where mouse is clicked
function mouseClicked() {
// FOOD.push(new makeFood(mouseX, mouseY));
producer = (new makeProducer(mouseX, mouseY, 30));
PRODUCERS[time] = producer;
print(PRODUCERS);
}
// Adds producers where mouse is dragged
function mouseDragged() {
if (millisecond % 2 == 0) {
producer = (new makeProducer(mouseX, mouseY, 30));
PRODUCERS[time] = producer;
}
}
//------------------------------------------
// Producers
// Make food from sunlight
// Grows overtime and increase cell amount
var PRODUCERS = {};
// Creates prodcuers that grows from light
// Producers are eaten by turtles
// Input: x,y, energy, dx, dy
function makeProducer(x, y, energy = 10, dx = 0, dy = 0) {
this.x = x;
this.y = y;
this.life_index = random(-1, 1); //random seem for perlin beheavior
this.life_index2 = random(-1, 1); //random seem for perlin beheavior
this.energy = energy;
this.c = color(0, 255 / 2, 0, 255 * random(.5, 1));
this.mitosis = false;
this.dx = dx;
this.dy = dy;
}
// Draws producers
function drawProducer() {
for (key in PRODUCERS) {
x = PRODUCERS[key].x;
y = PRODUCERS[key].y;
c = PRODUCERS[key].c;
energy = PRODUCERS[key].energy;
fill(c);
strokeWeight(1)
// Perlin noise to size to give it life
base_vivacity = 5;
speed = 1
life = base_vivacity * (sin(time / 5 * speed))
// Make rectangles rotate randomly
push();
rectMode(CENTER);
translate(x, y);
rotate((noise(PRODUCERS[key].life_index) * 360));
rect(0, 0, energy + life, energy + life);
pop();
// Debug mode
if (debug_mode == true) {
fill(255);
textAlign(CENTER);
text(round(PRODUCERS[key].energy), x, y);
}
}
}
// Makes producer grow and reproduce if it has enough energy
function growProducer() {
for (key in PRODUCERS) {
// Grow Producer
life_index = PRODUCERS[key].life_index;
PRODUCERS[key].energy += noise(time * life_index) / 4;
// Reproduce
if (PRODUCERS[key].energy > 50) {
PRODUCERS[key].mitosis = true
}
}
}
// Producers preform mitosis by using it's energy to reproduce
function mitosisProducer() {
for (key in PRODUCERS) {
if (PRODUCERS[key].mitosis == true) {
energy = PRODUCERS[key].energy
// Create 2 new cells
for (i = 0; i < 2; i++) {
producer = (new makeProducer(
PRODUCERS[key].x,
PRODUCERS[key].y,
5,
random(-energy / 4, energy / 4),
random(-energy / 4, energy / 4)));
PRODUCERS[time] = producer;
PRODUCERS[key].energy -= 25;
}
}
}
}
// Basic Physics for producer while splittig
function mitosisPhysic() {
for (key in PRODUCERS) {
PRODUCERS[key].x += PRODUCERS[key].dx;
PRODUCERS[key].y += PRODUCERS[key].dy;
PRODUCERS[key].dx = lerp(PRODUCERS[key].dx, 0, .1);
PRODUCERS[key].dy = lerp(PRODUCERS[key].dy, 0, .1);
}
}
// Kills Producer if its energy is below 0.
function dieProducer() {
for (key in PRODUCERS) {
if (PRODUCERS[key].energy < 0) {
delete PRODUCERS[key]
}
}
}
// Give Producer Perlin noise to make it look alive
function lifeProducer() {
for (key in PRODUCERS) {
SIZE = .5;
lifeX = (noise(time * PRODUCERS[key].life_index) - .5) * SIZE;
lifeY = (noise(time * PRODUCERS[key].life_index2) - .5) * SIZE;
PRODUCERS[key].x += lifeX;
PRODUCERS[key].y += lifeY;
}
}
var producer_timer = 1;
// Adds new producer into the system every producer timer interval
function add_food(interval) {
producer_timer += 1;
print(producer_timer)
if (producer_timer % 500 == 0) {
for (var i = 0; i < random(0, 6); i++) {
PRODUCERS[time] = (new makeProducer(random(0, w), random(0, h), 1));
time += .1;
}
}
}
//------------------------------------------
// SETUP
function preload() {
w = windowWidth;
h = windowHeight;
}
// Creates petri dish
function setup() {
createCanvas(w, h);
background(255);
num_node = 100;
// Create a diverse genetic pool of consumers
for (i = 0; i < num_node; i++) {
t = makeTurtle(random(0, w), random(0, h));
turtles_AI.push(t);
}
// Initial set of producers
for (i = 0; i < num_node / 10; i++) {
PRODUCERS[i] = (new makeProducer(random(0, w), random(0, h)));
}
}
//------------------------------------------
function draw() {
background(255, 255, 255, 255 * .33);
millisecond = floor(millis()) % 2000;
// Model
time += .1;
turtle_move_AI();
contain(turtles_AI);
contain(PRODUCERS);
HUNT();
MATE()
turtle_update();
// Producers
growProducer();
dieProducer();
mitosisProducer();
mitosisPhysic();
lifeProducer();
add_food(1000);
// Draw
noStroke();
drawTurtle();
drawProducer();
}
Producer splits into two after growing big enough:
Consumers mate after it exceeds a certain caloric threshold and combines their genes with some amount of mutation:
At first, the simulation begins with a lot genetic diversity:
But over time due to natural selection for faster (darker) consumers, the genetic diversity is significantly decreased:
Interestingly, membrane-like structures also start to emerge from how food is being consumed. Here, the producers form a membrane around a dense cluster of producers that is rapidly splitting.
These membrane-like and clusters macrostructures were not explicitly programmed, they emerge probabilistically out of the specification and rules of this simulation. Here, a wall of producer is protecting a rapidly splitting colony of producers.
Group behavior also emerged out of the algorithm. Here, the producers began to form almost cell like clusters where food is rapidly consumed and reproduction distance is significantly decreased thereby creating a cell-like unit.
As the system reaches its carrying capacity, the producer begins to starve and die.
And as this cycle of extreme population swing for both the producers and consumers restarts, the genetic diversity of population doesn’t. Over time, despite natural mutation rate, the consumer population becomes abosolutely homogenous without any genetic diversity. In real life, this means that the population is extremely susceptible to a disease that can complete wipe its whole population out.
Conclusion:
- Features can be created implicitly through explicitly programming beheaviors. Alhtough certain feaures of this simulation wasn’t explicity programmed, it emerge through the algorithms of the character’s beheavior.
- A system of genetic evolution will never reach its ultimate form. Although the black ones were the fastest, they needed to reproduced with inferior consumers thereby creating sub par offpsrings. Over time, this means that humans as a geneticically evolving organsims will never reach our ultimate form, if such form exist.
- What happens when there is no genetic diversity? If life on earth continued to evolve due to natural selection and converge towards an ideal form and homogenity what would happen?