/* ---------------------------------------------------------------------

Breakout Applet

Copyright (c) 1996 by The Regents of The University of Michigan.
All rights reserved.

Permission to use, copy, modify, and distribute this software
and its documentation for NON-COMMERCIAL purposes and without
fee is hereby granted provided that this copyright notice
appears in all copies.

File:		BreakoutApplet.java
Synopsis:	A simple game of breakout
Created:	1996/09/19 waterson
Modified:	1996/09/19 waterson

Notes
-----

1. There are some sloppy clipped paints that aren't right; e.g., when
the ball falls down and is first re-displayed on the paddle.

2. Should figure out how to wrap up the Block, Ball, and Paddle into
classes.

--------------------------------------------------------------------- */

import java.util.*;
import java.applet.*;
import java.awt.*;


/**
 * A simple breakout game.
 */

public class BreakoutApplet
extends java.applet.Applet
implements java.lang.Runnable
{
	/**
	 * The width of the paddle
	 */

	static final int PADDLE_WIDTH = 40;

	/**
	 * Half the width of the paddle, kept here just because we use it so
	 * much.
	 */

	static final int HALF_PADDLE_WIDTH = PADDLE_WIDTH / 2;


	/**
	 * The height of the paddle in pixels.
	 */

	static final int PADDLE_HEIGHT = 10;


	/**
	 * The width of a block
	 */

	static final int BLOCK_WIDTH = 19;


	/**
	 * The height of a block
	 */

	static final int BLOCK_HEIGHT = 10;


	/**
	 * The first row of blocks starts here.
	 */

	static final int TOPROW = 2;


	/**
	 * The total number of rows of blocks (including TOPROW).
	 */

	static final int ROWS = 6;


	/**
	 * The step size of the ball
	 */

	static final int STEP = 4;


	/**
	 * The ball's image
	 */

	Image m_ballImage;


	/**
	 * A double buffer for smooth graphics
	 */

	Image m_buffer;


	/**
	 * The handle of the graphics context for the double buffer
	 */

	Graphics m_bufferGraphics;


	/**
	 * The paddle's x-coordinate
	 */

	int m_paddleX;


	/**
	 * The ball's x-coordinate
	 */

	int m_ballX;


	/**
	 * The ball's y-coordinate
	 */

	int m_ballY;


	/**
	 * The ball's x velocity
	 */

	int m_ballDx;


	/**
	 * The ball's y velocity
	 */

	int m_ballDy;


	/**
	 * A background thread for animating the ball
	 */

	Thread m_animationThread;


	/**
	 * Set to true when the ball is in motion
	 */

	boolean m_ballIsBouncing;


	/**
	 * The blocks
	 */

	Block m_blocks[];


	/**
	 * The total number of blocks in a row
	 */

	int m_blocksPerRow;



	/**
	 * Initialize the applet. Set some basic parameters, load in required
	 * images.
	 */

	public void
	init() {
		Dimension d = size();
		m_paddleX = d.width / 2;

		setBackground(Color.black);

		m_ballImage = getImage(getDocumentBase(), "ball.gif");
		prepareImage(m_ballImage, this);
	}


	/**
	 * Called whenever the applet is activated. Start the animation
	 * thread (if one exists). Reset the ball to it's startup location
	 * and create a new set of blocks.
	 */

	public void
	start() {
		Dimension d = size();

		m_ballIsBouncing = false;
		m_ballX = 0;
		m_ballY = d.height - BLOCK_HEIGHT;

		m_blocksPerRow = d.width / BLOCK_WIDTH;
		m_blocks = new Block[m_blocksPerRow * ROWS];

		for (int j = TOPROW; j < ROWS; ++j) {
		for (int i = 0; i < m_blocksPerRow; ++i) {
			int x = i * BLOCK_WIDTH;
			int y = j * BLOCK_HEIGHT;

			m_blocks[j * m_blocksPerRow + i] =
				new Block(x, y, BLOCK_WIDTH, BLOCK_HEIGHT);
		} }

		redraw();

		if (m_animationThread == null || ! m_animationThread.isAlive()) {
			m_animationThread = new Thread(this);
			m_animationThread.start();
		} else {
			m_animationThread.resume();
		}
	}


	/**
	 * Called whenever the applet is deactivated. Suspend the
	 * animation thread.
	 */

	public void
	stop() {
		if (m_animationThread != null && m_animationThread.isAlive()) {
			m_animationThread.suspend();
		}
	}



	/**
	 * Called when the applet is completely removed from the browser.
	 * Force the animation thread to stop.
	 */

	public void
	destroy() {
		if (m_animationThread != null && m_animationThread.isAlive()) {
			m_animationThread.stop();
		}
	}


	/**
	 * Called when the applet is first laid out on the screen. Create
	 * a double buffer for drawing graphics.
	 */

	public void
	layout() {
		super.layout();

		Dimension d = size();
		if (d.width <= 0 || d.height <= 0) return;

		m_buffer = createImage(d.width, d.height);

		if (m_buffer == null) return;
			// Could happen if the peer isn't created yet.

		if (m_bufferGraphics != null) m_bufferGraphics.dispose();
		m_bufferGraphics = m_buffer.getGraphics();

		redraw();
	}



	/**
	 * Track the mouse by moving the paddle cursor
	 */

	public boolean
	mouseMove(Event event, int x, int y) {
		Dimension d = size();

		x = Math.max(x, HALF_PADDLE_WIDTH);
		x = Math.min(x, d.width - HALF_PADDLE_WIDTH);

		movePaddle(x);

		return true;
	}


	/**
	 * The user must click the mouse to start the ball in motion
	 */

	public boolean
	mouseDown(Event event, int x, int y) {
		if (! m_ballIsBouncing) {
			m_ballDx = STEP;
			m_ballDy = -STEP;

			m_ballIsBouncing = true;
		}

		return true;
	}


	public synchronized void
	redraw() {
		if (m_bufferGraphics == null) return;

		Dimension d = size();

		m_bufferGraphics.setColor(getBackground());
		m_bufferGraphics.fillRect(0, 0, d.width, d.height);

		if (m_blocks != null) {
			for (int j = 0; j < ROWS; ++j) {
			for (int i = 0; i < m_blocksPerRow; ++i) {
				Block block = m_blocks[j * m_blocksPerRow + i];
				if (block != null) block.draw(m_bufferGraphics);
			} }
		}

		movePaddle(m_paddleX);
		moveBall(m_ballX, m_ballY);
	}



	/**
	 * Move the paddle to the specified x-coordinate, re-drawing the
	 * double-buffer as necessary. Request a clipped paint, too.
	 */

	protected synchronized void
	movePaddle(int x) {
		Dimension d = size();

		m_paddleX = x;

		m_bufferGraphics.setColor(getBackground());
		m_bufferGraphics.fillRect(0, d.height - PADDLE_HEIGHT, d.width, PADDLE_HEIGHT);

		m_bufferGraphics.setColor(Color.gray);
		m_bufferGraphics.fill3DRect(m_paddleX - HALF_PADDLE_WIDTH,
				d.height - PADDLE_HEIGHT,
				PADDLE_WIDTH,
				PADDLE_HEIGHT,
				true);

		repaint();
	}


	/**
	 * Move the ball to the specified x- and y-coordinates. Redraws the
	 * double buffer appropriately and performs a clipped paint.
	 */

	protected synchronized void
	moveBall(int x, int y) {
		int width = m_ballImage.getWidth(this);
		int height = m_ballImage.getHeight(this);

		Rectangle r = new Rectangle(m_ballX, m_ballY, width, height);

		m_ballX = x;
		m_ballY = y;

		r.add(new Rectangle(x, y, width, height));

		m_bufferGraphics.setColor(getBackground());
		m_bufferGraphics.fillRect(r.x, r.y, r.width, r.height);

		int blockY = y / BLOCK_HEIGHT;

		if (blockY < ROWS) {
			int left  = x / BLOCK_WIDTH;
			int right = Math.min(left + 2, m_blocksPerRow);

			for (int j = 0; j < ROWS; ++j) {
			for (int i = left; i < right; ++i) {
				Block block = m_blocks[j * m_blocksPerRow + i];
				if (block != null) block.draw(m_bufferGraphics);
			} }
		}

		m_bufferGraphics.drawImage(m_ballImage, m_ballX, m_ballY, this);

		repaint();
	}




	/**
	 * Called to paint the applet. Just blt the double buffer to the
	 * screen.
	 */

	public synchronized void
	paint(Graphics g) {
		g.drawImage(m_buffer, 0, 0, this);
	}



	/**
	 * Called when part of the screen needs to be updated. Just perform
	 * a blt of the double buffer -- the Graphics will take care of
	 * clipping.
	 */

	public synchronized void
	update(Graphics g) {
		g.drawImage(m_buffer, 0, 0, this);
	}



	/**
	 * The thread that is used to animate the ball. Bounces the ball around
	 * the screen, or tracks the ball to the paddle if the ball isn't currently
	 * bouncing.
	 */

	public void
	run() {
	try {
		while (true) {
			if (m_ballIsBouncing) {
				bounceBall();
			} else {
				// Just track the paddle
				Dimension d = size();

				moveBall(m_paddleX - m_ballImage.getWidth(this) / 2,
					d.height - PADDLE_HEIGHT - m_ballImage.getHeight(this));
			}

			Thread.currentThread().sleep(10);
		}
	} catch (java.lang.InterruptedException e) {
		// We got interrupted! Just die...
		e.printStackTrace();
	} }



	/**
	 * Bounces the ball appropriately by computing collisions. This
	 * is still a little buggy.
	 */

	protected void
	bounceBall() {
		Dimension d = size();

		int ballWidth = m_ballImage.getWidth(this);
		int ballHeight = m_ballImage.getHeight(this);

		// Where the ball will be next
		int x = m_ballX + m_ballDx;
		int y = m_ballY + m_ballDy;

		if (y / BLOCK_HEIGHT < ROWS)
			checkHit(x, y, ballWidth, ballHeight);

		// Bounces off the side walls
		if (x < 0 || x + ballWidth > d.width) {
			m_ballDx = -m_ballDx;
		}

		// Bounces off the ceiling
		if (y < 0) {
			m_ballDy = -m_ballDy;
		}

		// Bounces off the paddle
		if (x + ballWidth >= m_paddleX - HALF_PADDLE_WIDTH
		&&  x <= m_paddleX + HALF_PADDLE_WIDTH
		&&  y + ballHeight >= d.height - PADDLE_HEIGHT) {
			if (x + ballWidth > m_paddleX - HALF_PADDLE_WIDTH && x <= m_paddleX - HALF_PADDLE_WIDTH / 2) {
				m_ballDx = -STEP;
			} else if (x > m_paddleX - HALF_PADDLE_WIDTH / 2 && x <= m_paddleX) {
				m_ballDx = -(STEP / 2);
			} else if (x > m_paddleX && x <= m_paddleX + HALF_PADDLE_WIDTH / 2) {
				m_ballDx = STEP / 2;
			} else if (x > m_paddleX + HALF_PADDLE_WIDTH / 2 && x <= m_paddleX + HALF_PADDLE_WIDTH) {
				m_ballDx = STEP;
			}
			m_ballDy = -m_ballDy;
		}

		// Falls past paddle
		if (y + ballHeight > d.height) {
			redraw();
			m_ballIsBouncing = false;
			return;
		}

		// Move the ball
		moveBall(m_ballX + m_ballDx, m_ballY + m_ballDy);
	}


	protected void checkHit(int x, int y, int ballWidth, int ballHeight) {
		int left = x / BLOCK_WIDTH;
		int right = (x + ballWidth) / BLOCK_WIDTH;
		int top = y / BLOCK_HEIGHT;
		int bottom = (y + ballHeight) / BLOCK_HEIGHT;

		boolean hit = false;

		for (int j = top; j <= bottom; ++j) {
			if (j >= ROWS) continue;

			for (int i = left; i <= right; ++i) {
				if (right >= m_blocksPerRow) continue;

				int index = j * m_blocksPerRow + i;

				if (m_blocks[index] != null) {
					m_blocks[index] = null;

					m_bufferGraphics.setColor(getBackground());
					m_bufferGraphics.fillRect(i * BLOCK_WIDTH, j * BLOCK_HEIGHT, BLOCK_WIDTH, BLOCK_HEIGHT);

					hit = true;
				}
			}
		}

		if (hit) {
			m_ballDx = -m_ballDx;
			m_ballDy = -m_ballDy;

			repaint();
		}
	}
}



/**
 * A helper class, representing a block on the screen.
 */

class Block {
	public int x, y;
	public int width, height;

	public
	Block(int _x, int _y, int _width, int _height) {
		x = _x;
		y = _y;
		width = _width;
		height = _height;
	}


	public void
	draw(Graphics g) {
		g.setColor(Color.gray);
		g.fill3DRect(x, y, width, height, true);
	}



	public boolean
	contains(int _x, int _y) {
		return (_x >= x && _x < x + width)
		    && (_y >= y && _y < y + height);
	}

}



