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
//ported 2020-07-27 to p5js
/*
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
let fps = 20; //framerate
let dim = 100; //dimensions x*x
let plantInitChance = 0.2; //percentage chance cell start as plant
let herbivoreInitChance = 0.15; //percentage chance cell start as herbivore
let carnivoreInitChance = 0.05; //percentage chance cell start as carnivore
let plantEnergyInit = 25; //initial energy
let herbivoreEnergyInit = 25; //initial energy
let carnivoreEnergyInit = 25; //initial energy
let herbivoreEnergyLoss = 10; //energy decrease per generation
let carnivoreEnergyLoss = 10; //energy decrease per generation
let herbivoreEnergyBaby = 250; //threshold for spawning baby
let carnivoreEnergyBaby = 250; //threshold for spawning baby
let objects = new Array(dim); //3d array
//--setup
function setup() {
let div = select('#sketch');
let cnv = createCanvas(div.width, div.height);
cnv.parent('sketch');
createInterface(div.width, div.position().x, div.position().y + height + 10);
initWorld();
}
function initWorld() {
frameRate(fps);
for (let x = 0; x < dim; x++) {
objects[x] = new Array(dim);
for (let y = 0; y < dim; y++) {
let q = random(1.0);
objects[x][y] = [];
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;
}
}
}
}
//--gui
function createInterface(width, x, y) {
let button = createButton('reset').position(x, y).size(50);
button.mousePressed(() => {
frameCount = 0;
fps = floor(fpsInput.value());
fpsInput.value(fps);
dim = constrain(floor(dimInput.value()), 1, 255);
dimInput.value(dim);
plantInitChance = plantInitChanceSlider.value();
herbivoreInitChance = herbivoreInitChanceSlider.value();
carnivoreInitChance = carnivoreInitChanceSlider.value();
plantEnergyInit = plantEnergyInitSlider.value();
herbivoreInitChance = herbivoreInitChanceSlider.value();
herbivoreEnergyInit = herbivoreEnergyInitSlider.value();
carnivoreInitChance = carnivoreInitChanceSlider.value();
carnivoreEnergyInit = carnivoreEnergyInitSlider.value();
herbivoreEnergyLoss = herbivoreEnergyLossSlider.value();
herbivoreEnergyBaby = herbivoreEnergyBabySlider.value();
carnivoreEnergyLoss = carnivoreEnergyLossSlider.value();
carnivoreEnergyBaby = carnivoreEnergyBabySlider.value();
initWorld();
})
createSpan('FPS:').position(x + button.width + 10, y + 4);
let fpsInput = createInput(fps, "number").position(x + 50 + 40, y).size(40);
createSpan('Dim:').position(x + button.width + 100, y + 4);
let dimInput = createInput(dim, "number").position(x + 50 + 128, y).size(40);
createSpan('plantInitChance:').position(x, y + 35);
let plantInitChanceSlider = createSlider(0, 1, plantInitChance, 0.01)
.position(x + 120, y + 35).size(100);
createSpan('plantEnergyInit:').position(width * 0.5 + x, y + 35);
let plantEnergyInitSlider = createSlider(0, 100, plantEnergyInit, 1)
.position(width * 0.5 + x + 120, y + 35).size(100);
createSpan('herbivoreInitChance:').position(x, y + 35 + 25);
let herbivoreInitChanceSlider = createSlider(0, 1, herbivoreInitChance, 0.01)
.position(x + 120, y + 35 + 25).size(100);
createSpan('herbivoreEnergyInit:').position(width * 0.5 + x, y + 35 + 25);
let herbivoreEnergyInitSlider = createSlider(0, 100, herbivoreEnergyInit, 1)
.position(width * 0.5 + x + 120, y + 35 + 25).size(100);
createSpan('carnivoreInitChance:').position(x, y + 35 + 25 + 25);
let carnivoreInitChanceSlider = createSlider(0, 1, carnivoreInitChance, 0.05)
.position(x + 120, y + 35 + 25 + 25).size(100);
createSpan('carnivoreEnergyInit:').position(width * 0.5 + x, y + 35 + 25 + 25);
let carnivoreEnergyInitSlider = createSlider(0, 100, carnivoreEnergyInit, 1)
.position(width * 0.5 + x + 120, y + 35 + 25 + 25).size(100);
createSpan('herbivoreEnergyLoss:').position(x, y + 35 + 25 + 25 + 25);
let herbivoreEnergyLossSlider = createSlider(0, 100, herbivoreEnergyLoss, 1)
.position(x + 120, y + 35 + 25 + 25 + 25).size(100);
createSpan('herbivoreEnergyBaby:').position(width * 0.5 + x, y + 35 + 25 + 25 + 25);
let herbivoreEnergyBabySlider = createSlider(0, 500, herbivoreEnergyBaby, 1)
.position(width * 0.5 + x + 120, y + 35 + 25 + 25 + 25).size(100);
createSpan('carnivoreEnergyLoss:').position(x, y + 35 + 25 + 25 + 25 + 25);
let carnivoreEnergyLossSlider = createSlider(0, 100, carnivoreEnergyLoss, 1)
.position(x + 120, y + 35 + 25 + 25 + 25 + 25).size(100);
createSpan('carnivoreEnergyBaby:').position(width * 0.5 + x, y + 35 + 25 + 25 + 25 + 25);
let carnivoreEnergyBabySlider = createSlider(0, 100, carnivoreEnergyBaby, 1)
.position(width * 0.5 + x + 120, y + 35 + 25 + 25 + 25 + 25).size(100);
}
function neighbours(x, y) { //return nearby x, y positions
let n = new Array(8);
for (let i = 0; i < 8; i++) {
n[i] = new Uint8Array(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;
}
function neighboursType(x, y, t) { //return nearby x, y positions with type t
let n = neighbours(x, y);
let arr = new Array();
for (let i = 0; i < 8; i++) {
if (objects[n[i][0]][n[i][1]][t] != undefined) {
arr.push(n[i]);
}
}
return arr;
}
function neighboursEmpty(x, y) { //return nerby x, y positions without objects
let n = neighbours(x, y);
let arr = new Array();
for (let i = 0; i < 8; i++) {
if ((objects[n[i][0]][n[i][1]][0] == undefined) && (objects[n[i][0]][n[i][1]][1] == undefined) && (objects[n[i][0]][n[i][1]][2] == undefined)) {
arr.push(n[i]);
}
}
return arr;
}
function allType(t) { //return all x, y positions with type t
let arr = new Array();
for (let x = 0; x < dim; x++) {
for (let y = 0; y < dim; y++) {
if (objects[x][y][t] != undefined) {
arr.push([x, y]);
}
}
}
return arr;
}
function allEmpty() { //return all x, y positions without objects
let arr = new Array();
for (let x = 0; x < dim; x++) {
for (let y = 0; y < dim; y++) {
if ((objects[x][y][0] == undefined) && (objects[x][y][1] == undefined) && (objects[x][y][2] == undefined)) {
arr.push([x, y]);
}
}
}
return arr;
}
function threePlants(x, y) { //optimised search for 3 neighbour plants
let i = 0, cnt = 0;
let n = neighbours(x, y);
while ((cnt < 3) && (i < 8)) {
if (objects[n[i][0]][n[i][1]][0] != undefined) {
cnt++;
}
i++;
}
return cnt == 3;
}
//--main loop
function draw() {
noStroke();
background(255);
//--rule #1: for every empty cell
let empty = allEmpty();
let plants = new Array();
for (let i = empty.length - 1; i >= 0; i--) {
let a = empty[i];
if (threePlants(a[0], a[1])) {
plants.push(a); //collect empty positions with >=3 neighbour plants
}
}
for (let i = plants.length - 1; i >= 0; i--) {
let a = plants[i];
objects[a[0]][a[1]][0] = plantEnergyInit; //grow new plant
}
//--rule #2: for every herbivore
let herbivores = myScramble(allType(1));
for (let i = herbivores.length - 1; i >= 0; i--) {
let a = herbivores[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] = undefined; //dies if no energy left
}
else {
let f = myChoose(neighboursType(a[0], a[1], 0)); //find food (plant)
if ((f[0] != undefined) && (f[1] != undefined)) { //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] = undefined;
objects[a[0]][a[1]][1] = undefined;
if (objects[f[0]][f[1]][1] > herbivoreEnergyBaby) { //enough energy spawns baby
objects[a[0]][a[1]][1] = herbivoreEnergyInit;
}
}
else {
let m = myChoose(neighboursEmpty(a[0], a[1])); //find position to move to
if ((m[0] != undefined) && (m[1] != undefined)) { //move to random empty space
objects[m[0]][m[1]][1] = objects[a[0]][a[1]][1];
objects[a[0]][a[1]][1] = undefined;
}
}
}
}
//--rule #3: for every carnivore
let carnivores = myScramble(allType(2));
for (let i = carnivores.length - 1; i >= 0; i--) {
let a = carnivores[i];
objects[a[0]][a[1]][2] = objects[a[0]][a[1]][2] - carnivoreEnergyLoss;
if (objects[a[0]][a[1]][2] <= 0) { //dies if no energy left
objects[a[0]][a[1]][2] = undefined;
}
else {
let f = myChoose(neighboursType(a[0], a[1], 1)); //find food (herbivore)
if ((f[0] != undefined) && (f[1] != undefined)) { //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] = undefined;
objects[a[0]][a[1]][2] = undefined;
if (objects[f[0]][f[1]][2] > carnivoreEnergyBaby) { //enough energy spawns baby
objects[a[0]][a[1]][2] = carnivoreEnergyInit;
}
}
else {
let m = myChoose(neighboursEmpty(a[0], a[1])); //find empty position to move to
if ((m[0] != undefined) && (m[1] != undefined)) { //move to random empty space
objects[m[0]][m[1]][2] = objects[a[0]][a[1]][2];
objects[a[0]][a[1]][2] = undefined;
}
else {
m = myChoose(neighboursType(a[0], a[1], 0)); //find plant position to move to
if ((m[0] != undefined) && (m[1] != undefined)) { //might be a newborn there
objects[m[0]][m[1]][2] = objects[a[0]][a[1]][2];
objects[a[0]][a[1]][2] = undefined;
}
}
}
}
}
//--render
let sizex = width / dim;
let sizey = height / dim;
let rectWidth = sizex * 0.75;
let rectHeight = sizey * 0.75;
translate(sizex * 0.125, sizey * 0.125);
for (let z = 0; z < 3; z++) {
fill((z == 1) * 255, (z == 0) * 255, (z == 2) * 255);
for (let x = 0; x < dim; x++) {
for (let y = 0; y < dim; y++) {
if (objects[x][y][z] != undefined) {
rect(x * sizex, y * sizey, rectWidth, rectHeight);
}
}
}
}
fill(0);
text('FPS:' + round(frameRate()), 8, 18);
text('Generation:' + frameCount, 8, 38);
}
function myMod(val1, val2) { //wrap around negative values
if (val1 < 0) {
return (val2 + val1) % val2;
}
return val1 % val2;
}
function myScramble(arr) { //random reorder
let arrOut = new Array();
let m = arr.length;
let i;
while (m > 0) {
i = int(random(m));
arrOut.push(arr[i]);
arr.splice(i, 1);
m = arr.length;
}
return arrOut;
}
function myChoose(arr) {
let m = arr.length;
if (m > 0) {
let i = int(random(m));
return arr[i];
}
return [undefined, undefined];
}