eco
See /f0blog/eco/
//ecosystem - redFrik 2010
//as described in Flake - "The Computational Beauty of Nature" (page 191)
//ported 2010-11-23 from supercollider
//updated 2010-11-27 important bugfixes
//updated 2020-08-01 for processing3
/*
white = empty space
green = plant
red = herbivore
blue = carnivore
// * For every time step:
// * For every empty cell, e:
// * If e has three or more neighbors that are plants, then e will become a plant at the next time step (assuming it isn't trampled by a herbivore or carnivore).
// * For every herbivore, h (in random order):
// * Decrease energy reserves of h by a fixed amount.
// * If h has no more energy, then h dies and becomes an empty space.
// * Else, if there is a plant next to h, then h moves on top of the plant, eats it, and gains the plant's energy.
// * If h has sufficient energy reserves, then it will spawn a baby herbivore on the space that it just exited.
// * Else, h will move into a randomly selected empty space, if one exists, that is next to h's current location.
// * For every carnivore, c (in random order):
// * Decrease energy reserves of c by a fixed amount.
// * If c has no more energy, then c dies and becomes an empty space.
// * Else, if there is a herbivore next to c, then c moves on top of the herbivore, eats it, and gains the herbivore's energy.
// * If c has sufficient energy reserves, then it will spawn a baby carnivore on the space that it just exited.
// * Else, c will move into a randomly selected empty space that is next to c's current location. If there are no empty spaces, then c will move through plants.
*/
//--settings
final int fps= 60; //default framerate (change with +/-)
final int screenWidth= 800; //window width
final int screenHeight= 600; //window height
final int dim= 100; //dimensions x*x
final float plantInitChance= 0.2; //percentage chance cell start as plant
final float herbivoreInitChance= 0.15; //percentage chance cell start as herbivore
final float carnivoreInitChance= 0.05; //percentage chance cell start as carnivore
final int plantEnergyInit= 25; //initial energy
final int herbivoreEnergyInit= 25; //initial energy
final int carnivoreEnergyInit= 25; //initial energy
final int herbivoreEnergyLoss= 10; //energy decrease per generation
final int carnivoreEnergyLoss= 10; //energy decrease per generation
final int herbivoreEnergyBaby= 250; //threshold for spawning baby
final int carnivoreEnergyBaby= 250; //threshold for spawning baby
//--variables
int[][][] objects;
ArrayList empty= new ArrayList();
ArrayList plants= new ArrayList();
ArrayList herbivores= new ArrayList();
ArrayList carnivores= new ArrayList();
final float sizex= float(screenWidth)/dim;
final float sizey= float(screenHeight)/dim;
final float rectWidth= sizex*0.75;
final float rectHeight= sizey*0.75;
final int nil= -99999;
//--setup
void settings() {
size(screenWidth, screenHeight);
}
void setup() {
frameRate(fps);
noStroke();
objects= new int[dim][dim][3];
for (int x= 0; x<dim; x++) {
for (int y= 0; y<dim; y++) {
float q= random(1.0);
objects[x][y][0]= nil;
objects[x][y][1]= nil;
objects[x][y][2]= nil;
if (q<plantInitChance) {
objects[x][y][0]= plantEnergyInit;
} else if (q<(plantInitChance+herbivoreInitChance)) {
objects[x][y][1]= herbivoreEnergyInit;
} else if (q<(plantInitChance+herbivoreInitChance+carnivoreInitChance)) {
objects[x][y][2]= carnivoreEnergyInit;
}
}
}
}
void keyPressed() {
if (key==' ') { //spacebar resets
setup();
} else if (key=='+') {
frameRate(min(120, frameRate+3)); //+ increase framerate
} else if (key=='-') {
frameRate(max(1, frameRate-3)); //- decrease framerate
}
}
int[][] neighbours(int x, int y) { //return nearby x, y positions
int[][] n= new int[8][2];
n[0][0]= myMod(x-1, dim);
n[0][1]= myMod(y-1, dim);
n[1][0]= x;
n[1][1]= myMod(y-1, dim);
n[2][0]= (x+1)%dim;
n[2][1]= myMod(y-1, dim);
n[3][0]= myMod(x-1, dim);
n[3][1]= y;
n[4][0]= (x+1)%dim;
n[4][1]= y;
n[5][0]= myMod(x-1, dim);
n[5][1]= (y+1)%dim;
n[6][0]= x;
n[6][1]= (y+1)%dim;
n[7][0]= (x+1)%dim;
n[7][1]= (y+1)%dim;
return n;
}
ArrayList neighboursType(int x, int y, int t) { //return nearby x, y positions with type t
int[][] n= neighbours(x, y);
ArrayList arr= new ArrayList();
for (int i= 0; i<8; i++) {
if (objects[n[i][0]][n[i][1]][t]!=nil) {
arr.add(n[i]);
}
}
return arr;
}
ArrayList neighboursEmpty(int x, int y) { //return nerby x, y positions without objects
int[][] n= neighbours(x, y);
ArrayList arr= new ArrayList();
for (int i= 0; i<8; i++) {
if ((objects[n[i][0]][n[i][1]][0]==nil) && (objects[n[i][0]][n[i][1]][1]==nil) && (objects[n[i][0]][n[i][1]][2]==nil)) {
arr.add(n[i]);
}
}
return arr;
}
ArrayList allType(int t) { //return all x, y positions with type t
ArrayList arr= new ArrayList();
for (int x= 0; x<dim; x++) {
for (int y= 0; y<dim; y++) {
if (objects[x][y][t]!=nil) {
int[] a= {
x, y
};
arr.add(a);
}
}
}
return arr;
}
ArrayList allEmpty() { //return all x, y positions without objects
ArrayList arr= new ArrayList();
for (int x= 0; x<dim; x++) {
for (int y= 0; y<dim; y++) {
if ((objects[x][y][0]==nil) && (objects[x][y][1]==nil) && (objects[x][y][2]==nil)) {
int[] a= {
x, y
};
arr.add(a);
}
}
}
return arr;
}
boolean threePlants(int x, int y) { //optimised search for 3 neighbour plants
int i= 0, cnt= 0;
int[][] n= neighbours(x, y);
while ((cnt<3) && (i<8)) {
if (objects[n[i][0]][n[i][1]][0]!=nil) {
cnt++;
}
i++;
}
return cnt==3;
}
//--main loop
void draw() {
background(255);
//--rule #1: for every empty cell
empty= allEmpty();
for (int i= empty.size()-1; i>=0; i--) {
int[] a= (int[])empty.get(i);
if (threePlants(a[0], a[1])) {
plants.add(a); //collect empty positions with >=3 neighbour plants
}
}
for (int i= plants.size()-1; i>=0; i--) {
int[] a= (int[])plants.get(i);
objects[a[0]][a[1]][0]= plantEnergyInit; //grow new plant
}
plants.clear();
//--rule #2: for every herbivore
herbivores= myScramble(allType(1));
for (int i= herbivores.size()-1; i>=0; i--) {
int[] a= (int[])herbivores.get(i);
objects[a[0]][a[1]][1]= objects[a[0]][a[1]][1]-herbivoreEnergyLoss;
if (objects[a[0]][a[1]][1]<=0) {
objects[a[0]][a[1]][1]= nil; //dies if no energy left
} else {
int[] f= myChoose(neighboursType(a[0], a[1], 0)); //find food (plant)
if ((f[0]!=nil) && (f[1]!=nil)) { //move to and eat nearby food
objects[f[0]][f[1]][1]= objects[a[0]][a[1]][1]+objects[f[0]][f[1]][0];
objects[f[0]][f[1]][0]= nil;
objects[a[0]][a[1]][1]= nil;
if (objects[f[0]][f[1]][1]>herbivoreEnergyBaby) { //enough energy spawns baby
objects[a[0]][a[1]][1]= herbivoreEnergyInit;
}
} else {
int[] m= myChoose(neighboursEmpty(a[0], a[1])); //find position to move to
if ((m[0]!=nil) && (m[1]!=nil)) { //move to random empty space
objects[m[0]][m[1]][1]= objects[a[0]][a[1]][1];
objects[a[0]][a[1]][1]= nil;
}
}
}
}
//--rule #3: for every carnivore
carnivores= myScramble(allType(2));
for (int i= carnivores.size()-1; i>=0; i--) {
int[] a= (int[])carnivores.get(i);
objects[a[0]][a[1]][2]= objects[a[0]][a[1]][2]-carnivoreEnergyLoss;
if (objects[a[0]][a[1]][2]<=0) {
objects[a[0]][a[1]][2]= nil; //dies if no energy left
} else {
int[] f= myChoose(neighboursType(a[0], a[1], 1)); //find food (herbivore)
if ((f[0]!=nil) && (f[1]!=nil)) { //move to and eat nearby food
objects[f[0]][f[1]][2]= objects[a[0]][a[1]][2]+objects[f[0]][f[1]][1];
objects[f[0]][f[1]][1]= nil;
objects[a[0]][a[1]][2]= nil;
if (objects[f[0]][f[1]][2]>carnivoreEnergyBaby) { //enough energy spawns baby
objects[a[0]][a[1]][2]= carnivoreEnergyInit;
}
} else {
int[] m= myChoose(neighboursEmpty(a[0], a[1])); //find empty position to move to
if ((m[0]!=nil) && (m[1]!=nil)) { //move to random empty space
objects[m[0]][m[1]][2]= objects[a[0]][a[1]][2];
objects[a[0]][a[1]][2]= nil;
} else {
m= myChoose(neighboursType(a[0], a[1], 0)); //find plant position to move to
if ((m[0]!=nil) && (m[1]!=nil)) { //might be a newborn there
objects[m[0]][m[1]][2]= objects[a[0]][a[1]][2];
objects[a[0]][a[1]][2]= nil;
}
}
}
}
}
//--render
for (int x= 0; x<dim; x++) {
for (int y= 0; y<dim; y++) {
if (objects[x][y][2]!=nil) {
fill(#0000FF); //carnivore
rect(x*sizex, y*sizey, rectWidth, rectHeight);
} else if (objects[x][y][1]!=nil) {
fill(#FF0000); //herbivore
rect(x*sizex, y*sizey, rectWidth, rectHeight);
} else if (objects[x][y][0]!=nil) {
fill(#00FF00); //plant
rect(x*sizex, y*sizey, rectWidth, rectHeight);
}
}
}
//println("generation: "+frameCount);
fill(#000000);
text("fps:"+frameRate, 8, 18);
}
int myMod(int val1, int val2) { //wrap around negative values
if (val1<0) {
return (val2+val1)%val2;
} else {
return val1%val2;
}
}
ArrayList myScramble(ArrayList arr) { //random reorder
ArrayList arrOut= new ArrayList();
int m= arr.size();
int i;
while (m>0) {
i= int(random(m));
arrOut.add(arr.get(i));
arr.remove(i);
m= arr.size();
}
return arrOut;
}
int[] myChoose(ArrayList arr) {
int m= arr.size();
if (m>0) {
int i= int(random(m));
return (int[])arr.get(i);
}
int[] q= {
nil, nil
};
return q;
}