/* 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();
}
}