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 wwidth= 200;                  //world dimension
final int wheight= 200;                 //world dimension
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
final color backgroundColor = color(255, 255, 255);
final color plantColor = color(0, 255, 0);
final color herbivoreColor = color(255, 0, 0);
final color carnivoreColor = color(0, 0, 255);

//--variables
int[][][] objects;
PImage img;
ArrayList empty= new ArrayList();
ArrayList plants= new ArrayList();
ArrayList herbivores= new ArrayList();
ArrayList carnivores= new ArrayList();

//--setup
void settings() {
  size(screenWidth, screenHeight, P2D);
}
void setup() {
  frameRate(fps);

  imageMode(CENTER);
  img = createImage(wwidth, wheight, RGB);
  img.loadPixels();

  objects= new int[wwidth][wheight][3];
  for (int x= 0; x<wwidth; x++) {
    for (int y= 0; y<wheight; y++) {
      float q= random(1.0);
      objects[x][y][0]= 0;
      objects[x][y][1]= 0;
      objects[x][y][2]= 0;
      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, wwidth);
  n[0][1]= myMod(y-1, wheight);
  n[1][0]= x;
  n[1][1]= myMod(y-1, wheight);
  n[2][0]= (x+1)%wwidth;
  n[2][1]= myMod(y-1, wheight);
  n[3][0]= myMod(x-1, wwidth);
  n[3][1]= y;
  n[4][0]= (x+1)%wwidth;
  n[4][1]= y;
  n[5][0]= myMod(x-1, wwidth);
  n[5][1]= (y+1)%wheight;
  n[6][0]= x;
  n[6][1]= (y+1)%wheight;
  n[7][0]= (x+1)%wwidth;
  n[7][1]= (y+1)%wheight;
  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]>0) {
      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++) {
    int[] energies= objects[n[i][0]][n[i][1]];
    if ((energies[0]==0) && (energies[1]==0) && (energies[2]==0)) {
      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<wwidth; x++) {
    for (int y= 0; y<wheight; y++) {
      if (objects[x][y][t]>0) {
        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<wwidth; x++) {
    for (int y= 0; y<wheight; y++) {
      int[] energies= objects[x][y];
      if ((energies[0]==0) && (energies[1]==0) && (energies[2]==0)) {
        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]>0) {
      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]= max(objects[a[0]][a[1]][1]-herbivoreEnergyLoss, 0);
    if (objects[a[0]][a[1]][1]>0) {
      int[] f= myChoose(neighboursType(a[0], a[1], 0));  //find food (plant)
      if (f.length>0) {  //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]= 0;
        objects[a[0]][a[1]][1]= 0;
        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.length>0) {  //move to random empty space
          objects[m[0]][m[1]][1]= objects[a[0]][a[1]][1];
          objects[a[0]][a[1]][1]= 0;
        }
      }
    }
  }

  //--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]= max(objects[a[0]][a[1]][2]-carnivoreEnergyLoss, 0);
    if (objects[a[0]][a[1]][2]>0) {
      int[] f= myChoose(neighboursType(a[0], a[1], 1));  //find food (herbivore)
      if (f.length>0) {  //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]= 0;
        objects[a[0]][a[1]][2]= 0;
        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.length>0) {  //move to random empty space
          objects[m[0]][m[1]][2]= objects[a[0]][a[1]][2];
          objects[a[0]][a[1]][2]= 0;
        } else {
          m= myChoose(neighboursType(a[0], a[1], 0));  //find plant position to move to
          if (m.length>0) {  //might be a newborn there
            objects[m[0]][m[1]][2]= objects[a[0]][a[1]][2];
            objects[a[0]][a[1]][2]= 0;
          }
        }
      }
    }
  }

  //--render
  for (int x= 0; x<wwidth; x++) {
    for (int y= 0; y<wheight; y++) {
      int i= x + y * wwidth;
      img.pixels[i]= backgroundColor;
      if (objects[x][y][2]>0) {
        img.pixels[i]= carnivoreColor;
      } else if (objects[x][y][1]>0) {
        img.pixels[i]= herbivoreColor;
      } else if (objects[x][y][0]>0) {
        img.pixels[i]= plantColor;
      }
    }
  }
  img.updatePixels();
  float scale = min(width / float(wwidth), height / float(wheight));
  image(img, width * 0.5, height * 0.5, scale * wwidth, scale * wheight);

  //println("generation: "+frameCount);
  fill(#000000);
  text("fps:"+nf(frameRate, 0, 1), 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 = {};
  return q;
}