import java.awt.*;

public class BeamApplet extends java.applet.Applet 
{
	int gridWidth;
	int gridHeight;
	boolean grid[][];
	Image offscreen;
	// A reference to the player's beam, to pass
	// keyboard events to.
	PlayerBeam playerBeam;
	// An array of all the beams in existence, so
	// the 'r'estart key can be implemented.
	Beam beams[];
	// How many beams are left.
	int beamsLeft;
	// Time to sleep between steps. This is shortened
	// greatly when the human player is out of the game.
	int sleepTime;
	boolean pause = false;
	public void init()
	{
		restart();
	}
	void restart()
	{
		// If this isn't the first time (the array of
		// beams *has* been created before) then stop all of
		// the existing beams now.
		stopAllBeams();
		// OK, create a new grid. Check the bounds
		// of the applet to figure out how
		// large it should be.
		gridWidth = (bounds().width) / 4;
		gridHeight = (bounds().height) / 4;
		grid = new boolean[gridWidth][gridHeight];	
		// Create a new offscreen image, the same size
		// as the applet.
		offscreen = createImage(bounds().width, bounds().height);
		// Make an array to keep references to the beams in.
		beams = new Beam[3];
		// Counter of beams remaining.
		beamsLeft = 3;
		// Time to sleep between steps (in milliseconds).
		sleepTime = 100;
		// Create a few beams, one of which is a player beam.	
		// Keep a reference to the player beam so we can
		// pass keypresses to it.
		playerBeam = new PlayerBeam(this, Color.pink, Color.red);
		beams[0] = playerBeam;
		// We don't need to keep references to these. They can
		// look out for themselves.
		beams[1] = new Beam(this, Color.cyan, Color.blue);
		beams[2] = new Beam(this, Color.orange, Color.yellow);
	}
	public void update(Graphics g)
	{
		paint(g);		
	}
	synchronized public void paint(Graphics g)
	{
		// Just copy the offscreen image to the screen.
		g.drawImage(offscreen, 0, 0, this);
	}		
	public boolean keyDown(Event evt, int key)
	{
		// Is it the 'r' key?
		if (key == (int) 'r') {
			restart();
			return true;
		}
		// Okay, try passing it to the player beam,
		// if there still is one.
		if (playerBeam != null) {
			return playerBeam.keyDown(evt, key);	
		}
		// Okay, just give up!
		return false;
	}
	// We synchronize this so the count will be accurate
	// even if two beams try to go away at once.
	synchronized void beamGoodbye(Beam b)
	{
		int i;
		// Forget about the beam that lost.
		for (i = 0; (i < beams.length); i++) {
			if (beams[i] == b) {
				beams[i] = null;
			}
		}	
		beamsLeft--;
		if (beamsLeft == 1) {
			// Game over -- the last survivor wins!
			gameOver();
		}
	}
	// This method is called before beamGoodbye if the
	// beam belonged to a human player.
	synchronized void playerIsDead()
	{
		playerBeam = null;
		// Greatly speed up the game now
		sleepTime = 20;
	}
	void gameOver()
	{
		// Find out who's left.
		int i;
		Beam winner = null;
		for (i = 0; (i < beams.length); i++) {
			if (beams[i] != null) {
				winner = beams[i];
				break;
			}
		}
		// Stop the winner.
		winner.stop();
		// Draw a 'game over' screen,
		// in the winner's colors.
		Graphics g = offscreen.getGraphics();
		g.setColor(winner.tailColor);
		g.fillRect(
			0, 
			0, 
			bounds().width,
			bounds().height);
		g.setColor(winner.headColor);
		// Create a font object for a big point size.
		Font f = new Font(	
			"Times Roman", Font.PLAIN, 24);
		g.setFont(f);
		// Get the metrics (size information)
		// for this font
		FontMetrics fm = getFontMetrics(f);
		// Figure out how big 'GAME OVER' is.
		int w = fm.stringWidth("GAME OVER");
		int h = fm.getAscent();
		// Now draw it, centered.
		g.drawString("GAME OVER", 
			(bounds().width - w) / 2,
			(bounds().height - h) / 2);
		// Finally, ask for a repaint so
		// the user can see this. The GAME OVER
		// display stays in place until the user
		// hits the 'r' key for a new game.
		repaint();	
	}
	public void start()
	{
		// This method is called when the user
		// returns to this web page, and also once
		// right after init(). Clear the pause flag.
		pause = false;
	}
	public void stop()
	{
		// This method is called when the user
		// leaves this web page. Set the pause flag.
		pause = true;
	}
	void stopAllBeams()
	{
		if (beams != null) {
			int i;
			for (i = 0; (i < beams.length); i++) {
				if (beams[i] != null) {
					beams[i].stop();
				}
			}
		}
	}
	public void destroy()
	{
		// This method is called when the browser
		// wants to throw this web page out of
		// cache, and the applet therefore
		// should clean itself up. It is our
		// job to stop our threads.
		stopAllBeams();
	}
}

// A beam, with its own thread. 

class Beam implements Runnable
{
	// Color to paint this beam
	Color headColor;
	Color tailColor;
	// The applet, which contains the grid
	BeamApplet beamApplet;
	// The current direction
	int direction;
	// The possible directions. These are
	// numbered clockwise so incrementing and
	// decrementing them will turn.
	static final int up = 0;
	static final int right = 1;
	static final int down = 2;
	static final int left = 3;
	// The position in the grid
	int x;
	int y;
	// The thread of execution that moves this beam.
	Thread myThread;
	Beam(BeamApplet beamAppletArg, 
		Color headColorArg,
		Color tailColorArg)
	{
		beamApplet = beamAppletArg;
		headColor = headColorArg;
		tailColor = tailColorArg;
		direction = randomBetween(0, 3);
		x = randomBetween(0, beamApplet.gridWidth - 1);
		y = randomBetween(0, beamApplet.gridHeight - 1);
		beamApplet.grid[x][y] = true;
		draw(headColor);
		myThread = new Thread(this);
		myThread.start();
	}
	public void run()
	{
		while(true) {
			if (beamApplet.pause) {
				// Sleep a lot when paused
				try {
					myThread.sleep(100);
				} catch (Exception e) {
					// We're not concerned.
				}
			} else {
				draw(headColor);
				think();
				move();
				draw(tailColor);
				// Now ask for a repaint
				beamApplet.repaint();
				sleep();		
			}
		}
	}
	void draw(Color c)
	{
		// Get a pen
		Graphics g = beamApplet.offscreen.getGraphics();
		// Set the color
		g.setColor(c);
		// Draw the beam. (The screen is 4 times bigger
		// than the grid of possible locations.)
		g.fillRect(x * 4, y * 4, 4, 4);
	}
	void sleep()
	{
		// Catch possible thread related exceptions (sigh)
		try {
			myThread.sleep(beamApplet.sleepTime);
		} catch (Exception e) {
			// We're not concerned.
		}
	}
	void turnClockwise()
	{
		direction++;
		if (direction == 4) {
			direction = up;
		}
	}
	void turnCounterclockwise()
	{
		// Counterclockwise
		direction--;
		if (direction == -1) {
			direction = left;
		}
	}
	void think()
	{
		Point p = nextPosition();
		// Is this a good place to go?
		if (beamApplet.grid[p.x][p.y]) {
			// Uh-oh. Try turning.
			// Save the old direction first.
			int oldDirection = direction;
			// Now randomly choose whether to prefer
			// clockwise or counterclockwise turns.
			// If the first looks dangerous, 
			// try the other instead.
			if (Math.random() < .5) {
				// Clockwise
				turnClockwise();
				// Take a peek
				p = nextPosition();			
				if (beamApplet.grid[p.x][p.y]) {
					// Counterclockwise
					direction = oldDirection;
					turnCounterclockwise();
				}	
			} else {
				// Counterclockwise
				turnCounterclockwise();
				// Take a peek
				p = nextPosition();			
				if (beamApplet.grid[p.x][p.y]) {
					// clockwise
					direction = oldDirection;
					turnClockwise();
				}		
			}	
		}
	}
	void move()
	{
		Point p = nextPosition();
		// Move now.
		x = p.x;
		y = p.y;
		// If this space is already occupied -- we lose!
		if (beamApplet.grid[x][y]) {
			// Oops! Goodbye!
			// One less beam; say goodbye...
			goodbye();
			// And now we stop
			stop();
		}	
		// Mark this space as occupied.
		beamApplet.grid[x][y] = true;
	}
	// What would the next position be?
	Point nextPosition()
	{
		int nx = x;	
		int ny = y;	
		if (direction == up) {
			ny--;
			if (ny < 0) {
				ny = beamApplet.gridHeight - 1;
			}	
		} else if (direction == down) {
			ny++;
			if (ny >= beamApplet.gridHeight) {
				ny = 0;
			}	
		} else if (direction == left) {
			nx--;
			if (nx < 0) {
				nx = beamApplet.gridWidth - 1;
			}			
		} else if (direction == right) {
			nx++;
			if (nx >= beamApplet.gridWidth) {
				nx = 0;
			}	
		}
		return (new Point(nx, ny));
	}
	// A random integer between these two (inclusive)
	int randomBetween(int low, int high)
	{
		int value = (int) (Math.random() * (high - low + 1) + low);
		// Just in case "any value between 0.00 and 1.00" actually
		// turns out to be 1.00, which isn't very likely...
		if (value == high + 1) {
			value = high;
		}
		return value;
	}
	// Stop this beam now!
	void stop()
	{
		if (myThread != null) {
			myThread.stop();
			myThread = null;
		}
	}
	// Say goodbye to the applet.
	void goodbye() {
		beamApplet.beamGoodbye(this);
	}
}

class PlayerBeam extends Beam
{
	PlayerBeam(BeamApplet beamAppletArg, 
		Color headColorArg,
		Color tailColorArg)
	{	
		// Initialize the parent class
		super(beamAppletArg, headColorArg, tailColorArg);
	}
	// The applet will call this when it gets a keystroke --
	// see the keyDown method in the BeamApplet class.
	boolean keyDown(Event evt, int key)
	{
		if (key == (int) 'j') {
			turnCounterclockwise();
			return true;
		} else if (key == (int) 'k') {
			turnClockwise();
			return true;
		}
		// We didn't do anything with this key.
		return false;
	}
	// Our version of 'think' does nothing! This leaves
	// it up to the player to save the day.
	void think() 
	{
	}
	// Say goodbye to the applet -- and let it know the 
	// beam that died was a human player.
	void goodbye() {
		// Let the game know that a player's beam has died.
		beamApplet.playerIsDead();
		// Call the 'original' version in our superclass also.
		super.goodbye();
	}
}

