Although it's not recommended, you can add more producers in the system by clicking.
xxxxxxxxxx
//
// 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;
}
// Adds producers where mouse is dragged
function mouseDragged() {
if (millisecond % 2 == 0) {
producer = (new makeProducer(mouseX, mouseY, 30));
}
}
//------------------------------------------
// 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;
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();
}