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);
)