eco
See /f0blog/eco/
//ecosystem - redFrik 2010
//as described in Flake - "The Computational Beauty of Nature" (page 191)
//optimised version using Image. press 'space' to reset
/*
// * 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
var fps= 60, //framerate
wrapBorders= true, //wrap around edges or not
width= 640, //window width
height= 480, //window height
wwidth= 200, //world dimension
wheight= 200, //world dimension
plantInitChance= 0.2, //percentage chance of cell start as plant
herbivoreInitChance= 0.15, //percentage chance of cell start as herbivore
carnivoreInitChance= 0.05, //percentage chance of cell start as carnivore
plantEnergyInit= 25, //initial plant energy
herbivoreEnergyInit= 25, //initial herbivore energy
carnivoreEnergyInit= 25, //initial carnivoew energy
herbivoreEnergyLoss= 10, //energy decrease per generation
carnivoreEnergyLoss= 10, //energy decrease per generation
herbivoreEnergyBaby= 250, //threshold for spawning baby
carnivoreEnergyBaby= 250, //threshold for spawning baby
backgroundColor= Color.white,
plantColor= Color.green,
herbivoreColor= Color.red,
carnivoreColor= Color.blue;
//--world
var cnt, dimdim= wwidth*wheight;
var win= Window("ecosystem", Rect(300, 300, width, height), false);
var img= Image.color(wwidth, wheight).interpolation_(\fast);
var scale= min(width/wwidth, height/wheight)*0.5;
var rect= Rect.aboutPoint(Point(width*0.5, height*0.5), wwidth*scale, wheight*scale);
var objects;
//--init;
var init= {
var weights= [
1-plantInitChance-herbivoreInitChance-carnivoreInitChance,
plantInitChance,
herbivoreInitChance,
carnivoreInitChance
].normalizeSum;
objects= Int16Array.newClear(dimdim*3);
cnt= 0;
dimdim.do{|i|
[
{},
{objects[i]= plantEnergyInit},
{objects[i+dimdim]= herbivoreEnergyInit},
{objects[i+dimdim+dimdim]= carnivoreEnergyInit}
].wchoose(weights).value;
}
};
var neighbours= Array.fill(dimdim, {|i|
var x= i%wwidth;
var y= i.div(wwidth);
var x0, x2, y0, y2;
if(wrapBorders, {
//--unbounded version - wraps around borders
x0= x-1%wwidth;
x2= x+1%wwidth;
y0= y-1%wheight*wwidth;
y2= y+1%wheight*wwidth;
[
x0+y0, x+y0, x2+y0,
x0+(y*wwidth), x2+(y*wwidth),
x0+y2, x+y2, x2+y2
];
}, {
//--bounded version - respects borders
x0= x-1;
x2= x+1;
y0= y-1;
y2= y+1;
[
[x0, y0], [x, y0], [x2, y0],
[x0, y], [x2, y],
[x0, y2], [x, y2], [x2, y2]
].collect{|arr|
if(arr[0]>=0 and:{
arr[0]<wwidth and:{
arr[1]>=0 and:{
arr[1]<wheight}}}, {
arr[0]+(arr[1]*wwidth);
}, {
-1;
});
};
});
}).flat.as(Int32Array);
//--functions
var neighboursType= {|i, t| //return nearby base positions for type t
var o= dimdim*t;
var ni= i%dimdim*8;
var list= List.new;
var index= neighbours[ni];
if(index>=0 and:{objects[o+index]>0}, {list.add(index)});
index= neighbours[ni+1];
if(index>=0 and:{objects[o+index]>0}, {list.add(index)});
index= neighbours[ni+2];
if(index>=0 and:{objects[o+index]>0}, {list.add(index)});
index= neighbours[ni+3];
if(index>=0 and:{objects[o+index]>0}, {list.add(index)});
index= neighbours[ni+4];
if(index>=0 and:{objects[o+index]>0}, {list.add(index)});
index= neighbours[ni+5];
if(index>=0 and:{objects[o+index]>0}, {list.add(index)});
index= neighbours[ni+6];
if(index>=0 and:{objects[o+index]>0}, {list.add(index)});
index= neighbours[ni+7];
if(index>=0 and:{objects[o+index]>0}, {list.add(index)});
list;
};
var neighboursEmpty= {|i| //return nearby base positions without objects
var ni= i%dimdim*8;
var list= List.new;
var f= {|index|
if(index>=0 and:{
objects[index]==0 and:{
objects[index+dimdim]==0 and:{
objects[index+dimdim+dimdim]==0}}}, {
list.add(index);
});
};
f.value(neighbours[ni]);
f.value(neighbours[ni+1]);
f.value(neighbours[ni+2]);
f.value(neighbours[ni+3]);
f.value(neighbours[ni+4]);
f.value(neighbours[ni+5]);
f.value(neighbours[ni+6]);
f.value(neighbours[ni+7]);
list;
};
var allType= {|t| //return base positions for type t
var o= dimdim*t;
var list= List.new;
var i= 0;
while({i<dimdim}, {
if(objects[i+o]>0, {
list.add(i);
});
i= i+1;
});
list;
};
var allEmpty= { //return base positions without objects
var list= List.new;
var i= 0;
while({i<dimdim}, {
if(objects[i]==0 and:{
objects[i+dimdim]==0 and:{
objects[i+dimdim+dimdim]==0}}, {
list.add(i);
});
i= i+1;
});
list;
};
var threePlants= {|i| //optimised search for 3 neighbour plants
var j= 0, c= 0, index;
i= i*8;
while({c<3 and:{j<8}}, {
index= neighbours[i+j];
if(index>=0 and:{objects[index]>0}, {
c= c+1;
});
j= j+1;
});
c==3;
};
//--main loop
init.value;
win.drawFunc= {
var herbivores= 0;
var carnivores= 0;
var plants= 0;
var newPlants= List.new;
//--rule #1: for every empty cell
allEmpty.value.do{|i|
if(threePlants.value(i), {
newPlants.add(i); //collect empty positions with >=3 neighbour plants
});
};
newPlants.do{|i|
objects[i]= plantEnergyInit; //grow new plant
};
//--rule#2: for every herbivore
allType.value(1).scramble.do{|i|
var f, m;
i= i+dimdim;
objects[i]= max(objects[i]-herbivoreEnergyLoss, 0);
if(objects[i]>0, {
f= neighboursType.value(i, 0).choose;//find food (plant)
if(f.notNil, { //move to and eat nearby food
objects[f+dimdim]= objects[i]+objects[f];
objects[f]= 0;
objects[i]= 0;
if(objects[f+dimdim]>herbivoreEnergyBaby, {//enough energy spawns baby
objects[i]= herbivoreEnergyInit;
});
}, {
m= neighboursEmpty.value(i).choose;//find position to move to
if(m.notNil, { //move to random empty space
m= m+dimdim;
objects[m]= objects[i];
objects[i]= 0;
});
});
});
};
//--rule#3: for every carnivore
allType.value(2).scramble.do{|i|
var f, m;
i= i+dimdim+dimdim;
objects[i]= max(objects[i]-carnivoreEnergyLoss, 0);
if(objects[i]>0, {
f= neighboursType.value(i, 1).choose;//find food (herbivore)
if(f.notNil, { //move to and eat nearby food
f= f+dimdim;
objects[f+dimdim]= objects[i]+objects[f];
objects[f]= 0;
objects[i]= 0;
if(objects[f+dimdim]>carnivoreEnergyBaby, {//enough energy spawns baby
objects[i]= carnivoreEnergyInit;
});
}, {
m= neighboursEmpty.value(i).choose;//find empty position to move to
if(m.notNil, {
m= m+dimdim+dimdim;
objects[m]= objects[i];
objects[i]= 0;
}, {
m= neighboursType.value(i, 0).choose;//find plant position to move to
if(m.notNil, { //might be a newborn there
m= m+dimdim+dimdim;
objects[m]= objects[i];
objects[i]= 0;
});
});
});
});
};
//--render
img.fill(backgroundColor);
dimdim.do{|i|
var x= i%wwidth;
var y= i.div(wwidth);
if(objects[i+dimdim+dimdim]>0, {
img.setColor(carnivoreColor, x, y);
carnivores= carnivores+1;
}, {
if(objects[i+dimdim]>0, {
img.setColor(herbivoreColor, x, y);
herbivores= herbivores+1;
}, {
if(objects[i]>0, {
img.setColor(plantColor, x, y);
plants= plants+1;
});
});
});
};
Pen.drawImage(rect, img);
"generation: % plants: % herbivores: % carnivores: %".format(
cnt,
plants,
herbivores,
carnivores,
).postln;
cnt= cnt+1;
};
CmdPeriod.doOnce({win.close});
win.background = Color.white;
win.front;
win.onClose= {img.free};
win.view.keyDownAction= {|view, key|
if(key==Char.space, {
init.value;
});
};
Routine({
var nextTime;
while({win.isClosed.not}, {
nextTime= Main.elapsedTime+(1/fps);
win.refresh;
(nextTime-Main.elapsedTime).max(0.001).wait;
});
}).play(AppClock);
)