Genetic algorithm driven ecosystem

Hi everyone, i'd like to share with you the project i'm working on. First of all i want to give credit to Daniel Shiffman form "the coding train", i started learning Processing just a month ago, and i heavily relied on his amazing work.

This is an ecosystem simulation in which creatures can evolve from generation to generation based on their previous performances. There are 2 main classes of creatures, herbivores (Bloops = black/pink circles) and carnivores (Predators = brown/red triangles). Each species implements steering behavior such as seek and flee based on their own senses.

they carry a dna that initialize their stats like hearing range, max health, and so on. When two creatures mate, the dna is taken from the parents and mixed to generate a child that will carry his parents genes. To avoid evolution to strive just for maximizing stats, every value is inversely proportional to another one (eg. hearing is initialized by gene[0], smell is equal to the maximum possible hearing value - hearing, so if you have incredibly developed hears, you'll end up with almost no sense of smell and vice versa). That way evolution will have to find a good balance between stats over time. After reaching the mating age, every creature can emit hormones to attract possible partners. if the ecosystem collapse (less than 2 creatures left per species) it resets, evaluate every creatures based on age and child number, randomly choose among creatures (higher evaluation = higher chance of being chosen), cross their genes, and generates a new ecosystem populated with the newborn children.

I tried to comment the code as much as possible to make it readable. This is unfinished but still fun to watch. Right click to show/hide the thinking process of the creatures. Click on a creature to show/hide its senses.

World world;

boolean thinkingProcess = true;
int generations;
int lifetime;
int numF;
int numB;
int numP;
float mutationRate;
float bestBloop;
float bestPredator;
int longestGeneration;
int bloopScore;
int predatorScore;

void setup() {
  fullScreen();
  numF = 200;                                         // initialize food count
  numB = 50;                                          // initialize bloops population
  numP = 30;                                          // initialize predators population
  mutationRate = 0.01;                                // set mutationRate
  bestBloop = 0;
  bestPredator = 0;
  lifetime = 0;
  generations = 0;
  longestGeneration = 0;
  bloopScore = 0;
  predatorScore = 0;
  world = new World(numF, numB, numP);
}

void draw() {
  background(255);

  // run simulation until there are at least 2 predators and 2 bloops (if one of the species goes up to 200+ units, something went wrong, so reset ecosystem)
  if ((world.predators.size() > 1) && (world.bloops.size() > 1) && (world.predators.size() < 200) && (world.bloops.size() < 200)) {   
    world.run();
    lifetime++;
  } else { 
    if (world.bloops.size() > world.predators.size()) {     // check which species survived
      bloopScore += 1;
    } else {
      predatorScore += 1;
    }


    for (int i = world.bloops.size()-1; i >= 0; i--) {      // check for surviving bloops
      Bloop b = world.bloops.get(i);
      b.age += b.health;                                    // reward them by adding exceeding health to fitness
      world.bloopsClones.add(b);                            // move them to the clones arraylist
      world.bloops.remove(i);                               // remove them from bloop arraylist
    }
    for (int i = world.predators.size()-1; i >= 0; i--) {   // do the same for predators
      Predator p = world.predators.get(i);
      p.age += p.health;
      world.predatorsClones.add(p);         
      world.predators.remove(i);
    }

    if (lifetime > longestGeneration) longestGeneration = lifetime;

    lifetime = 0;                                                        // create new generation
    generations++;

    world.pFoods.clear();
    world.foods.clear();                                                 //clear old food array    
    for (int i=0; i < numF; i++) {                            
      world.foods.add(new Food(random(0, width), random(0, height)));    //initialize food for the next gen
    }

    world.getBloopsMaxFitness();                                         // calculate bloops fitness
    if (world.getBloopsMaxFitness() > bestBloop) {
      bestBloop = world.getBloopsMaxFitness();
    }
    world.bloopSelection();                                              // select bloops
    world.bloopReproduction();                                           // reproduce bloops

    world.getPredatorsMaxFitness();                                      // calculate predators fitness
    if (world.getPredatorsMaxFitness() > bestPredator) {                
      bestPredator = world.getPredatorsMaxFitness();
    }
    world.predatorSelection();                                           // select predators
    world.predatorsReproduction();                                       // reproduce predators
  }

  fill(0);                                                               // display some stats
  textSize(12);
  text("Generation #: " + (generations), 10, 18);
  text("Lifetime: " + (lifetime), 10, 36);
  text("Living Bloops: " + world.bloops.size(), 10, 54);
  text("Living Predators: " + world.predators.size(), 10, 72);
  text("Oldest Bloop: " + bestBloop, 10, 90);
  text("Oldest Predator: " + bestPredator, 10, 108);
  text("Longest generation: " + longestGeneration, 10, 126);
  text("Bloop score: " + bloopScore, 10, 144);
  text("Predator score: " + predatorScore, 10, 162);
  text("Click on a creature to display senses", width-250, height-33);
  text("Right click to toggle thinking process", width-250, height-15);
}



void mousePressed() {                                     // show/hide creatures thinking process 
  if (mouseButton == RIGHT) {
    thinkingProcess = !thinkingProcess;
  }


  if (mouseButton == LEFT) {
    for (int i = 0; i < world.bloops.size(); i++) {       // show/hide creature senses when you click on it (red circle = hearing, green = smell)
      world.bloops.get(i);
      if (mouseX >  world.bloops.get(i).position.x - world.bloops.get(i).r*2 && mouseX < world.bloops.get(i).position.x + world.bloops.get(i).r*2 && mouseY > world.bloops.get(i).position.y - world.bloops.get(i).r*2 && mouseY < world.bloops.get(i).position.y + world.bloops.get(i).r*2) {
        world.bloops.get(i).showSenses = !world.bloops.get(i).showSenses;
      }
    }
    for (int i = 0; i < world.predators.size(); i++) {
      world.predators.get(i);
      if (mouseX >  world.predators.get(i).position.x - world.predators.get(i).r*4 && mouseX < world.predators.get(i).position.x + world.predators.get(i).r*4 && mouseY > world.predators.get(i).position.y - world.predators.get(i).r*4 && mouseY < world.predators.get(i).position.y + world.predators.get(i).r*4) {
        world.predators.get(i).showSenses = !world.predators.get(i).showSenses;
      }
    }
  }
}

Comments

  • // Bloops are the herbivores creature of the ecosystem, they only eat plants.
    // they are displayed as black/pink circle, depending on the sex
    // they only eat plants, they do not attack other creatures.
    
    
    class Bloop {
    
      BloopDNA dna;
    
      PVector position;
      PVector velocity;
      PVector acceleration;
      PVector target;                               // what is my target
      PVector desired;                              // how to reach it
      ArrayList<PVector> threats;                   // list of enemy under hearing range (used to calculate the best runaway path)
      float r;                                      // dimension of the creature
      float wandertheta;                            // wandering angle
      float maxforce;                               // Maximum steering force
      float maxspeed;                               // Maximum speed
      float health;
      float maxHealth;
      float hearing;
      float smell;
      float age;
      float matingAge;
      float hormonDuration;
      float hormonRange;
      float tempHormonDuration;
      float tempHormonPulse;
      boolean showSenses;                           // show/hide creature senses (red circle = hearing, green circle = smell)
      boolean male;                                 // define creature sex (true = male, false = female)
      boolean mating;                               // define if the creature is sexually receptive 
    
      Bloop(float x, float y, BloopDNA dna_) {
        acceleration = new PVector(0, 0);
        velocity = new PVector(0, 0);
        position = new PVector(x, y);
        target = new PVector(0, 0); 
        desired = new PVector(0, 0);
        threats = new ArrayList<PVector>();
        dna = dna_;
        r = 10;
        wandertheta = 0;
        maxspeed = 2;
        maxforce = 0.2;
        hearing = dna.genes[0];                      // hearing is used to detect the presence of predators and is initialized by gene[0]
        smell = 200 - hearing;                       // inversely proportional to hearing, smell is used to find food
        maxHealth = dna.genes[1];                    // maxHealt is initialized by gene[1], it defines health cap.
        health = maxHealth/2;                        // initial health is 1/2 maxHealth to avoid beeing too slow at the beginning of the simulation
        age = 0;                                     
        matingAge = 150;                             // how much time has to pass before beeing old enough to reproduce
        hormonDuration = dna.genes[2];               // initialized by gene[2], it determines the duration of the hormon pulse
        hormonRange = 200 - hormonDuration;          // inversely proportional to hormonDuration, hormonRange defines "how far" the hormons can be perceived
        tempHormonPulse = r;                         // defines the initial size of the pulse (equal to the creature dimension)
        tempHormonDuration = 0;                      // keep track of pulse duration
        showSenses = false;
        mating = false;
    
        if (random(0, 1) > 0.5) {                    // sex is randomly decided (50%-50%)
          male = true;
        } else {
          male = false;
        }
      }
    
      void run() {
        update();
        borders();    
        wander(); 
        sexChange();    
        display();
        if (age > matingAge && random(0, 1) < 0.005)  mating = true;   // if above mating age, there's a 0.005% chance to become sexually receptive
      }
    
    
      // Method to update position
      void update() {    
    
        r = map(health, 0, 400, 5, 20);           // the healthier, the bigger. set accordingly to DNA genes[1] max possible value
        maxspeed = map(health, 0, 400, 3, 1);     // the bigger, the slower. set accordingly to DNA genes[1] max possible value
    
        velocity.add(acceleration);               // Update velocity    
        velocity.limit(maxspeed);                 // Limit speed
        position.add(velocity);    
        acceleration.mult(0);                     // Reset accelertion to 0 each cycle
        health -= 0.2;                            // health is lost over time
      }
    
      void wander() {
        float wanderR = 10;                       // Radius for our "wander circle"
        float wanderD = 30;                       // Distance for our "wander circle"
        float change = 0.3;
        wandertheta += random(-change, change);   // Randomly change wander theta
    
        if (mating) pulse();
    
        // Now we have to calculate the new position to steer towards on the wander circle
        PVector circlepos = velocity.copy();      // Start with velocity
        circlepos.normalize();                    // Normalize to get heading
        circlepos.mult(wanderD);                  // Multiply by distance
        circlepos.add(position);                  // Make it relative to boid's position
    
        float h = velocity.heading();             // We need to know the heading to offset wandertheta
    
        PVector circleOffSet = new PVector(wanderR*cos(wandertheta+h), wanderR*sin(wandertheta+h));
        target = PVector.add(circlepos, circleOffSet);    
        seek();
    
        // Render wandering circle, targets, senses, etc.
        if (thinkingProcess) drawWanderStuff(position, circlepos, target, wanderR, health, threats);
        if (showSenses) drawBloopSenses(position, hearing, smell);
      }
    
      void applyForce(PVector force) {
        // We could add mass here if we want A = F / M
        acceleration.add(force);
      }
    
      // A method that calculates and applies a steering force towards a target or away from it
      // STEER = DESIRED MINUS VELOCITY
    
      void seek() {  
    
        float newFoodDistance = 1000;                    // initialize the food distance to a vey high value to avoid getting stuck with disappeared targets
        PVector closestFood = new PVector (random(0, 1), random(0, 1));
    
        threats.clear();             // clear the threats arraylist to avoid beeing stuck with older targets.
    
        // we will now check arrays of enemies, foods, and mating partners one by one, the order determines priorities. 
        // order is inverse to priority, first we check the LAST important thing, that way we can override the target with subsequents checks if needed
        // bloops priorities are: flee predators > mate > eat.
    
        ArrayList<Food> foods = world.getFood();                       // get the distance of every plant in the world
        for (int j = foods.size()-1; j >= 0; j--) {                    // since plants will be removed when eaten, we have to check the arraylist backwards to avoid skipping some of them
          PVector foodPosition = foods.get(j).position;
          float foodDistance = PVector.dist(position, foodPosition);
    
          if (foodDistance < newFoodDistance) {                      
            newFoodDistance = foodDistance;
            closestFood = foodPosition;
          }
    
          if (newFoodDistance < smell) {             // we just want to target the closest plant 
            target = closestFood;
          } 
    
          if (foodDistance < r/2) {                  // if we reach a plant         
            foods.remove(j);                         // remove it from the world
            health += 50;                            // enjoy the meal
          }
    
          if (health > maxHealth) {                  // avoid to exceed max health when overeating
            health = maxHealth;
          }
        }
    
        if (mating) {                                        // if we are not sexually receptive we do not bother to look for partners.
          ArrayList<Bloop> bloops = world.getBloops();       // get distance of other bloops.
          for (int i = 0; i < bloops.size(); i++) {
            PVector matingBloopPosition = bloops.get(i).position;
            float matingBloopDistance = PVector.dist(position, matingBloopPosition);
    
            // if we are in range to smell hormons AND the possible partner sex is different form our sex AND partner is sexually receptive too:
            if ((matingBloopDistance - bloops.get(i).hormonRange/2) < smell && male != bloops.get(i).male && bloops.get(i).mating == true) {
              target = matingBloopPosition;          // target partner
    
              if (matingBloopDistance < r) {         // if we reach the partner
                Bloop partner = bloops.get(i);
    
                BloopDNA myGenes = dna;                       // take our dna
                BloopDNA partnerGenes = partner.getDNA();     // take partner dna
    
                BloopDNA child = myGenes.crossover(partnerGenes);   // generate a new dna by crossing parents dna
                child.mutate(mutationRate);                         // chance of random mutation
    
                bloops.add(new Bloop(position.x, position.y, child));    // add a new bloop at he position of the parent and with our freshmade dna
                age += 500;                                              // having a child is hard, peoples age faster...
                bloops.get(i).age += 500;                                // jokes aside, since fitness is based on the age, we reward parents for their contribution to society.
                mating = false;                                          // reset mating state for both parents after mating.
                bloops.get(i).mating = false;
              }
            }
          }
        } 
    
        ArrayList<Predator> predators = world.getPredators();                   //cycle through predators
        for (int i = predators.size()-1; i >= 0; i--) {
          PVector predatorPosition = predators.get(i).position;
          float predatorDistance = PVector.dist(position, predatorPosition);    // get their distance                        
    
          if (predatorDistance < hearing) {                            // if a predator is under hearing range
            threats.add(predatorPosition);                             // add it to the list of threats
          }
    
          if (predatorDistance < r/2) {                                // if a predator reaches us  
            health -= 20;                                              // well... youcan easily guess the consequences :D
          }
        }
    
        if (threats.size() > 0) {                                      // if there's at leat a threat
          target = threats.get(0);                                     // target it
          desired = PVector.sub(position, target);                     // desired is a vector pointing AWAY from the first threat (from target to position)
    
          if (threats.size() > 1) {                                    // if there's more than a single threat
            for (int i = 1; i < threats.size(); i++) {                 // we cycle through all of them
              PVector tempDes = PVector.sub(position, threats.get(i));
              desired = desired.add(tempDes);                          // and we add them one by one to our desired runaway vector so we can avoid getting trapped
            }
          }
        } else {
          desired = PVector.sub(target, position);                     // if there's no threat, desired is a vector pointing TO the target (from position to target)
        }
    
        // after checking through every arraylist, we finally have our target based on our priority list
        desired.normalize();                              // Normalize desired and scale to maximum speed
        desired.mult(maxspeed);    
        PVector steer = PVector.sub(desired, velocity);   // Steering = Desired minus Velocity
        steer.limit(maxforce);                            // Limit to maximum steering force
        applyForce(steer);
      }
    
    
    
    // if there's less than 1 male for every 2 female or vice versa there's a small chance of a spontaneous sex change
      void sexChange() {                                        
        int malesNum = world.getBloopSex();                     
        int femalesNum = world.bloops.size() - malesNum;
    
        if (male == false && malesNum < femalesNum/2 || male == true && femalesNum < malesNum/2) {
          if (random(0, 1) < 0.0005) {
            male = !male;
          }
        }
      }
    
    // when sexually receptive we emit hormons to attract potential partners
      void pulse() {
    
        if (tempHormonDuration < hormonDuration) {
          ellipseMode(CENTER);
          if (male == true) stroke(0);                 // different hormon color based on sex
          else              stroke(255, 125, 255);
          noFill();
          ellipse(position.x, position.y, tempHormonPulse, tempHormonPulse);  // emit from actual location, range id defined by hormonRange
        }
    
        tempHormonDuration += 1;
        tempHormonPulse += 3;    
    
        if (tempHormonPulse > hormonRange) {      // if we exceed hormonRange we reset it to our current size
          tempHormonPulse = r;
        }
    
        if (tempHormonDuration == hormonDuration) {    // after reaching max hormonDuration, we reset everithing.
          tempHormonDuration = 0;
          tempHormonPulse = r;
          mating = false;
        }
      }
    
    
    
    
    
      void display() {
    
        // Draw a circle rotated in the direction of velocity
        float theta = velocity.heading() + radians(90);
        stroke(0, health+10);
    
        if (age < matingAge) {                 // baies have different color
          fill(0, 125, 255, health+10);
        } else {
          if (male == true) {                  // after reaching the mating age, the appropriate sex is displayed
            fill(0, health+10);
          } else {
            fill(255, 125, 255, health+10);
          }
        }
        pushMatrix();
        translate(position.x, position.y);
        rotate(theta);
        ellipseMode(CENTER);
        ellipse(0, 0, r, r);    
        popMatrix();
      }
    
      boolean dead() {               // defines death condition
        if (health < 0.0) {
          return true;
        } else {
          return false;
        }
      }
    
    
      // Wraparound
      void borders() {
        if (position.x < -r) position.x = width+r;
        if (position.y < -r) position.y = height+r;
        if (position.x > width+r) position.x = -r;
        if (position.y > height+r) position.y = -r;
      }
    
      BloopDNA getDNA() {
        return dna;
      }
    }
    
    // a method to display bloop senses range (red = hearing, green = smell)
    void drawBloopSenses(PVector position, float hearing, float smell) {
      noFill();
      stroke(175, 175, 0);
      ellipseMode(CENTER);
      ellipse(position.x, position.y, smell*2, smell*2);
      stroke(255, 0, 0);
      ellipse(position.x, position.y, hearing*2, hearing*2);
    }
    
    // A method just to draw the circle associated with wandering
    void drawWanderStuff(PVector location, PVector circle, PVector target, float rad, float health, ArrayList threats) {
      stroke(0, health);
      noFill();
      ellipseMode(CENTER);
      ellipse(circle.x, circle.y, rad*2, rad*2);
      ellipse(target.x, target.y, 4, 4);
      line(location.x, location.y, circle.x, circle.y);
      line(circle.x, circle.y, target.x, target.y);
      if (threats.size() > 1) {
        for (int i = 1; i < threats.size(); i++) {
          PVector threatPos = (PVector) threats.get(i);
          line(location.x, location.y, threatPos.x, threatPos.y);
        }
      }
    }
    
  • edited July 2017
          // DNA is an array of genes
          // genes can be a vector, a float, an itneger and so on (in our case it is an integer array so we can only store integers)
          // each gene initialize a specific variable
    
        class BloopDNA {
    
          int[] genes;
    
          BloopDNA() {
            genes = new int[3];        
            genes[0] = int(random(30, 200));    // genes 0 initialize hearing that will influence smell
            genes[1] = int(random(100, 400));   // genes 1 inizialize bloops maxHealth that will influences maxSpeed and r
            genes[2] = int(random(30, 200));    // genes 2 initialize hormonDuration that will influence hormonRange
          }
    
          BloopDNA(int[] newGenes) {            // second contructor creates instance based on an existing array
            genes = newGenes;
          }
    
          // Crossover creates a new DNA sequence from two partners  
          BloopDNA crossover(BloopDNA partner) {
            int [] child = new int[genes.length];
    
            int crossover = int(random(genes.length));            // Pick a random midpoint among genes
    
            for (int i = 0; i < genes.length; i++) {              // Take "half" from one parent and "half" from the other one
              if (i > crossover) child[i] = genes[i];
              else               child[i] = partner.genes[i];
            }    
    
            BloopDNA newgenes = new BloopDNA(child);              // return new made DNA
            return newgenes;
          }
    
    
          // based on mutation rate, pick a new random float  
          void mutate(float m) {   
            m = mutationRate;
    
            if (random(1) < m) {
              genes[0] = int(random(30, 200));
            }     
            if (random(1) < m) {
              genes[1] = int(random(100, 400));
            }
            if (random(1) < m) {
              genes[2] = int(random(30, 200));
            }
          }
        }
    
  • // these are the carnivores of this world.
    // they are displayed as red/brown triangles, depending on sex.
    // they hunt for bloops all day long to get meat.
    
    // refer to the bloop class for details on variables and functions.
    
    class Predator {
    
      PredatorDNA dna;
    
      PVector position;
      PVector velocity;
      PVector acceleration;
      PVector target;
      float r;
      float predatorTheta;
      float maxforce;    
      float maxspeed;   
      float health;
      float maxHealth;
      float hearing;
      float smell;
      float age;
      float matingAge;
      float hormonDuration;
      float hormonRange;
      float tempHormonDuration;
      float tempHormonPulse;
      boolean showSenses;
      boolean male;
      boolean mating;
    
      Predator(float x, float y, PredatorDNA dna_) {
        acceleration = new PVector(0, 0);
        velocity = new PVector(0, 0);
        position = new PVector(x, y);
        target = new PVector (0, 0);
        dna = dna_;
        r = 5;   
        predatorTheta = 0;
        maxspeed = 2.3;
        maxforce = 0.1;
        maxHealth = dna.genes[1];
        health = maxHealth/2;
        hearing = dna.genes[0];
        smell = 200 - hearing;
        age = 0;
        matingAge = 400;
        hormonDuration = dna.genes[2];
        hormonRange = 300 - hormonDuration;
        tempHormonPulse = r;
        tempHormonDuration = 0;
        showSenses = false;
        mating = false;
        if (random(0, 1) > 0.5) {
          male = true;
        } else {
          male = false;
        }
      }
    
      void run() {
        update();
        borders();
        wander();
        sexChange();
        display();
        if (age > matingAge && random(0, 1) < 0.001)  mating = true;
      }
    
      // Method to update position
      void update() {
    
        r = map(health, 0, 400, 3, 8);           // the healthier, the bigger
        maxspeed = map(health, 0, 400, 3.5, 1);  // the bigger, the slower
    
        velocity.add(acceleration);              // Update velocity    
        velocity.limit(maxspeed);                // Limit speed
        position.add(velocity);   
        acceleration.mult(0);                    // Reset accelertion to 0 each cycle
        health -= 0.2;                           // loosing health over time
      }
    
      void wander() { 
        float predatorR = 10;                    // Radius for our "wander circle"
        float predatorD = 30;                    // Distance for our "wander circle"
        float change = 0.3;
        predatorTheta += random(-change, change);     // Randomly change wander theta
    
        if (mating) pulse();
    
        // Now we have to calculate the new position to steer towards on the wander circle
        PVector circlepos = velocity.copy();    // Start with velocity
        circlepos.normalize();                  // Normalize to get heading
        circlepos.mult(predatorD);              // Multiply by distance
        circlepos.add(position);                // Make it relative to bloop's position
    
        float h = velocity.heading();           // We need to know the heading to offset wandertheta
    
        PVector circleOffSet = new PVector(predatorR*cos(predatorTheta+h), predatorR*sin(predatorTheta+h));
        target = PVector.add(circlepos, circleOffSet);
        seek();
    
        // Render wandering circle, etc.
        if (thinkingProcess) drawPredatorStuff(position, circlepos, target, predatorR, health);
        if (showSenses) drawPredatorSenses();
      }
    
      void applyForce(PVector force) {
        acceleration.add(force);
      }
    
    
      // A method that calculates and applies a steering force towards a target
      // STEER = DESIRED MINUS VELOCITY
      // predator priorities are: mating > eat meat > hunt bloops
      void seek() {
    
        float newDistance = 1000;
        float fNewDistance = 1000;
        PVector closestBloop = new PVector(random(0, 1), random(0, 1));
        PVector closestFood = new PVector(random(0, 1), random(0, 1));
    
    
        ArrayList<Bloop> bloops = world.getBloops();
        for (int j = 0; j < bloops.size(); j++) {
          PVector bloopPosition = bloops.get(j).position;
          float distance = PVector.dist(position, bloopPosition);
    
          if (distance < newDistance) {
            newDistance = distance;
            closestBloop = bloopPosition;
          }
    
          if (newDistance < hearing) {
            target = closestBloop;
          }
    
          if (health > maxHealth) {           // hunting is energy consuming
            health = maxHealth;               // but the reward is an healthy meal that will boost your health up to the top.
          }
        }
    
        ArrayList<PredatorFood> pFoods = world.getPredatorFood();
        for (int i = 0; i < pFoods.size(); i++) {
          PVector pFoodPosition = pFoods.get(i).position;
          float fDistance = PVector.dist(position, pFoodPosition);
    
          if (fDistance < r/2) {
            health = maxHealth;
            pFoods.remove(i);
          }
    
          if (fDistance < fNewDistance) {
            fNewDistance = fDistance;
            closestFood = pFoodPosition;
          }
    
          if (fNewDistance < smell) {
            target = closestFood;
          }
        }
    
        if (mating) {   // if we are not sexually receptive we do not bother to look for partners
          ArrayList<Predator> predators = world.getPredators();
          for (int i = 0; i < predators.size(); i++) {
            PVector matingPredatorPosition = predators.get(i).position;
            float matingPredatorDistance = PVector.dist(position, matingPredatorPosition);
    
            if ((matingPredatorDistance - predators.get(i).hormonRange/2) < smell && male != predators.get(i).male && predators.get(i).mating == true) {
              target = matingPredatorPosition;
    
              if (matingPredatorDistance < r) {
                Predator partner = predators.get(i);
    
                PredatorDNA myGenes = dna;
                PredatorDNA partnerGenes = partner.getDNA();
    
                PredatorDNA child = myGenes.crossover(partnerGenes);
                child.mutate(mutationRate);
    
                predators.add(new Predator(position.x, position.y, child));
                age += 500;
                predators.get(i).age += 500;
                mating = false;
                predators.get(i).mating = false;
              }
            }
          }
        } 
    
        PVector desired = PVector.sub(target, position);  // A vector pointing from the position to the target    
        desired.normalize();                              // Normalize desired and scale to maximum speed
        desired.mult(maxspeed);                     
        PVector steer = PVector.sub(desired, velocity);   // Steering = Desired minus Velocity
        steer.limit(maxforce);                            // Limit to maximum steering force
    
        applyForce(steer);
      }
    
     // if there's less than 1 male for every 2 female or vice versa there's a small chance of a spontaneous sex change
      void sexChange() {                                        
        int malesNum = world.getPredatorSex();                      
        int femalesNum = world.predators.size() - malesNum;
    
        if (male == false && malesNum < femalesNum/2 || male == true && femalesNum < malesNum/2) {
          if (random(0, 1) < 0.0005) {
            male = !male;
          }
        }
      }
    
      void pulse() {
    
        if (tempHormonDuration < hormonDuration) {
          ellipseMode(CENTER);
          if (male == true) stroke(155, 0, 0);
          else              stroke(255, 0, 0);
          noFill();
          ellipse(position.x, position.y, tempHormonPulse, tempHormonPulse);
        }
    
        tempHormonDuration += 1;
        tempHormonPulse += 3;    
    
        if (tempHormonPulse > hormonRange) {
          tempHormonPulse = r;
        }
    
        if (tempHormonDuration == hormonDuration) {
          tempHormonDuration = 0;
          tempHormonPulse = r;
          mating = false;
        }
      }
    
      boolean dead() {
        if (health < 0.0) {
          return true;
        } else {
          return false;
        }
      }
    
      void display() {
    
        // Draw a triangle rotated in the direction of velocity
        float theta = velocity.heading() + radians(90);
    
        if (age < matingAge) {
          fill(200, 0, 255, health+10);        // babies are purple
        } else {
          if (male == true) {
            fill(155, 0, 0, health+10);        // males are brown
          } else {
            fill(255, 0, 0, health+10);        // females are red
          }
        }
        stroke(0, health+10);
        pushMatrix();
        translate(position.x, position.y);
        rotate(theta);
        beginShape(TRIANGLES);
        vertex(0, -r*2);
        vertex(-r, r*2);
        vertex(r, r*2);
        endShape(); 
        popMatrix();
      }
    
      // Wraparound
      void borders() {
        if (position.x < -r) position.x = width+r;
        if (position.y < -r) position.y = height+r;
        if (position.x > width+r) position.x = -r;
        if (position.y > height+r) position.y = -r;
      }
    
      PredatorDNA getDNA() {
        return dna;
      }
    
      void drawPredatorSenses() {
        noFill();
        stroke(255, 0, 0);
        ellipseMode(CENTER);
        ellipse(position.x, position.y, hearing*2, hearing*2);
        stroke(175, 175, 0);
        ellipse(position.x, position.y, smell*2, smell*2);
      }
    }
    
    // A method just to draw the circle associated with wandering
    void drawPredatorStuff(PVector position, PVector circle, PVector target, float rad, float health) {
      stroke(0, health);
      noFill();
      ellipseMode(CENTER);
      ellipse(circle.x, circle.y, rad*2, rad*2);
      ellipse(target.x, target.y, 4, 4);
      line(position.x, position.y, circle.x, circle.y);
      stroke(255, 0, 0, health);
      line(circle.x, circle.y, target.x, target.y);
    } 
    
  • edited July 2017
    // DNA is an array of genes
    // genes can be a vector, a float, an integer and so on (in our case it is an integer array so we can only store integers)
    // each gene initialize a specific variable
    
    class PredatorDNA {
    
      int[] genes;
    
      PredatorDNA() {
        genes = new int[3];        
        genes[0] = int(random(30, 200));    // genes 0 initialize hearing that influence smell
        genes[1] = int(random(100, 400));   // initialize predator maxHealth that influences maxSpeed and r
        genes[2] = int(random(100, 300));   // genes 2 initialize hormonDuration that influence hormonRange
      }
    
      PredatorDNA(int[] newGenes) {
        genes = newGenes;
      }
    
      // Crossover creates a new DNA sequence from two partners  
      PredatorDNA crossover(PredatorDNA partner) {
        int [] child = new int[genes.length];
    
        int crossover = int(random(genes.length));            // Pick a random midpoint
    
        for (int i = 0; i < genes.length; i++) {              // Take "half" from one and "half" from the other
          if (i > crossover) child[i] = genes[i];
          else               child[i] = partner.genes[i];
        }    
    
        PredatorDNA newgenes = new PredatorDNA(child);         // return new made dna
        return newgenes;
      }
    
    
      // based on mutation rate, pick a new random float  
      void mutate(float m) {  
        m = mutationRate;
    
        if (random(1) < m) {
          genes[0] = int(random(30, 200));
        }     
        if (random(1) < m) {
          genes[1] = int(random(100, 400));
        }
        if (random(1) < m) {
          genes[2] = int(random(100, 300));
        }
      }
    }
    
  • // These are the plants 
    // they spontaneously grow all over the world
    
    class Food {
    
      PVector position;
    
      Food(float x, float y) {
        position = new PVector(x, y);
      }
    
      void run() {
        display();
      }  
    
      void display() {
        stroke(0);
        fill(200, 200, 0);
        ellipse(position.x, position.y, 5, 5);
      }
    }
    
  • // this is meat left by dead creatures (both bloops and predator)
    // only predators can eat meat
    
    class PredatorFood {
    
      PVector position;
    
      PredatorFood(float x, float y) {
        position = new PVector(x, y);
      }
    
      void run() {
        display();
      }  
    
      void display() {            // meat is red and bigger than plants
        stroke(0);
        fill(255, 0, 0);
        ellipse(position.x, position.y, 8, 8);
      }
    }
    
  • edited July 2017
    // the world class is what keeps everything togheter
    
    class World {
    
      ArrayList<Food> foods;
      ArrayList<PredatorFood> pFoods;
      ArrayList<Bloop> bloops;                          // living bloops
      ArrayList<Bloop> bloopsClones;                    // backup for dead bloops ( we need to keep track of their state at the moment of death, so we can rank them later).
      ArrayList<Bloop> maleBloopsMatingPool;            // list of possible bloops dads
      ArrayList<Bloop> femaleBloopsMatingPool;          // list of possible bloops mom
      ArrayList<Predator> predators;
      ArrayList<Predator> predatorsClones;
      ArrayList<Predator> malePredatorsMatingPool;
      ArrayList<Predator> femalePredatorsMatingPool;
      float foodSpawnRate;
    
    
      World(int numF, int numB, int numP) {
    
        foodSpawnRate = 0.5;                                      // defines the rate at which plants will spawn.
        foods = new ArrayList<Food>();                            // initialize every arraylist
        pFoods =new ArrayList<PredatorFood>();
        bloops = new ArrayList<Bloop>();  
        bloopsClones = new ArrayList<Bloop>();
        maleBloopsMatingPool = new ArrayList<Bloop>();
        femaleBloopsMatingPool = new ArrayList<Bloop>();
        predators = new ArrayList<Predator>();
        predatorsClones = new ArrayList<Predator>();
        malePredatorsMatingPool = new ArrayList<Predator>();
        femalePredatorsMatingPool = new ArrayList<Predator>();
    
        for (int i=0; i < numF; i++) {                                                        // populate the arraylist for plants, bloops and predators
          foods.add(new Food(random(0, width), random(0, height)));
        }
        for (int i=0; i < numB; i++) {
          bloops.add(new Bloop(random(0, width), random(0, height), new BloopDNA()));
        }
        for (int i=0; i < numP; i++) {
          predators.add(new Predator(random(0, width), random(0, height), new PredatorDNA()));
        }
      }
    
      void run() {
    
        // populate the world with plants, bloops and predators.
        // since they will be either added or removed from the world during the simulation,
        // we have to check every arrays backward to avoid skypping objects while removing them.
    
        for (int i = foods.size()-1; i >= 0; i--) {                  
          Food f = foods.get(i);                                     
          f.run();
          if (foods.size() > 500) foods.remove(0);     // max plants num
        }
    
        for (int i = pFoods.size()-1; i >= 0; i--) {
          PredatorFood pf = pFoods.get(i);
          pf.run();
        }
    
        for (int i = bloops.size()-1; i >= 0; i--) {                  
          Bloop b = bloops.get(i);
          b.run();
          if (b.dead()) {                                               // if a bloop dies
            bloopsClones.add(b);                                        // move it to the clones array to have a backup of his stats at the moment of death
            bloops.remove(i);                                           // remove the dead bloop from the world
            pFoods.add(new PredatorFood(b.position.x, b.position.y));   // leave meat behind
          } else {
            b.age ++;                                                   // if we are alive we keep aging
          }
        }
    
        for (int i = predators.size()-1; i >= 0; i--) {
          Predator p = predators.get(i);
          p.run();
          if (p.dead()) {
            predatorsClones.add(p);
            predators.remove(i);
            pFoods.add(new PredatorFood(p.position.x, p.position.y));
          } else {
            p.age++;
          }
        }
    
        if (random(1) < foodSpawnRate) foods.add(new Food(random(0, width), random(0, height)));    // plants will randomly spawn over time
      }
    
    
      // when an ecosystem collapse (one of the species dies off) we will need to create a new one.
      // we will populate it based on the previous results (creatures fitness) to achieve evolution.
    
      // first we have to generate two bloops mating pool (moms and dads) and fill them.
      void bloopSelection() {
    
        maleBloopsMatingPool.clear();                    // clear both mating pools
        femaleBloopsMatingPool.clear();
    
        float maxFitness = getBloopsMaxFitness();        // calculate total population fitness
    
        // Calculate fitness for each member of the population (scaled to value between 0 and 1)
        // Based on fitness, each member will get added to the mating pool a certain number of times
        // A higher fitness = more entries to mating pool = more likely to be picked as a parent
        // A lower fitness = fewer entries to mating pool = less likely to be picked as a parent
        // This is called the "wheel of fortune" method.
        for (int i = 0; i < bloopsClones.size(); i++) {
          float fitnessNormal = map(bloopsClones.get(i).age, 0, maxFitness, 0, 1);
          int n = (int) (fitnessNormal * 100);
          for (int j = 0; j < n; j++) {
            if (bloopsClones.get(i).male == true) {        
              maleBloopsMatingPool.add(bloopsClones.get(i));
            } else {
              femaleBloopsMatingPool.add(bloopsClones.get(i));
            }
          }
        }
      }
    
      // generate pools for the predators and fill them (wheel of fortune method).
      void predatorSelection() {
    
        malePredatorsMatingPool.clear(); 
        femalePredatorsMatingPool.clear();
    
        float maxFitness = getPredatorsMaxFitness();        
    
        for (int i = 0; i < predatorsClones.size(); i++) {
          float fitnessNormal = map(predatorsClones.get(i).age, 0, maxFitness, 0, 1);
          int n = (int) (fitnessNormal * 100);
          for (int j = 0; j < n; j++) {
            if (predatorsClones.get(i).male == true) {
              malePredatorsMatingPool.add(predatorsClones.get(i));
            } else {
              femalePredatorsMatingPool.add(predatorsClones.get(i));
            }
          }
        }
      }
    
      // and now let's create the next generation
      void bloopReproduction() {
    
        for (int i = 0; i < numB; i++) {                           // create N children based on the initial bloops count.
          int m = int(random(femaleBloopsMatingPool.size()));      // spin the wheel for the mother
          int d = int(random(maleBloopsMatingPool.size()));        // spin the wheel for the dad
    
          Bloop mom = femaleBloopsMatingPool.get(m);               // pick the two parents
          Bloop dad = maleBloopsMatingPool.get(d); 
          BloopDNA momgenes = mom.getDNA();                        // get their DNA
          BloopDNA dadgenes = dad.getDNA();
    
          BloopDNA child = momgenes.crossover(dadgenes);           // cross genes
          child.mutate(mutationRate);                              // apply chance of mutation
    
          bloops.add(new Bloop(random(0, width), random(0, height), child));    // generate a new bloop at random position with the feshmade dna
        }
        bloopsClones.clear();                                      // clear the clonearray to get it ready for the next gen
      }
    
      void predatorsReproduction() {
    
        for (int i = 0; i < numP; i++) {           
          int m = int(random(femalePredatorsMatingPool.size()));     
          int d = int(random(malePredatorsMatingPool.size()));       
    
          Predator mom = femalePredatorsMatingPool.get(m);           
          Predator dad = malePredatorsMatingPool.get(d);
          PredatorDNA momgenes = mom.getDNA();                       
          PredatorDNA dadgenes = dad.getDNA();
    
          PredatorDNA child = momgenes.crossover(dadgenes);          
          child.mutate(mutationRate);                                
    
          predators.add(new Predator(random(0, width), random(0, height), child));
        }
        predatorsClones.clear();
      }
    
      int getGenerations() {
        return generations;
      }
    
      // find Highest fitness for the bloops population
      float getBloopsMaxFitness() {
        float record = 0;
        for (int i = 0; i < bloopsClones.size(); i++) {
          if (bloopsClones.get(i).age > record) {    // in our case fitness is just "how long did you survived"
            record = bloopsClones.get(i).age;
          }
        }
        return record;
      }
    
      float getPredatorsMaxFitness() {
        float record = 0;
        for (int i = 0; i < predatorsClones.size(); i++) {
          if (predatorsClones.get(i).age > record) {    // in our case fitness is just "how long did you survived"
            record = predatorsClones.get(i).age;
          }
        }
        return record;
      }
    
      int getBloopSex() {      // determines how many male/female bloops are alive
        int males = 0;
        for (int i = 0; i < bloops.size(); i++) {
          if (bloops.get(i).male == true) {
            males += 1;
          }
        }
        return males;
      }
    
      int getPredatorSex() {     // determines how many male/female predators are alive
        int males = 0;
        for (int i = 0; i < predators.size(); i++) {
          if (predators.get(i).male == true) {
            males += 1;
          }
        }
        return males;
      }
    
      ArrayList getPredators() {
        return predators;
      }
    
      ArrayList getBloops() {
        return bloops;
      }
    
      ArrayList getFood() {
        return foods;
      }
    
      ArrayList getPredatorFood() {
        return pFoods;
      }
    }
    
  • Hope everything works fine, there's still a lot to do but it's quite fun to watch. you can tune parameters and see what changes in the evolution of the creatures.

    If you like the project i'll keep you updated!!!

    enjoy :D

  • Very impressive work

    Thanks for sharing

    You could upload it on github

    Best, Chrisir

  • Hello @m4l4. Here Venerando, from Spain. Im very interested in the matter of genetic algoritms and also arrived into Processing after reading the book "The nature of code" by Daniel Shiffman, a very interesting book. I am implied now in developing a database manager for Processing, as I would like to develop a genetic program that stores the evolution of an ecosystem like yours. I expected to have finished before, but it is proving to be more complicated than I thought. However, I have learned a lot from it and hope to finish soon. Then I'll start doing my genetic project. Surely I will copy things from yours. Anyway, if you want to have a look at my database manager, you can do it at https://github.com/vesolba/DBManager-for-Processing

    Greetings.

  • hi @vesolba, feel free to copy whatever you want (as i did myself ;P) i'm not a programmer nor an engineer, just a geek with a lot of patience ;P I like your idea and i was thinking about something like that for the next part of my project, i want to add a neural network to the simulation (a lot easier to say than it actually is) and i will need a way to "save" the brain of the creatures whenever i quit the simulation. I'm struggling to write a rnn lstm in processing, i'll post something as soon as i have something that actually works.

Sign In or Register to comment.