color GREEN = color(100,255,100);


HashSet<Apple> apples = new HashSet<Apple>();
Snake s = new Snake();


int score = -1;
boolean playing = false;

void startGame(){
   score = 0;
   playing = true;
    apples = new HashSet<Apple>();
for(int i = 0; i < 10; i++){
    apples.add(new Apple());
  }
  s = new Snake();
  
}




void setup() {
  size(500,500); 
  textSize(20);
 // smooth();

}
boolean virgin = true;
color bg = color(200);
void draw() {
  background(bg);
  fill(120);stroke(0);strokeWeight(3);
  ellipse(250,250,500,500);
  
  if(playing){
    s.move();
      }
  s.draw();


  for(Apple a : apples){
    a.move();
     a.draw(); 
  }


  if(!playing){
      fill(0);
    
      text("SNAKEDRIVE",100,100);
      if(score != -1){
       text("SCORE:"+score,200,200);
           
      }
      text("SPACE TO PLAY",200,300);
           text("LEFT & RIGHT STEERS",200,325);
 
      text("EAT MICE",200,350);
      text("AVOID WALLS & SELF",200,375);
      
      
  } else {
    fill(0);
          text("SCORE:"+score,345,495);
 
  }

}
class Segment{
    float x,y;
    Segment(float px, float py ){
       x = px;
      y = py; 
    }
    
}
class Apple{
  float x,  y;
  float ang;
  float as;
  Apple(){
      float a = random(2*PI);
      x = 250+250 * cos(a);
      y = 250+250 * sin(a);
      ang = a-PI;

  }
  float sp = 1;
  void move(){
    if(random(1) < .2){
    as += random(-.05,.05);
    ang += as;
    if(as > .2) as = .2;
    if(as < -.2) as = -.2;
    }
    x += cos(ang)*sp;
    y += sin(ang)*sp;
    
    if(s.appleHit(this)){
       ang += PI;
     
    }
    
    if(dist(x,y,250,250) >250){
       ang = atan2(y-250,x-250)-PI; 
    }
    
  }
  float size = 10;
  void draw(){
     fill(200);stroke(200);     strokeWeight(2);
    ellipse(x,y,size,size); 

    line(x,y,x+cos(ang-PI) * 14,y+sin(ang-PI) * 14);
    strokeWeight(1);stroke(0);
    float nx = x+cos(ang) * 5;
    float ny = y+sin(ang) * 5;
   float nl = 5;
    float na;
    float nd = .3;
       na =ang+ PI/2+nd;
       line(nx,ny, nx+cos(na)*nl,ny+sin(na)*nl);       
       na =ang+ PI/2-nd;
       line(nx,ny, nx+cos(na)*nl,ny+sin(na)*nl);       
      
       na =ang- PI/2+nd;
       line(nx,ny, nx+cos(na)*nl,ny+sin(na)*nl);       
       na =ang- PI/2-nd;
       line(nx,ny, nx+cos(na)*nl,ny+sin(na)*nl);       
      
    
  }
  
}

class Snake{
   float x,y,a;
   ArrayList<Segment>tail = new ArrayList<Segment>();
   
   Snake(){
      x = 250;
     y = 250; 
     a = -PI/2;
   }
   float TURN = .1;
   float MAX = 50;
   float speed = 1;
   void move(){
     if(leftPressed) a -= TURN;
     if(rightPressed) a += TURN;
   
     tail.add(new Segment(x,y));
     
     x += speed*cos(a);
     y += speed * sin(a);
     
     if(dist(x,y,250,250) > 250){
        playing = false; 
     }
     
   
      bg = color(200);   
   
     HashSet<Apple>applesToKill = new HashSet<Apple>();
     for(Apple a: apples){
        if(dist(x,y,a.x,a.y) < a.size){
          bg = color(255,0,0);
           applesToKill.add(a);
           MAX += 30;
           speed += .04;
           score++;
        } 
     }
      apples.removeAll(applesToKill);
      for(int i = 0; i < applesToKill.size();i++){
         apples.add(new Apple()); 
      }
     
     
   }
   void draw(){
     if(playing || score == -1){
     fill(GREEN);
     
     stroke(GREEN);
     } else {
        fill(0); stroke(0); 
     }
     strokeWeight(4);
     
     
     if(tail.size() > MAX){
        tail.remove(0); 
     }
 
 
     float lastX = x; float lastY = y;
     
     for(int i =  tail.size() -1; i >= 0; i--){
         Segment s = tail.get(i);
     //  print(lastX+","+lastY+"  ");
         line(lastX,lastY,s.x,s.y);
         
        
       lastX = s.x;
       lastY = s.y;
     }
 
     int c = tail.size() - 3;
     if(c >= 0){
     lastX = tail.get(c).x; lastY = tail.get(c).y;
    stroke(0);     
     for(int i = c; i >= 0; i--){
         Segment s = tail.get(i);
         if(circleLineIntersect(lastX,lastY,s.x,s.y,x,y,2)){
           playing = false;
         
     }
         
       lastX = s.x;
       lastY = s.y;
     }
     }
 
   
     noStroke();
      ellipse(x,y,10,10); 
      fill(0);noStroke();
      ellipse(x + cos(a - PI/4)* 3,y + sin(a - PI/4)* 3,4,4);
      ellipse(x + cos(a + PI/4)* 3,y + sin(a + PI/4)* 3,4,4);
   }
    boolean appleHit(Apple a){
      float lastX,lastY;
     int c = tail.size()-1;
     if(c > 0){
     lastX = tail.get(c).x; lastY = tail.get(c).y;
    stroke(0);     
     for(int i = c; i >= 0; i--){
         Segment s = tail.get(i);
         if(circleLineIntersect(lastX,lastY,s.x,s.y,a.x,a.y,a.size)){
           return true;
         
     }
         
       lastX = s.x;
       lastY = s.y;
     }
     }
      return false;
    }
  
  
}



boolean leftPressed, rightPressed, shiftPressed;
void keyPressed(){
  if(keyCode == LEFT){
    leftPressed = true;
  } 
  if(keyCode == RIGHT){
    rightPressed = true;
  } 
   if(key == ' '){
      if(!playing){
          startGame();
      } 
   }
}
void keyReleased(){
  if(keyCode == LEFT){
    leftPressed = false;
  } 
  if(keyCode == RIGHT){
    rightPressed = false;
  } 
  if(key=='z'){
    shiftPressed = false; 
  }

}

//from http://www.openprocessing.org/visuals/?visualID=8009

boolean circleLineIntersect(float x1, float y1, float x2, float y2, float cx, float cy, float cr ) {
  float dx = x2 - x1;
  float dy = y2 - y1;
  float a = dx * dx + dy * dy;
  float b = 2 * (dx * (x1 - cx) + dy * (y1 - cy));
  float c = cx * cx + cy * cy;
  c += x1 * x1 + y1 * y1;
  c -= 2 * (cx * x1 + cy * y1);
  c -= cr * cr;
  float bb4ac = b * b - 4 * a * c;
 
  //println(bb4ac);
 
  if (bb4ac < 0) {  // Not intersecting
    return false;
  }
  else {
     
    float mu = (-b + sqrt( b*b - 4*a*c )) / (2*a);
    float ix1 = x1 + mu*(dx);
    float iy1 = y1 + mu*(dy);
    mu = (-b - sqrt(b*b - 4*a*c )) / (2*a);
    float ix2 = x1 + mu*(dx);
    float iy2 = y1 + mu*(dy);
 
    // The intersection points
    //ellipse(ix1, iy1, 10, 10);
    //ellipse(ix2, iy2, 10, 10);
     
    float testX;
    float testY;
    // Figure out which point is closer to the circle
    if (dist(x1, y1, cx, cy) < dist(x2, y2, cx, cy)) {
      testX = x2;
      testY = y2;
    } else {
      testX = x1;
      testY = y1;
    }
     
    if (dist(testX, testY, ix1, iy1) < dist(x1, y1, x2, y2) || dist(testX, testY, ix2, iy2) < dist(x1, y1, x2, y2)) {
      return true;
    } else {
      return false;
    }
  }
}