> show canvas only <


/* built with Studio Sketchpad: 
 *   https://sketchpad.cc
 * 
 * observe the evolution of this sketch: 
 *   https://studio.sketchpad.cc/sp/pad/view/ro.4SZdV9t0CRg/rev.4370
 * 
 * authors: 
 *   Jason Reich
 *   

 * license (unless otherwise specified): 
 *   creative commons attribution-share alike 3.0 license.
 *   https://creativecommons.org/licenses/by-sa/3.0/ 
 */ 



// Pressing Control-R will render this sketch.
// Click to add human targets

ArrayList flock = new ArrayList();
float personal_space = 30;
float zombie_sight = 50;
float human_sight = 75;
float zombie_maxacc = 0.1;
float human_maxacc = 0.5;
float zombie_maxspeed = 1;
float human_maxspeed = 2;
int max_life = 100;

void setup() {  // this is run once.   
    // canvas size (Variable aren't evaluated. Integers only, please.)
    size(500, 500); 
      
    // smooth edges
    smooth();
    
    // limit the number of frames per second
    frameRate(30);
    
    // set the width of the line. 
    strokeWeight(3);
    
    for(int i = 0; i < 50; i++) {
        Boid b = new Boid( new PVector(random(width), random(height))
                         , new PVector(random(-0.1,0.1), random(-0.1,0.1)), true);
        flock.add(b);
    }
    
    println("Green rings are zombies. White rings are prey.");
    println("Click to add a human target at that location.");
    println("The centre of a human ring gets more green as they get closer to becoming zombied.");
} 

void draw() {  // this is run repeatedly.  
    background(50);
    
    for(int i = 0; i < flock.size(); i++) {
        Boid b = (Boid) flock.get(i);
        b.draw();
        b.step();
    }
    
    for(int i = 0; i < flock.size(); i++) {
        Boid b = (Boid) flock.get(i);
        b.flip();
    }
}

void mousePressed() {  
  flock.add(new Boid( new PVector(mouseX, mouseY)
                    , new PVector(random(-0.1,0.1), random(-0.1,0.1)), false));
}  

class Boid {
    PVector position, velocity, next_position, next_velocity;
    boolean undead;
    int life;
    
    Boid(PVector pos, PVector vel, boolean und) {
        next_position = position = pos;
        next_velocity = velocity = vel;
        undead = und;
        life = max_life;
    }
    
    float sight() {
        return undead ? zombie_sight : human_sight;
    }

    void flip() {
        position = next_position;
        velocity = next_velocity;
        if( !undead && life <= 0 ) undead = true;
    }
    
    void draw() {
        if(undead) {
            noFill();
            stroke(0, 255, 0, 255);
        } else {
            fill(0, 255, 0, 255 * (max_life - life) / max_life);
            stroke(255, 255, 255, 255);
        }
        ellipse(position.x-5, position.y-5, 10,10);
    }
    
    ArrayList calcNeighbourhood() {
        ArrayList n = new ArrayList();
        
        for(int i = 0; i < flock.size(); i++) {
            Boid b = (Boid) flock.get(i);
            if( PVector.dist(position, b.position) <= sight() ) {
                n.add(b);
            }
        }
        return n;
    }
    
    PVector stumble() {
        PVector v = new PVector(random(-1, 1), random(-1, 1));
        v.normalize();
        v.mult(velocity.mag() / 100);
        return v;
    }
    
    PVector separation(ArrayList n) {
        PVector v = new PVector();
        float count = 0;
        for(int i = 0; i < n.size(); i++) {
            Boid b = n.get(i);
            PVector diff = PVector.sub(position, b.position);
            float m = diff.mag();
            if(m > 0 && m <= personal_space && !(undead && !(b.undead))) {
                diff.normalize();
                diff.div(m);
                v.add(diff);
                count = count + 1;
            };
        }
        if(count > 0) v.div(count);
        return v;
    }

    PVector prey(ArrayList n) {
        PVector v = new PVector();
        if( !undead ) {
            for(int i = 0; i < n.size(); i++) {
                Boid b = n.get(i);
                PVector diff = PVector.sub(position, b.position);
                float m = diff.mag();
                if(m > 0 && b.undead) {
                    diff.normalize();
                    diff.div(m);
                    v.add(diff);
                    if(m <= personal_space) life -= 2;
                };
            }
            if( life < max_life ) life++;
        }
        return v;
    }

    PVector predator(ArrayList n) {
        PVector v = new PVector();
        if( undead ) {
            PVector nearest;
            float nearest_dist = 1 + zombie_sight;
            for(int i = 0; i < n.size(); i++) {
                Boid b = n.get(i);
                float b_dist = PVector.sub(b.position, position).mag();
                if( !(b.undead) && b_dist < nearest_dist ) {
                    nearest = b.position;
                    nearest_dist = b_dist;
                }
            }
            if( nearest != null ) {
                v = PVector.sub(nearest, PVector.add(position, velocity));
            }
        }
        return v;
    }
    
    void step() {
        ArrayList n = calcNeighbourhood();
        PVector next_acc = new PVector(0, 0);
        next_acc.add(separation(n));
        next_acc.add(predator(n));
        next_acc.add(stumble());
        next_acc.add(prey(n));
        
        next_acc.limit(undead ? zombie_maxacc : human_maxacc);
        
        next_velocity = PVector.add(velocity, next_acc);
        next_velocity.limit(undead ? zombie_maxspeed : human_maxspeed);
        next_position = PVector.add(position, next_velocity);
        
        float bufferzone = 10;
        float xmin = 0 - bufferzone;
        float xmax = width + bufferzone;
        float ymin = 0 - bufferzone;
        float ymax = height + bufferzone;
        if( next_position.x < xmin ) next_position.x = xmax - (xmin - next_position.x);
        if( next_position.x > xmax ) next_position.x = xmin - (xmax - next_position.x);
        if( next_position.y < ymin ) next_position.y = ymax - (ymin - next_position.y);
        if( next_position.y > ymax ) next_position.y = ymin - (ymax - next_position.y);
    }
}