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(); } }