
package seeker.seeker1_0;

import java.applet.Applet;
import java.lang.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;

public class Seeker extends Applet 
                    implements MouseMotionListener, KeyListener, Runnable {

  static final int width=640;
  static final int height=400;

  static Image doblebuffer;
  static Graphics CG; 
    
  static Nave nave;

  Thread t;

  public void init() {
     nave=new Nave((int)Math.round(Math.random()*width),
                        (int)Math.round(Math.random()*height));
     doblebuffer=createImage(width,height);
     CG=doblebuffer.getGraphics();
     // setBackground(Color.black);
     CG.setColor(Color.black);
     CG.fillRect(0,0,width,height);
     addMouseMotionListener(this);
     addKeyListener(this);
     t=new Thread(this);
     t.start();
  }
  

  public void run() {
     while (true) {
       repaint();
       try {
         t.sleep(5);
       } catch (InterruptedException e) {
       }
     }
  }


  public void update(Graphics g) {
    paint(g);
  }

  public void paint(Graphics g) {
    g.drawImage(doblebuffer,0,0,this);
  }


  ////// Metodos de MouseMotionListener ///////

  public void mouseDragged(MouseEvent e) {
  }

  public void mouseMoved(MouseEvent e) {
     nave.x=e.getX();
     nave.y=e.getY();
     nave.dibujaNave();
  }

  /////// Metodos de KeyListener ///////

  public void keyTyped(KeyEvent e) {
  }

  public void keyPressed(KeyEvent e) {
      if (e.getKeyCode()==VK_ENTER) {
             Sam misil=new Sam((int)Math.round(Math.random()*width),
                               (int)Math.round(Math.random()*height),
                               nave); // el misil seguira a la nave
             Thread t=new Thread(misil);
             t.start();
      }
  }

  public void keyReleased(KeyEvent e) {
  }

} // end class Seeker



/** Clase que define lo que deberia tener cualquier objeto que puede ser
  * blanco de un misil
  */

class Target extends Observable { // los blancos seran observados por sus perseguidores
  double x;
  double y;
}



/* Nave */

class Nave extends Target { // 
   
   double xx;
   double yy;

   Nave(double xi, double yi) {
     x=xi; y=yi;
     xx=x; yy=y;
   }

   void dibujaNave() {
      setChanged();
      notifyObservers();       
      int xx=(int)Math.round(this.xx);
      int yy=(int)Math.round(this.yy);
      int x=(int)Math.round(this.x);
      int y=(int)Math.round(this.y);
      synchronized(Seeker.CG) {
               Seeker.CG.setColor(Color.black);
               Seeker.CG.fillRect(xx,yy,5,5);
               Seeker.CG.setColor(Color.white);
               Seeker.CG.fillRect(x,y,5,5);
      }
      this.xx=this.x; 
      this.yy=this.y; 
  }

} // end class Nave




/** SAM: Surface Air Missile 
  */

class Sam extends Target implements Observer, Runnable { // son observadores de sus blancos. A su vez pueden
                                                         // ser blancos de otros misiles
    private double targetX;
    private double targetY;
    double dx,dy;      // para calcular la diferencia de desplazamiento 
                       // (basado en speed)
    int DX,DY;         // para calcular la diferencia al dibujar el misil 
                       // (basado en length)
    int XX,YY,DXX,DYY; // valores anteriores de dibujo
    double angle;
    int speed;
    boolean activo;
    int fuel;
    int aceleracion;
    Color color;

    double giro=0.05;
    int length=5;
    double dospi=Math.PI*2;
    static final double gravedad=0.1;


    public Sam(int x, int y, Target t) {
       this.x=x;
       this.y=y;
       targetX=t.x;
       targetY=t.y;
       dx=0; 
       dy=0; 
       XX=(int)Math.round(x);
       YY=(int)Math.round(y);
       DXX=0;
       DYY=0;
       angle=Math.PI/2;
       fuel=1000;
       speed=0;       // velocidad inicial
       aceleracion=3; // aceleracion inicial
       color=Color.green;
       activo=true;
       t.addObserver(this); // se convierte en un observador del blanco
    }


    public void run() {
       while (activo) {
          control();
          dibujaSam();
          try {
            Thread.sleep(10);
          }
          catch (InterruptedException e) {}
       }
    }

    /** Devuelve el angulo que parte de x1,y1 y llega a x2,y2
      */

    double angulo(double x1, double y1, double x2, double y2) {
          double alfa=Math.atan(Math.abs(y2-y1)/
                                Math.abs(x2-x1));
          if (x2-x1<0) { alfa=Math.PI-sgn(y2-y1)*alfa; }
                   else { alfa=sgn(y2-y1)*alfa; };
          return arregla(alfa);
    }

    double arregla(double a) {
          if (a>dospi) { a=a-dospi; }
            else { if (a<0) { a=dospi+a; }   
                 }
          return a;
    }
 
    int sgn(double n) {
       if (n<0) return(-1);
           else if (n>0) return(1);
                     else return(0);
    }

    void control() {
       if (fuel>0) {
          fuel=fuel-speed;
          double dif=angle-angulo(x,y,targetX,targetY);
          if (Math.round(dif)==0) { speed=4; } // Mas rapido si no hay que girar 
                 else { speed=1; }
          if (Math.abs(dif)>Math.PI) { angle=angle+giro*sgn(dif); }
                 else { angle=angle-giro*sgn(dif); }
          angle=arregla(angle);
          dx=speed*Math.cos(angle);
          dy=speed*Math.sin(angle);
         }
        else { // No hay fuel
           angle=arregla(angulo(x,y,XX,YY));
           dy=dy+gravedad;
       }

       x=x+dx;
       y=y+dy;
       if ((Math.abs(x-targetX)<2 && Math.abs(y-targetY)<2) || (y>Seeker.height)) {
                      Explosion.boom(x,y,dx/2,dy/2);
                      activo=false;                // Acabara la ejecucion del Thread
               //       target.deleteObserver(this); // Se elimina como observer
                      this.color=Color.black;      // Para que se borre
       }

      
    }    
   


    void dibujaSam() {
       try {
         int X=(int)Math.round(this.x);
         int Y=(int)Math.round(this.y);
         int DX=(int)Math.round(length*Math.cos(angle));
         int DY=(int)Math.round(length*Math.sin(angle));    
         synchronized(Seeker.CG) {
              Seeker.CG.setColor(Color.black);
              Seeker.CG.drawLine(XX,YY,XX-DXX,YY-DYY);
              Seeker.CG.setColor(this.color);
              Seeker.CG.drawLine(X,Y,X-DX,Y-DY);
         }
         if ((Math.random()>0.7)&&(fuel>0)) {
                Debris afterburner=new Debris(X-DX,Y-DY,0,0,30,2);
                afterburner.start();
         }
         XX=X; YY=Y; DXX=DX; DYY=DY;
       } catch (Exception e) {}
    }



    /** Metodo que se disparara cada vez que cambien las coordenadas del blanco
      */

    public void update(Observable t, Object args) {
       if (fuel>0) {
                this.targetX=((Target)t).x;
                this.targetY=((Target)t).y;
               }
         else t.deleteObserver(this); // Sin fuel ya no seguira al blanco
    }

}


/* EXPLOSION */

class Explosion {

  static double inercia=1;
  static int puntos=20;

  static void boom (double x, double y, double dx, double dy) {
      double dif=Math.PI/5;
      double ang=0;
      for (double i=puntos; i>0; i--) {
          ang=ang+dif;
          double dxx=dx+inercia*Math.sin(ang)+Math.random()*4-2;
          double dyy=dy+inercia*Math.cos(ang)+Math.random()*4-2;
          int temp=80+(int)Math.round(Math.random()*40-20);
          Debris chispa=new Debris(x,y,dxx,dyy,temp,0.5);
          chispa.start(); 
      }
  }

}


/* DEBRIS */

class Debris extends Thread {

  private double x;
  private double y;
  private double xx;
  private double yy;
  private double dx;
  private double dy;
  private double caosX;
  private int temp;   // 0-100
  private boolean activo;
  private Color color;

  static double gravedad=0.1;
  static int enfriamiento=3;


  /** Crea un nuevo punto partiendo en la coordenada x,y con el desplazamiento 
    * inicial de dx y dy, con la temperatura inicial temp. CaosX indica la variabilidad
    * que sufrira en dx. Dy siempre cambiara segun la gravedad
    */

  public Debris(double x, double y, double dx, double dy, int temp, double caosX) {
     this.x=x;
     this.y=y;
     this.xx=x;
     this.yy=y;
     this.dx=dx;
     this.dy=dy;
     this.temp=temp;
     this.caosX=caosX;
     activo=true;
   }

   public void run() {
     while (activo) {
       temp=temp-enfriamiento;
       dx=dx+(caosX*Math.random()-caosX/2);
       dy=dy+gravedad;
       if (y+dy>Seeker.height) dy=-dy;  // Choque contra el suelo
       xx=x;
       yy=y;
       x=x+dx;
       y=y+dy; 
       if (temp>60) color=Color.white;
         else if (temp>30) color=Color.red;
                    else if (temp>0) color=Color.yellow;
                               else color=Color.black;
       dibujaDebris(); 
       if (temp<=0) activo=false;
       try {
         sleep(10);
       } catch (InterruptedException e) {
       }
     }
   }

   void dibujaDebris() {
       int X=(int)Math.round(x);
       int Y=(int)Math.round(y);
       int XX=(int)Math.round(xx);
       int YY=(int)Math.round(yy);     
       synchronized(Seeker.CG) {
            Seeker.CG.setColor(Color.black);
            Seeker.CG.fillRect(XX,YY,2,2); 
            Seeker.CG.setColor(this.color);
            Seeker.CG.fillRect(X,Y,2,2); 
       }
   }


}
