/////////////////////////////
/* B  A  L  L   P  A  R  K */
/* Written by Andrew Broad */
/*-------------------------*/
/* (Version 1.0)           */
/* (2000:04:26 15:43)      */
/////////////////////////////

import java.applet.Applet;
import java.awt.*;
import java.awt.Frame; //I have another class called Frame in my CLASSPATH :-(
import java.awt.event.*;
import java.util.*;


public class Ballpark extends Applet {
	Court court;
	
	public void init () {
		setLayout(new BorderLayout());
		court= new Court();
		add("Center", court);
	}
	
	public void start () {
		court.start();
	}

	public static void main(String args[]) {
		Frame f= new Frame("Ballpark");
		Ballpark bp= new Ballpark();
		
		bp.init();

		f.add("Center", bp);
		f.setSize(512,416);
		f.show();
		
		bp.start();
	}
}


class Court extends Panel implements MouseMotionListener {
	Bat bat;
	Ball ball;
	Wall wall;
	Color background_colour= Color.green;
	int score;

	public Court() {
		this.addMouseMotionListener(this);
		setBackground(background_colour);
		bat= new Bat(this);
		ball= new Ball(this);
		wall= new Wall(this);
	}
	
	public void start () {
		repaint();
		new Thread(ball).start();
	}
	
	public void update (Graphics g) {
		if (bat.to_draw) {
			bat.draw(g);
		}
		if (ball.to_draw) {
			ball.draw(g);
		}
		if (wall.to_draw) {
			wall.draw(g);
		}
	}
	
	public void paint (Graphics g) {
		update (g);
	}

	public void mouseMoved (MouseEvent me) {
		bat.moveHorizontal(me.getX());
	}
	
	public void mouseDragged (MouseEvent me) {}
}




abstract class GameObject {
	protected Court the_court;
	protected Point top_left;
	protected Point old_top_left;
	int width;
	int height;
	protected Color colour;
	boolean to_draw= true;
	
	abstract void draw (Graphics g);
	
	protected void setCentre (Point p) {
		top_left.x= p.x - width/2;
		top_left.y= p.y - height/2;
	}

	public Point getCentre () {
		return new Point (top_left.x + width/2, top_left.y + height/2);
	}
	
	public boolean touching (GameObject that) {

		return (this.top_left.y <= that.top_left.y
		        && this.top_left.y + this.height >= that.top_left.y
		     || this.top_left.y >= that.top_left.y
		        && that.top_left.y + that.height >= this.top_left.y)

		    && (this.top_left.x <= that.top_left.x
		        && this.top_left.x + this.width >= that.top_left.x
		     || this.top_left.x >= that.top_left.x
		        && that.top_left.x + that.width >= this.top_left.x);
	}
}


abstract class RectObject extends GameObject {
	public void draw (Graphics g) {
		g.setColor(the_court.background_colour);
		g.fillRect(old_top_left.x, old_top_left.y, width, height);
		g.setColor(colour);
		g.fillRect(top_left.x, top_left.y, width, height);
		old_top_left= new Point(top_left); to_draw= false;
	}
}


class Bat extends RectObject {
	public Bat (Court court) {
		the_court= court;
		top_left= old_top_left= new Point(224,368);
		width= 64; height= 16;
		colour= Color.white;
	}
	
	public void moveHorizontal (int mouse_x) {
		top_left.x= mouse_x - width/2;
		to_draw= true; the_court.repaint();
	}
}


class Ball extends GameObject implements Runnable {
	private double horizontal_velocity;
	private double vertical_velocity;
	
	public Ball (Court court) {
		the_court= court;
		top_left= old_top_left= new Point(248,184);
		height= width= 16;
		colour= Color.yellow;
		horizontal_velocity= 0; vertical_velocity= 16;
	}
	
	public void draw (Graphics g) {
		g.setColor(the_court.background_colour);
		g.fillOval(old_top_left.x, old_top_left.y, width, height);
		g.setColor(colour);
		g.fillOval(top_left.x, top_left.y, width, height);
		old_top_left= new Point(top_left); to_draw= false;
	}
	
	public void run () {
		while (true) {
			try {
				Thread.sleep(40);
			} catch (InterruptedException e) {
				return;
			}
			
			top_left.x+= horizontal_velocity;
			top_left.y+= vertical_velocity;
			if (top_left.x < 0) {
				bounce_horizontally();
				top_left.x= 1;
			} else if (top_left.x > 512) {
				bounce_horizontally();
				top_left.x= 512 - width;
			}
			if (top_left.y < 0) {
				bounce_vertically();
				top_left.y= 1;
			}
			if (touching(the_court.bat)) {
				bounce_off_bat();
				top_left.y= the_court.bat.top_left.y - height ;
			}
			if (touching(the_court.wall)) {
				the_court.wall.collision_detection(this);
			}
			if ((int)vertical_velocity == 0) {
				vertical_velocity= horizontal_velocity;
				horizontal_velocity= 0;
			}
			to_draw= true; the_court.repaint();
		}
	}
	
	public void bounce_horizontally () {
		top_left.x-= horizontal_velocity;
		horizontal_velocity= -horizontal_velocity;
	}
	
	public void bounce_vertically () {
		top_left.y-= vertical_velocity;
		vertical_velocity= -vertical_velocity;
	}
	
	private void bounce_off_bat () {
		double angle;
		double speed;
		
		angle= (getCentre().x - the_court.bat.top_left.x) * Math.PI
		     /  the_court.bat.width;
		speed= Math.sqrt(Math.pow(vertical_velocity,2)
		               + Math.pow(horizontal_velocity,2));
		vertical_velocity=   -(speed * Math.sin(angle));
		horizontal_velocity= -(speed * Math.cos(angle));
	}
}


class Wall extends GameObject {
	Brick bricks[][];
	Vector bricks_to_draw;
	
	public Wall (Court court) {
		the_court= court;
		top_left= old_top_left= new Point(0,80);
		width= 512; height= 80;
		bricks= new Brick[5][16];
		bricks_to_draw= new Vector();
		for (int y= 0; y < 5; y++)
			for (int x= 0; x < 16; x++) {
				bricks[y][x]= new Brick(court,this,y,x);
				bricks_to_draw.addElement(bricks[y][x]);
		}
	}
	
	public void draw (Graphics g) {
		Enumeration enumeration_of_bricks_to_draw;
		
		enumeration_of_bricks_to_draw= bricks_to_draw.elements();
		while (enumeration_of_bricks_to_draw.hasMoreElements()) {
			Brick e= (Brick)enumeration_of_bricks_to_draw.nextElement();
			e.draw(g);
		}
		bricks_to_draw.removeAllElements();
		to_draw= false;
	}
	
	public void collision_detection (Ball ball) {
		boolean bounce= false;
		int y= ball.top_left.y / 16 - 5;
		int x= ball.top_left.x / 32;
		
		if (y>=0 && y<5) if (!(bricks[y][x].knocked_out)) {
			bricks[y][x].knock_out();
			bounce= true;
		} else if (x<15 && !(bricks[y][x+1].knocked_out)) {
			bricks[y][x+1].knock_out();
			bounce= true;
		} else if (y>1 && !(bricks[y-1][x].knocked_out)) {
			bricks[y-1][x].knock_out();
			bounce= true;
		}
		if (bounce) {
			ball.bounce_vertically();
		}
	}
}


class Brick extends RectObject {
	Wall the_wall;
	boolean knocked_out;
	
	public Brick (Court court, Wall wall, int y, int x) {
		the_court= court; the_wall= wall;
		top_left= old_top_left= new Point(32*x,16*y + 81);
		width= 31; height= 15;
		if (y == 0) {
			colour= Color.blue;
		} else if (y == 1) {
			colour= Color.magenta;
		} else if (y == 2) {
			colour= Color.pink;
		} else if (y == 3) {
			colour= Color.orange;
		} else if (y == 4) {
			colour= Color.red;
		}
		knocked_out= false;
	}
	
	public void knock_out () {
		knocked_out= true;
		colour= the_court.background_colour;
		the_court.score++;
		the_wall.to_draw= true;
		the_wall.bricks_to_draw.addElement(this);
		the_court.repaint();
	}
}
