/* Test Applet by Timothy M. Radonich from examples in 
"Black Art of JAVA Game Programming" 
by Joel Fan, Eric Ries and Calin Tenitchi	Copyright (c) 1996 by The Waite Group
and/or
"1001 Java Programmer's Tips" 
by Mark C. Chan, Steven W. Griffith, and Anthony F. Iasi
Copyright (c) by Jamsa Press

all Rights reserved Copyright (c) 1997 Timothy M. Radonich

*/

import java.applet.*;
import java.awt.*;

/////////////////////////////////////////////////////////////////
public class Bounce extends Applet implements Runnable {
  
  Thread animation;
  
  Graphics offscreen;
  Image image;


  static final int NUM_SPRITES = 9;       
  static final int REFRESH_RATE = 80; // in ms

  Sprite sprites[];                    // sprite array
  int width, height;                   // applet dimensions

  public void init() {
    System.out.println(">> init <<");
    setBackground(Color.white);        // applet background
    width = bounds().width;            // set applet dimensions
    height = bounds().height;
    initSprites();
    image = createImage(width,height); // make offscreen buffer
    offscreen = image.getGraphics();
 }

  public void initSprites() {

    sprites = new Sprite[NUM_SPRITES]; // init sprite array
    
    // define sprite for border
    sprites[0] = new RectSprite(0,0,width-1,height-1,Color.green);

    sprites[1] = new BouncingOval(0,0,35,35,Color.yellow,
				  width-1,height-1);

    sprites[2] = new BouncingOval(17,17,50,50,Color.red,
				  width-1,height-1);

    sprites[3] = new BouncingOval(6,25,30,30,Color.orange,
				  width-1,height-1);

    sprites[4] = new BouncingOval(78,17,13,13,Color.green,
				  width-1,height-1);

    sprites[5] = new BouncingOval(0,0,40,40,Color.red,
				  width-1,height-1);

    sprites[6] = new BouncingOval(17,17,25,25,Color.blue,
				  width-1,height-1);

    sprites[7] = new BouncingOval(6,25,22,22,Color.green,
				  width-1,height-1);

    sprites[8] = new BouncingOval(78,17,45,45,Color.green,
				  width-1,height-1);

    ((Moveable)sprites[1]).setVelocity(4,3);
    ((Moveable)sprites[2]).setVelocity(6,2);
    ((Moveable)sprites[3]).setVelocity(4,5);
    ((Moveable)sprites[4]).setVelocity(1,2);
    ((Moveable)sprites[5]).setVelocity(10,3);
    ((Moveable)sprites[6]).setVelocity(6,4);
    ((Moveable)sprites[7]).setVelocity(4,3);
    ((Moveable)sprites[8]).setVelocity(1,9);
    ((Sprite2D)sprites[1]).setFill(true);  // fill this sprite
    ((Sprite2D)sprites[2]).setFill(true);  // fill this sprite
    ((Sprite2D)sprites[3]).setFill(true);  // fill this sprite
    ((Sprite2D)sprites[4]).setFill(true);  // fill this sprite
    ((Sprite2D)sprites[5]).setFill(true);  // fill this sprite
    ((Sprite2D)sprites[6]).setFill(true);  // fill this sprite
    ((Sprite2D)sprites[7]).setFill(true);  // fill this sprite
    ((Sprite2D)sprites[8]).setFill(true);  // fill this sprite

  }

  public void start() {

    System.out.println(">> start <<");
    animation = new Thread(this);
     if (animation != null) {
       animation.start();
     }
  }

  // CALL EACH SPRITE'S update() METHOD
  // DYNAMIC METHOD BINDING OCCURS HERE!
  public void updateSprites() {
    for (int i=0; i<sprites.length; i++) {
      sprites[i].update();          // call each sprite's
                                    //    update() method
    }
  }



  // override update so it doesn't erase screen
  public void update(Graphics g) {
    paint(g);
  }


  //
  public void paint(Graphics g) {

    offscreen.setColor(Color.white);
    offscreen.fillRect(0,0,width,height);  // clear buffer

    for (int i=0; i<sprites.length; i++) {
      sprites[i].paint(offscreen);     // paint each rectangle
    }

    g.drawImage(image,0,0,this);
  }

  public void run() {
    while (true) {
      repaint();
      updateSprites();
      try {
	Thread.sleep (REFRESH_RATE);
      } catch (Exception exc) { };
    }
  }



  public void stop() {

    System.out.println(">> stop <<");
    if (animation != null) {
      animation.stop();
      animation = null;
    }
  }

}


  
// Moveable interface. Note that the declarations public 
// and abstract are optional. All methods specified in an
// interface are automatically public and abstract!

interface Moveable {
  public abstract void setPosition(int c, int d);
  public abstract void setVelocity(int x, int y);
  public abstract void updatePosition();
}


/////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////
class OvalSprite extends Sprite2D {

  protected int width, height;    // dimensions of rectangle

  
  public OvalSprite(int x,int y,int w,int h,Color c) {
    locx = x;
    locy = y;
    width = w;
    height = h;
    color = c;
    fill = false;                 // default: don't fill
    restore();                    // restore the sprite
  }

  // provide implementation of abstract methods:

  public void update() {
   
    // does nothing

  }


  // check if sprite's visible before painting
  public void paint(Graphics g) {
    if (visible) {
      g.setColor(color);

      if (fill) {
	g.fillOval(locx,locy,width,height);
      }

      else {
	g.drawOval(locx,locy,width,height);
      }

    }

  }

}
  
/////////////////////////////////////////////////////////////////
class BouncingOval extends OvalSprite implements Moveable {
  
  // the coords at which
  //  the rectangle bounces  
  protected int max_width;
  protected int max_height;    

  // sprite velocity. used to implement Moveable interface
  protected int vx;
  protected int vy;

  public BouncingOval(int x,int y,int w,int h,Color c,
		      int max_w,int max_h) {
    super(x,y,w,h,c);
    max_width = max_w;
    max_height = max_h;
  }

  public void setPosition(int x,int y) {
    locx = x;
    locy = y;
  }

  public void setVelocity(int x,int y) {
    vx = x;
    vy = y;
  }

  // update position according to velocity
  public void updatePosition() {
    locx += vx;
    locy += vy;
  }

  // move and bounce rectangle if it hits borders
  public void update() {

    
    // flip x velocity if it hits left or right bound
    if ((locx + width > max_width) ||
	locx < 0) {
      vx = -vx;
      }

    // flip y velocity if it hits top or bottom bound
    if ((locy + height > max_height) ||
	locy < 0) {
      vy = -vy;
      }
    updatePosition();
  }
}

// Sprite class
//
// Sprites are objects which can be displayed
//



/////////////////////////////////////////////////////////////////
abstract class Sprite {
  protected boolean visible;           // is sprite visible
  protected boolean active;            // is sprite updatable

  // abstract methods:
  abstract void paint (Graphics g);
  abstract void update();

  // accessor methods:
  public boolean isVisible() {
    return visible;
  }

  public void setVisible(boolean b) {
    visible = b;
  }

  public boolean isActive() {
    return active;
  }

  public void setActive(boolean b) {
    active = b;
  }

  // suspend the sprite
  public void suspend() {
    setVisible(false);
    setActive(false);
  }

  // restore the sprite
  public void restore() {
    setVisible(true);
    setActive(true);
  }

}


abstract class Sprite2D extends Sprite {

  protected int locx;   
  protected int locy;

  Color color;
  boolean fill; 

  public boolean getFill() {
    return fill;
  }

  public void setFill(boolean b) {
    fill = b;
  }

  public void setColor(Color c) {
    color = c;
  }

  public Color getColor() {
    return color;
  }


}

/////////////////////////////////////////////////////////////////
class RectSprite extends Sprite2D {

  protected int width, height;    // dimensions of rectangle

  
  public RectSprite(int x,int y,int w,int h,Color c) {
    locx = x;
    locy = y;
    width = w;
    height = h;
    color = c;
    fill = false;                 // default: don't fill
    restore();                    // restore the sprite
  }

  // provide implementation of abstract methods:

  public void update() {
   
    // does nothing

  }


  // check if sprite's visible before painting
  public void paint(Graphics g) {
    if (visible) {
      g.setColor(color);

      if (fill) {
	g.fillRect(locx,locy,width,height);
      }

      else {
	g.drawRect(locx,locy,width,height);
      }

    }

  }

}
  

