> show canvas only <


/* built with Studio Sketchpad: 
 *   https://sketchpad.cc
 * 
 * observe the evolution of this sketch: 
 *   https://studio.sketchpad.cc/sp/pad/view/ro.Q95GCWaE9E0/rev.3708
 * 
 * authors: 
 *   
 *   Jesse Steinfort
 *   

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



// This sketch builds on a prior work, "fish-tank-005", created by Jesse Steinfort
// http://studio.sketchpad.cc/sp/pad/view/ro.96WLx-XgErqmt/rev.156

// The next generation of the Fish Tank starts with 
// this sketch: http://studio.sketchpad.cc/ppUOf52bPA

// Pressing Control-R will render this sketch.

int i = 0; 
int CANVAS_WIDTH = 600;
int CANVAS_HEIGHT = 600;
int NUMFISH = 10;
int FISH_SX = 300;
int FISH_SY = 300;
int NUMFOOD = 20;

ArrayList school = new ArrayList();
ArrayList meals = new ArrayList();

void setup() {  // this is run once.   
  
  // canvas size (Variable aren't evaluated. Integers only, please.)
  size(600, 600); 
    
  // smooth edges
  smooth();
  
  // limit the number of frames per second
  frameRate(30);
  
  //Initialize the Fish

  
  
  for(int i=0;i<NUMFISH;i++){
      Fish fish = new Fish(FISH_SX,FISH_SY);
      Target target = createTarget();
      fish.swimTo(target);
      school.add(fish);
  }
  
  
  //Initialize the Food
  
  
  for(int i=0;i<NUMFOOD;i++){
      FishFood food = new FishFood(
        round(random(0,CANVAS_WIDTH)),
        round(random(0,CANVAS_HEIGHT)),
        round(random(3,15)));
      food.setId(i);
      meals.add(food);
  }

} 


interface Target{
    public int getId();
    public void setId(int id);
    public int getX();
    public void setX(int x);
    public int getY();
    public void setY(int y);
    public boolean isFood();//TODO can we check types?    
}

class GenericTarget implements Target{
    int id;
    int x;
    int y;
    boolean _isFood = false;
    
    public GenericTarget(int x,int y){
        this.x = x;
        this.y = y;
    }
    public int getX(){
        return this.x;
    }
    public void setX(int x){
        this.x = x;
    }
    public int getY(){
        return this.y;
    }
    public void setY(int y){
        this.y = y;
    }
    public boolean isFood(){
      //println("isFood() == " + (this.isFood?"true":"false"));
      return _isFood;
    }
    public void setIsFood(boolean isFood){
        _isFood = isFood;
    }
    public int getId(){
        return this.id;
    }
    public void setId(int id){
        this.id = id;
    }
}

//-------------------------------------------------------------------
// FISH FOOD

int FOOD_RED = 200;
int FOOD_GREEN = 100;
int FOOD_BLUE = 50;


class FishFood extends GenericTarget{
    int sz;
    int sz0;
    
    
    FishFood(int x, int y, int sz){
        super(x,y);
        this.sz = sz;
        this.sz0 = sz;
        setIsFood(true);
    }
    
    void consume(int amount){
        this.sz -= amount;
    }

    boolean isConsumed(){
        return this.sz <= 0;  
    }

    void draw(){
        pushMatrix();
        translate(this.x,this.y);
        stroke(1);
        fill(FOOD_RED,FOOD_GREEN,FOOD_BLUE, round(255 * (this.sz / this.sz0)));
        rect(-1 * round(this.sz / 2),-1 * round(this.sz / 2),this.sz,this.sz);     
        popMatrix();
    }
}

//----------------------------------------------------------------
// FISH
class Fish extends GenericTarget{

    //characteristics
    color fishColor;
    int visionRange=50;
    int speed=2;
    
    //state
    Target target;
    int extraSize=0;
    
    int wagleStart=15;
    int wagleMax=15;
    int wagle=0;
    int wagleDirection=1;
    int startX;
    int startY;
    
    Fish(int x, int y){  
        super(x,y);
        this.startX = x;
        this.startY = y;
        this.target = new GenericTarget(x,y);

        this.fishColor = color(166, 230, 170);
        this.wagle=this.wagleStart;
    }

    void draw() {
        /*
        //debug: line to target
        line(startX,startY,this.target.getX(),this.target.getY());
        */
        
        pushMatrix();
        translate(this.x,this.y);
        float angle = calcAngle();
        rotate(angle-radians(this.wagle));
        //rotate(angle);//debug: no wagle
        
        /*
        //debug aura
        stroke(10,10,10);
        fill(255,75,75,50);
        ellipse(0,0,this.visionRange*2,this.visionRange*2);
        line(0,0,40,0);//debug unicorn fish
        */
        
        stroke(60, 94, 62);
        fill(this.fishColor);
        int q = round(this.extraSize / 2);//adjust for extraSize
        quad(-15-q,-7,-10-q,-2,-10-q,2,-15-q,7);
        ellipse(0,0,20+this.extraSize,10+this.extraSize);
        popMatrix();

        /*
        //debug: paint a target
        pushMatrix();
        translate(this.target.getX(),this.target.getY());
        fill(color(255,50,50));
        ellipse(0,0,10,10);
        popMatrix();
        */
    }
    
    private float calcAngle(){
        int tx=this.target.getX();
        int ty=this.target.getY();
        
        int dx = tx-this.x;
        int dy = ty-this.y;
        float a = atan(dy/dx);
        
        if(dx <= 0){
            
            a+=PI;
        }
        //println("angle = " + degrees(a));
        return a;
        
    }

    void swimTo(Target target){
        println("Swim to target at " + 
            target.getX() + "," + target.getY() + 
            " from " + this.x + "," + this.y);
        this.target = target;
        this.startX = this.x;
        this.startY = this.y;
    }

    void swim(){
        println("swim start: " + this.x + "," + this.y);

        int distanceToTarget = 
            dist(this.x,this.y,
                 this.target.getX(),this.target.getY());

        if(distanceToTarget <= this.speed){
            this.whenAtTarget();
        } 
        
        int tx=this.target.getX();
        int ty=this.target.getY();
        
        int dx = tx-this.x;
        int dy = ty-this.y;
        int ix = 0;
        int iy = 0;
        
        if(dx == 0){
            iy = speed*(dy/abs(dy));
        } else if(dy == 0) {
            ix = speed*(dx/abs(dx));
        } else {
            float a = atan(abs(dy/dx));
        
            float s = sin(a);
            if(dy != 0){
                iy = (this.speed*s)*(dy/abs(dy));//mult by sign of delta
            }

            float c = cos(a);
            if(dx != 0){
                ix = (this.speed*c)*(dx/abs(dx));//mult by sign of delta
            }
        }

        x+=ix;
        y+=iy;
        
        this.draw();

        if(abs(this.wagle) >= this.wagleMax){
            this.wagleDirection *= -1;
        }
        this.wagle = this.wagle + (5 * this.wagleDirection);
    
        if(frameCount % 100 == 1 && this.extraSize > 0){
            this.extraSize--;
        }
             
    }

    float fmt(float x){
        float z=x*100;
        z=round(z)/100;
        return z;
    }

    void whenAtTarget(){
      println("...at target...");
      if(this.target.isFood() && ((FishFood)target).sz >= 0){
          ((FishFood)target).consume(1);
          this.extraSize++;
      } else {
        println("create new target @" + minute() + ":" + second());
        Target target = createTarget();
        swimTo(target);
      }
    }

    boolean canSmell(Target target){
        int d = dist(this.x,this.y,target.getX(),target.getY());
        return  d <= this.visionRange;
    }

    boolean isTargeting(Target target){
      //return this.target == target;
      return this.target.getX() == target.getX() && this.target.getY() == target.getY();
    }

    boolean isHungry(){
        return this.extraSize <= 10;   
    }
}    

// -----------------------------------------------------
// Execution

Target createTarget(){
    Target target = new GenericTarget(round(random(0,CANVAS_WIDTH)),
                             round(random(0,CANVAS_HEIGHT)));
    return target;                         
}





// Main Draw Function (ProcessingJS reserved)
void draw() {
    /*
    if(frameCount % 60 != 1){
        return;    
    }
    */
    
    background(202, 239, 252);
    stroke(0, 0, 0);
    for(int i=0;i<school.size();i++){
        Fish fish = (Fish)school.get(i);
        for(int j=0;j<meals.size();j++){
            FishFood food = (FishFood)meals.get(j);
            if(fish.canSmell(food) && !food.isConsumed() &&
                !fish.isTargeting(food) && !fish.target.isFood() && fish.isHungry()){
                fish.swimTo(food);
            }
        }
        fish.swim();
    }
    
    for(int i=0;i<meals.size();i++){
       FishFood food = (FishFood)meals.get(i);
       food.draw();
    }
    
}