/* StarBase.java */ /* * Copyright (C) 1995 Mark Boyns * * StarBase * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ import java.applet.*; import java.awt.*; import java.util.Enumeration; import java.util.Vector; /* Abstract class used for moving objects. */ abstract class Thing extends Thread { StarBase parent; int x; int y; abstract void paint (Graphics g); abstract void erase (Graphics g); abstract void explode (); } /* A star located at x,y with depth z. */ class Star { int x; int y; int z; Star (int x, int y, int z) { this.x = x; this.y = y; this.z = z; } } /* The moving starfield. */ class Starfield extends Thing { private final int SPEED = 3; private final int DELAY = 100; boolean paintme = false; int max_x; int max_y; int max_z; int max_stars; int dir; Star stars[]; Color colors[]; int delay; Starfield (StarBase parent, int max_stars, int dir, int max_x, int max_y, int max_z) { this.parent = parent; this.max_stars = max_stars; this.max_x = max_x; this.max_y = max_y; this.max_z = max_z; start (); } public void run () { int i; int c; Thread.currentThread().setPriority (Thread.MIN_PRIORITY); delay = DELAY; /* Create shades of white/grey used for stars. */ colors = new Color[max_z]; for (i = 0, c = 255; i < max_z; i++) { colors[i] = new Color (c, c, c); c -= 10; if (c < 50) { c = 50; } } /* Create the first set of stars. */ stars = new Star[max_stars]; for (i = 0; i < max_stars; i++) { stars[i] = new Star ((int)(Math.random () * max_x), (int)(Math.random () * max_y), (int)(Math.random () * max_z)); } for (;;) { paintme = true; parent.repaint (); try { Thread.sleep (delay); } catch (InterruptedException e) { } } } public void fast () { delay = 1; } public void paint (Graphics g) { int i; int f; if (!paintme) { return; } /* Move the stars. */ for (i = 0; i < max_stars; i++) { g.setColor (Color.black); g.drawLine (stars[i].x, stars[i].y, stars[i].x+1, stars[i].y+1); g.drawLine (stars[i].x+1, stars[i].y, stars[i].x, stars[i].y+1); f = 1 + (int)(SPEED*(((float)max_z - (float)stars[i].z)/(float)max_z)); dir = parent.curr_dir; switch (dir) { case 0: stars[i].y += f; break; case 1: stars[i].x += f; break; case 2: stars[i].y -= f; break; case 3: stars[i].x -= f; break; } /* Create a new star. */ if (stars[i].x < 0 || stars[i].x > max_x || stars[i].y < 0 || stars[i].y > max_y) { int nx = 0; int ny = 0; switch (dir) { case 0: nx = (int)(Math.random () * max_x); ny = 0; break; case 1: nx = 0; ny = (int)(Math.random () * max_y); break; case 2: nx = (int)(Math.random () * max_x); ny = max_y; break; case 3: nx = max_x; ny = (int)(Math.random () * max_y); break; } stars[i] = new Star (nx, ny, (int)(Math.random () * max_z)); } g.setColor (colors[stars[i].z]); g.drawLine (stars[i].x, stars[i].y, stars[i].x+1, stars[i].y+1); g.drawLine (stars[i].x+1, stars[i].y, stars[i].x, stars[i].y+1); } paintme = false; } public void erase (Graphics g) { } public void explode () { stop (); } } /* Display an explosion image for 1/2 a second. */ class Explosion extends Thing { Image image; Explosion (StarBase parent, Image image, int x, int y) { this.parent = parent; this.x = x; this.y = y; this.image = image; start (); } public void run () { Thread.currentThread().setPriority (Thread.MIN_PRIORITY); parent.repaint (); try { Thread.sleep (500); } catch (InterruptedException e) { } } public void paint (Graphics g) { g.drawImage (image, x - 16, y - 16, parent); } public void erase (Graphics g) { g.setColor (Color.black); g.fillRect (x - 16, y - 16, 32, 32); } public void explode () { stop (); } } /* A moving spaceship which moves from x,y to max_x,max_y. */ class UFO extends Thing { boolean paintme = false; int dir; int max_x; int max_y; int speed; public Image image; boolean hit = false; UFO (StarBase parent, Image image, int x, int y, int dir, int max_x, int max_y, int speed) { this.parent = parent; this.image = image; this.x = x; this.y = y; this.dir = dir; this.max_x = max_x; this.max_y = max_y; this.speed = speed; start (); } public void run () { Thread.currentThread().setPriority (Thread.MIN_PRIORITY); do { paintme = true; parent.repaint (); try { Thread.sleep (100); } catch (InterruptedException e) { } } while (!hit); } public boolean collision (int cx, int cy) { return (cy >= y - 16) && (cy <= y + 16) && (cx >= x - 16) && (cx <= x + 16); } public void paint (Graphics g) { if (!paintme) { return; } g.setColor (Color.black); g.fillRect (x - 16, y - 16, 32, 32); switch (dir) { case 0: y += speed; hit = y >= max_y; break; case 1: x += speed; hit = x >= max_x; break; case 2: y -= speed; hit = y <= max_y; break; case 3: x -= speed; hit = x <= max_x; break; } g.drawImage (image, x - 16, y - 16, parent); paintme = false; } public void erase (Graphics g) { g.setColor (Color.black); g.fillRect (x - 16, y - 16, 32, 32); } public void explode () { stop (); } } /* Laser pulse which moves from x,y to max_x,max_y. */ class Laser extends Thing { private final int SPEED = 20; Color color; boolean paintme = false; int dir; int max_x; int max_y; int length; int start_x; int start_y; Laser (StarBase parent, int x, int y, int length, int dir, int max_x, int max_y) { this.parent = parent; this.color = Color.red; start_x = x; start_y = y; this.length = length; this.dir = dir; this.max_x = max_x; this.max_y = max_y; start (); } public void run () { int i; Thread.currentThread().setPriority (Thread.MIN_PRIORITY); x = start_x; y = start_y; do { paintme = true; parent.repaint (); try { Thread.sleep (100); } catch (InterruptedException e) { } } while (x >= 0 && x <= max_x && y >= 0 && y <= max_y); } public void paint (Graphics g) { if (!paintme) { return; } switch (dir) { case 0: if (y <= start_y - length) { g.setColor (Color.black); g.fillRect (x - 1, y, 3, length); y -= SPEED; g.setColor (color); g.fillRect (x - 1, y, 3, length); } else { y -= SPEED; } break; case 1: if (x <= start_x - length) { g.setColor (Color.black); g.fillRect (x, y - 1, length, 3); x -= SPEED; g.setColor (color); g.fillRect (x, y - 1, length, 3); } else { x -= SPEED; } break; case 2: if (y >= start_y + length) { g.setColor (Color.black); g.fillRect (x - 1, y - length, 3, length); y += SPEED; g.setColor (color); g.fillRect (x - 1, y - length, 3, length); } else { y += SPEED; } break; case 3: if (x >= start_x + length) { g.setColor (Color.black); g.fillRect (x - length, y - 1, length, 3); x += SPEED; g.setColor (color); g.fillRect (x - length, y - 1, length, 3); } else { x += SPEED; } break; } paintme = false; } public void erase (Graphics g) { switch (dir) { case 0: g.setColor (Color.black); g.fillRect (x - 1, y, 3, length); break; case 1: g.setColor (Color.black); g.fillRect (x, y - 1, length, 3); break; case 2: g.setColor (Color.black); g.fillRect (x - 1, y - length, 3, length); break; case 3: g.setColor (Color.black); g.fillRect (x - length, y - 1, length, 3); break; } } public void explode () { stop (); } } /* The applet. */ public class StarBase extends java.applet.Applet implements Runnable { /* Screen size. */ private final int SCREEN_WIDTH = 501; private final int SCREEN_HEIGHT = 501; /* Base and cannon size. */ private final int BASE_WIDTH = 13; private final int CANNON_WIDTH = 5; private final int CANNON_HEIGHT = 8; /* Laser parameters. */ private final int LASER_ENERGY = 4; private final int LASER_LENGTH = 20; private final int MAX_LASERS = 8; /* UFO parameters. */ private final int UFO_POINTS = 100; private final int UFO_DAMAGE = 10; private final int MAX_UFOS = 8; /* Game speed. */ private final int DELAY_MIN = 1000; private final int DELAY_MAX = 3000; private final int DELAY_DECAY = 100; private final int MIN_SPEED = 1; private final int MAX_SPEED = 8; /* Keys */ private int key_clockwise = 'k'; private int key_counter_clockwise = 'j'; private int key_fire = ' '; /* Current and next cannon direction. */ public int curr_dir = 0; private int new_dir = 0; /* Polygons which represent the 4 cannons. */ private Polygon cannons[]; /* Fonts */ private Font font; private FontMetrics fontMetrics; /* Game parameters */ private int score = 0; private int energy = 100; private int shield = 100; private int laserCount = 0; private int ufoCount = 0; private boolean dead = true; private boolean clearScreen = false; /* Vector of moving Things. */ private Vector things = null; /* The main applet's thread. */ private Thread thread = null; /* Images */ private MediaTracker tracker; Image images[]; String image_files[] = { "earth.gif", "jupiter.gif", "mars.gif", "mercury.gif", "neptune.gif", "pluto.gif", "saturn.gif", "uranus.gif", "venus.gif" }; Image energyImage; Image deathImage; Image shieldImage; Image explosionImage; /* Strings */ private final String scoreString = "Score: "; private final String energyString = "Energy: "; private final String shieldString = "Shield: "; private final String welcomeString = "<< Insert Coin >>"; private final String titleString = "Welcome to StarBase"; /* Initialize the applet. */ public void init () { font = new Font ("TimesRoman", Font.BOLD, 24); fontMetrics = getFontMetrics (font); setFont (font); setBackground (Color.black); /* Create the cannons. */ createCannons (); /* Load all images with MediaTracker. */ tracker = new MediaTracker (this); images = new Image[image_files.length]; for (int i = 0; i < images.length; i++) { images[i] = getImage (getCodeBase (), "images/" + image_files[i]); tracker.addImage (images[i], 0); } energyImage = getImage (getCodeBase (), "images/netscape.gif"); tracker.addImage (energyImage, 0); deathImage = getImage (getCodeBase (), "images/skull.gif"); tracker.addImage (deathImage, 0); shieldImage = getImage (getCodeBase (), "images/mozilla.gif"); tracker.addImage (shieldImage, 0); explosionImage = getImage (getCodeBase (), "images/boom.gif"); tracker.addImage (explosionImage, 0); resize (SCREEN_WIDTH, SCREEN_HEIGHT); } /* Start a new game. */ public synchronized void doit () { stopThings (); thread = new Thread (this); thread.start (); } /* The game engine. */ public void run () { int level = 0; int delay = DELAY_MAX; int min_speed = MIN_SPEED; int max_speed = 2; int speed = 0; int dir; int nufos; int i; dead = false; energy = 100; shield = 100; score = 0; laserCount = 0; ufoCount = 0; clearScreen = true; repaint (); /* Wait for the images to load. */ try { showStatus ("Loading images..."); tracker.waitForAll (); } catch (InterruptedException e) { return; } showStatus (""); while (!dead) { /* wait */ try { Thread.sleep (delay); } catch (InterruptedException e) { } nufos = 1 + (int)(Math.random () * 4); for (i = 0; i < nufos; i++) { /* Pick a random direction. */ dir = (int)(Math.random () * 4); /* Pick a random speed. */ speed = min_speed + (int)(Math.random () * max_speed); if (speed > MAX_SPEED) { speed = MAX_SPEED; } /* Pick a UFO. */ if (Math.random () <= 0.10) { createUFO (deathImage, dir, speed); } else if (Math.random () <= 0.30) { createUFO (Math.random () > 0.5 ? energyImage : shieldImage, dir, speed); } else { createUFO (images[(int)(Math.random () * images.length)], dir, speed); } } level++; if ((level % 5) == 0) { delay -= DELAY_DECAY; if (delay < DELAY_MIN) { delay = DELAY_MIN; } } if ((level % 10) == 0) { min_speed += 1; max_speed += 2; } } clearScreen = true; repaint (); } /* Create a starfield with 20 stars and a depth of 20. */ public void createStarfield () { things.addElement (new Starfield (this, 50, curr_dir, SCREEN_WIDTH, SCREEN_HEIGHT, 20)); } /* Create the list of polygons which represent the cannons. */ public void createCannons () { int x = SCREEN_WIDTH / 2; int y = SCREEN_HEIGHT / 2; int extra = 1; cannons = new Polygon[4]; cannons[0] = new Polygon (); cannons[0].addPoint (x - CANNON_WIDTH, y - BASE_WIDTH - extra); cannons[0].addPoint (x, y - BASE_WIDTH - CANNON_HEIGHT); cannons[0].addPoint (x + CANNON_WIDTH, y - BASE_WIDTH - extra); cannons[1] = new Polygon (); cannons[1].addPoint (x - BASE_WIDTH - extra, y - CANNON_WIDTH); cannons[1].addPoint (x - BASE_WIDTH - CANNON_HEIGHT, y); cannons[1].addPoint (x - BASE_WIDTH - extra, y + CANNON_WIDTH); cannons[2] = new Polygon (); cannons[2].addPoint (x - CANNON_WIDTH, y + BASE_WIDTH + extra); cannons[2].addPoint (x, y + BASE_WIDTH + CANNON_HEIGHT); cannons[2].addPoint (x + CANNON_WIDTH, y + BASE_WIDTH + extra); cannons[3] = new Polygon (); cannons[3].addPoint (x + BASE_WIDTH + extra, y - CANNON_WIDTH); cannons[3].addPoint (x + BASE_WIDTH + CANNON_HEIGHT, y); cannons[3].addPoint (x + BASE_WIDTH + extra, y + CANNON_WIDTH); } /* Create a UFO. */ public void createUFO (Image image, int dir, int speed) { int x = 0; int y = 0; int max_x = 0; int max_y = 0; int w = SCREEN_WIDTH/2; int h = SCREEN_HEIGHT/2; if (ufoCount == MAX_UFOS) { return; } switch (dir) { case 0: x = cannons[dir].xpoints[1]; y = cannons[dir].ypoints[1] - h; break; case 1: x = cannons[dir].xpoints[1] - w; y = cannons[dir].ypoints[1]; break; case 2: x = cannons[dir].xpoints[1]; y = cannons[dir].ypoints[1] + h; break; case 3: x = cannons[dir].xpoints[1] + w; y = cannons[dir].ypoints[1]; break; } max_x = cannons[dir].xpoints[1]; max_y = cannons[dir].ypoints[1]; things.addElement (new UFO (this, image, x, y, dir, max_x, max_y, speed)); ufoCount++; } /* BOOM! */ public void createExplosion (int x, int y) { things.addElement (new Explosion (this, explosionImage, x, y)); } /* Create a laser moving in DIR direction. */ public void createLaser (int dir) { if (laserCount == MAX_LASERS || energy < LASER_ENERGY) { return; } things.addElement (new Laser (this, cannons[dir].xpoints[1], cannons[dir].ypoints[1], LASER_LENGTH, dir, SCREEN_WIDTH, SCREEN_HEIGHT)); laserCount++; energy -= LASER_ENERGY; if (energy < 0) { energy = 0; } } /* Handle mouse events. */ public boolean mouseDown (Event e, int x, int y) { if (dead) { doit (); } return true; } /* Handle keyboard events. */ public boolean keyDown (Event e, int key) { if (dead) { return true; } if (key == key_clockwise) /* move cannon clockwise */ { new_dir = curr_dir + 1; if (new_dir == 4) { new_dir = 0; } repaint (); } else if (key == key_counter_clockwise) /* move cannon counter-clockwise */ { new_dir = curr_dir - 1; if (new_dir == -1) { new_dir = 3; } repaint (); } else if (key == key_fire) /* fire a laser pulse */ { createLaser (curr_dir); } return true; } /* See if this laser hits anything. */ public void laserCollision (Thing laser) { Enumeration e; boolean collision = false; e = things.elements (); while (e.hasMoreElements ()) { Thing thing = (Thing) e.nextElement (); if (thing.isAlive () && thing instanceof UFO) { UFO ufo = (UFO) thing; if (ufo.collision (laser.x, laser.y)) { ufo.explode (); collision = true; score += UFO_POINTS; createExplosion (ufo.x, ufo.y); } } } if (collision) { laser.explode (); } } /* Stop this applet. */ public void stop () { stopAll (); if (thread != null) { thread.stop (); thread = null; } } /* Stop all threads. */ public void stopAll () { if (things != null) { Enumeration e; e = things.elements (); while (e.hasMoreElements ()) { Thing thing = (Thing) e.nextElement (); thing.explode (); } } } /* Stop everything except the starfield. */ public void stopThings () { if (things != null) { Enumeration e; e = things.elements (); while (e.hasMoreElements ()) { Thing thing = (Thing) e.nextElement (); if (!(thing instanceof Starfield)) { thing.explode (); } } } } /* Update all moving objects. */ public void updateThings (Graphics g) { if (things == null || things.size () == 0) { return; } Enumeration e = things.elements (); while (e.hasMoreElements ()) { Thing thing = (Thing) e.nextElement (); if (thing.isAlive ()) { thing.paint (g); if (thing instanceof Laser) { laserCollision (thing); } } else { if (thing instanceof UFO) { ufoCount--; UFO ufo = (UFO) thing; if (ufo.hit && !dead) { if (ufo.image == energyImage) { energy += 25; if (energy > 100) { energy = 100; } } else if (ufo.image == shieldImage) { shield += 25; if (shield > 100) { shield = 100; } } else if (ufo.image == deathImage) { dead = true; } else { createExplosion (ufo.x, ufo.y); shield -= UFO_DAMAGE; if (shield <= 0) { shield = 0; dead = true; } } } } if (thing instanceof Laser) { laserCount--; } thing.erase (g); things.removeElement (thing); } } } /* Draw the energy and shield meters. */ void paintMeters (Graphics g) { int w; int h = fontMetrics.getHeight (); int l = 100; int x = 3; int y = SCREEN_HEIGHT - 50; g.setColor (Color.white); w = fontMetrics.stringWidth (energyString); g.drawString (energyString, x, y + h); g.drawRect (x + w, y, l, h); paintMeter (g, x + w + 2, y + 2, l - 3, h - 3, energy); x = SCREEN_WIDTH - l - w; g.setColor (Color.white); w = fontMetrics.stringWidth (shieldString); g.drawString (shieldString, x, y + h); g.drawRect (x + w, y, l, h); paintMeter (g, x + w + 2, y + 2, l - 3, h - 3, shield); } /* Draw a meter slider. */ void paintMeter (Graphics g, int x, int y, int w, int h, int value) { int m; if (value > 75) { g.setColor (Color.blue); } else if (value > 50) { g.setColor (Color.green); } else if (value > 25) { g.setColor (Color.yellow); } else { g.setColor (Color.red); } m = (int)(w*((100f - (100-value))/100f)); if (m < 0) { m = 0; } g.clearRect (x, y, w, h); g.fillRect (x, y, m, h); } /* Paint the cannon. */ void paintCannon (Graphics g, int dir, Color color) { g.setColor (color); g.fillPolygon (cannons[dir]); } /* Paint the base. */ public void paintBase (Graphics g) { int x = SCREEN_WIDTH / 2; int y = SCREEN_HEIGHT / 2; if (dead) { int w, h; h = fontMetrics.getHeight (); w = fontMetrics.stringWidth (titleString); g.setColor (Color.green); g.drawString (titleString, x - w/2, 2*h); w = fontMetrics.stringWidth ("XXXX"); paintCannon (g, curr_dir, Color.black); if (tracker.checkAll (true)) { g.drawImage (deathImage, x - 16, y - 16, this); } g.setColor (Color.red); g.drawString ("GAME", x - w/2, y - 32); g.drawString ("OVER", x - w/2, y + 32 + h); w = fontMetrics.stringWidth (welcomeString); g.setColor (Color.green); g.drawString (welcomeString, x - w/2, SCREEN_HEIGHT - h); } else { g.setColor (Color.green); g.fillRect (x - BASE_WIDTH, y - BASE_WIDTH, BASE_WIDTH*2, BASE_WIDTH*2); if (curr_dir != new_dir) { paintCannon (g, curr_dir, Color.black); } curr_dir = new_dir; paintCannon (g, curr_dir, Color.green); } } /* Create a number prefixed with zeros. */ String zeroNumberString (int number, int length) { StringBuffer s; s = new StringBuffer (Integer.toString (number)); while (s.length () < length) { s.insert (0, "0"); } return s.toString (); } /* Paint the current score. */ public void paintScore (Graphics g) { int sw = fontMetrics.stringWidth (scoreString); int w = fontMetrics.stringWidth ("00000"); int h = fontMetrics.getHeight (); int x = 0; int y = h; g.setColor (Color.white); g.drawString (scoreString, SCREEN_WIDTH - w - sw - 5, y); x = SCREEN_WIDTH - w - 5; g.setColor (Color.black); g.fillRect (x, y - h, w, h); g.setColor (Color.white); g.drawString (zeroNumberString (score, 5), x, y); } /* Don't clear the screen; just call paint. */ public void update (Graphics g) { paint (g); } /* Paint the screen. */ public void paint (Graphics g) { if (things == null || things.size () == 0) { things = new Vector (32); createStarfield (); } if (clearScreen) { g.setColor (Color.black); g.fillRect (0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); clearScreen = false; } updateThings (g); if (!dead) { paintMeters (g); } if (!dead || score > 0) { paintScore (g); } paintBase (g); } } /* Local variables: eval: (progn (make-local-variable 'compile-command) (setq compile-command (concat "javac " buffer-file-name))) End: */