svitoora – Evolution

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.

Evolution

// 
// 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:

Producers splitting.

Consumers mate after it exceeds a certain caloric threshold and combines their genes with some amount of mutation:

Consumers mating.

At first, the simulation begins with a lot genetic diversity:

The simulation begins with a lot gentic diversity.

But over time due to natural selection for faster (darker) consumers, the genetic diversity is significantly decreased:

Over time natural selection reduces genetic diversity.

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.

Membrane-like structures emerged although it wasn’t explicity programmed.

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.

Wall of producers protecting a rapidly growing colony.

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.

Individual cell-like clusters of rapidly reproducing and consuming begin to emerge.
Here, one cell-like cluster separately moves towards another group of producers.
Last colony of producers consumed.

As the system reaches its carrying capacity, the producer begins to starve and die.

No more food left.
Consumers begin to starve and die.
As food emerge, the cycle of population repeats.

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. 

No more genetic diversity is left.

Conclusion:

  1. 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.
  2. 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.
  3. 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?

 

SaveSave

SaveSave

SaveSave

Leave a Reply