Asteroids.java
上传用户:nblingfeng
上传日期:2007-01-07
资源大小:80k
文件大小:35k
源码类别:

游戏

开发平台:

Java

  1. /************************************************************************************************
  2. Asteroids.java
  3.   Usage:
  4.   <applet code="Asteroids.class" width=w height=h></applet>
  5.   Keyboard Controls:
  6.   S            - Start Game    P           - Pause Game
  7.   Cursor Left  - Rotate Left   Cursor Up   - Fire Thrusters
  8.   Cursor Right - Rotate Right  Cursor Down - Fire Retro Thrusters
  9.   Spacebar     - Fire Cannon   H           - Hyperspace
  10.   M            - Toggle Sound  D           - Toggle Graphics Detail
  11. ************************************************************************************************/
  12. import java.awt.*;
  13. import java.net.*;
  14. import java.util.*;
  15. import java.applet.Applet;
  16. import java.applet.AudioClip;
  17. /************************************************************************************************
  18.   The AsteroidsSprite class defines a game object, including it's shape, position, movement and
  19.   rotation. It also can detemine if two objects collide.
  20. ************************************************************************************************/
  21. class AsteroidsSprite {
  22.   // Fields:
  23.   static int width;    // Dimensions of the graphics area.
  24.   static int height;
  25.   Polygon shape;                 // Initial sprite shape, centered at the origin (0,0).
  26.   boolean active;                // Active flag.
  27.   double  angle;                 // Current angle of rotation.
  28.   double  deltaAngle;            // Amount to change the rotation angle.
  29.   double  currentX, currentY;    // Current position on screen.
  30.   double  deltaX, deltaY;        // Amount to change the screen position.
  31.   Polygon sprite;                // Final location and shape of sprite after applying rotation and
  32.                                  // moving to screen position. Used for drawing on the screen and
  33.                                  // in detecting collisions.
  34.   // Constructors:
  35.   public AsteroidsSprite() {
  36.     this.shape = new Polygon();
  37.     this.active = false;
  38.     this.angle = 0.0;
  39.     this.deltaAngle = 0.0;
  40.     this.currentX = 0.0;
  41.     this.currentY = 0.0;
  42.     this.deltaX = 0.0;
  43.     this.deltaY = 0.0;
  44.     this.sprite = new Polygon();
  45.   }
  46.   // Methods:
  47.   public void advance() {
  48.     // Update the rotation and position of the sprite based on the delta values. If the sprite
  49.     // moves off the edge of the screen, it is wrapped around to the other side.
  50.     this.angle += this.deltaAngle;
  51.     if (this.angle < 0)
  52.       this.angle += 2 * Math.PI;
  53.     if (this.angle > 2 * Math.PI)
  54.       this.angle -= 2 * Math.PI;
  55.     this.currentX += this.deltaX;
  56.     if (this.currentX < -width / 2)
  57.       this.currentX += width;
  58.     if (this.currentX > width / 2)
  59.       this.currentX -= width;
  60.     this.currentY -= this.deltaY;
  61.     if (this.currentY < -height / 2)
  62.       this.currentY += height;
  63.     if (this.currentY > height / 2)
  64.       this.currentY -= height;
  65.   }
  66.   public void render() {
  67.     int i;
  68.     // Render the sprite's shape and location by rotating it's base shape and moving it to
  69.     // it's proper screen position.
  70.     this.sprite = new Polygon();
  71.     for (i = 0; i < this.shape.npoints; i++)
  72.       this.sprite.addPoint((int) Math.round(this.shape.xpoints[i] * Math.cos(this.angle) + this.shape.ypoints[i] * Math.sin(this.angle)) + (int) Math.round(this.currentX) + width / 2,
  73.                  (int) Math.round(this.shape.ypoints[i] * Math.cos(this.angle) - this.shape.xpoints[i] * Math.sin(this.angle)) + (int) Math.round(this.currentY) + height / 2);
  74.   }
  75.   public boolean isColliding(AsteroidsSprite s) {
  76.     int i;
  77.     // Determine if one sprite overlaps with another, i.e., if any vertice
  78.     // of one sprite lands inside the other.
  79.     for (i = 0; i < s.sprite.npoints; i++)
  80.       if (this.sprite.inside(s.sprite.xpoints[i], s.sprite.ypoints[i]))
  81.         return true;
  82.     for (i = 0; i < this.sprite.npoints; i++)
  83.       if (s.sprite.inside(this.sprite.xpoints[i], this.sprite.ypoints[i]))
  84.         return true;
  85.     return false;
  86.   }
  87. }
  88. /************************************************************************************************
  89.   Main applet code.
  90. ************************************************************************************************/
  91. public class Asteroids extends Applet implements Runnable {
  92.   // Thread control variables.
  93.   Thread loadThread;
  94.   Thread loopThread;
  95.   // Constants
  96.   static final int DELAY = 50;             // Milliseconds between screen updates.
  97.   static final int MAX_SHIPS = 3;           // Starting number of ships per game.
  98.   static final int MAX_SHOTS =  6;          // Maximum number of sprites for photons,
  99.   static final int MAX_ROCKS =  8;          // asteroids and explosions.
  100.   static final int MAX_SCRAP = 20;
  101.   static final int SCRAP_COUNT = 30;        // Counter starting values.
  102.   static final int HYPER_COUNT = 60;
  103.   static final int STORM_PAUSE = 30;
  104.   static final int UFO_PASSES  =  3;
  105.   static final int MIN_ROCK_SIDES =  8;     // Asteroid shape and size ranges.
  106.   static final int MAX_ROCK_SIDES = 12;
  107.   static final int MIN_ROCK_SIZE  = 20;
  108.   static final int MAX_ROCK_SIZE  = 40;
  109.   static final int MIN_ROCK_SPEED =  2;
  110.   static final int MAX_ROCK_SPEED = 12;
  111.   static final int BIG_POINTS    =  25;     // Points for shooting different objects.
  112.   static final int SMALL_POINTS  =  50;
  113.   static final int UFO_POINTS    = 250;
  114.   static final int MISSLE_POINTS = 500;
  115.   static final int NEW_SHIP_POINTS = 5000;  // Number of points needed to earn a new ship.
  116.   static final int NEW_UFO_POINTS  = 2750;  // Number of points between flying saucers.
  117.   // Background stars.
  118.   int     numStars;
  119.   Point[] stars;
  120.   // Game data.
  121.   int score;
  122.   int highScore;
  123.   int newShipScore;
  124.   int newUfoScore;
  125.   boolean loaded = false;
  126.   boolean paused;
  127.   boolean playing;
  128.   boolean sound;
  129.   boolean detail;
  130.   // Key flags.
  131.   boolean left  = false;
  132.   boolean right = false;
  133.   boolean up    = false;
  134.   boolean down  = false;
  135.   // Sprite objects.
  136.   AsteroidsSprite   ship;
  137.   AsteroidsSprite   ufo;
  138.   AsteroidsSprite   missle;
  139.   AsteroidsSprite[] photons    = new AsteroidsSprite[MAX_SHOTS];
  140.   AsteroidsSprite[] asteroids  = new AsteroidsSprite[MAX_ROCKS];
  141.   AsteroidsSprite[] explosions = new AsteroidsSprite[MAX_SCRAP];
  142.   // Ship data.
  143.   int shipsLeft;       // Number of ships left to play, including current one.
  144.   int shipCounter;     // Time counter for ship explosion.
  145.   int hyperCounter;    // Time counter for hyperspace.
  146.   // Photon data.
  147.   int[] photonCounter = new int[MAX_SHOTS];    // Time counter for life of a photon.
  148.   int   photonIndex;                           // Next available photon sprite.
  149.   // Flying saucer data.
  150.   int ufoPassesLeft;    // Number of flying saucer passes.
  151.   int ufoCounter;       // Time counter for each pass.
  152.   // Missle data.
  153.   int missleCounter;    // Counter for life of missle.
  154.   // Asteroid data.
  155.   boolean[] asteroidIsSmall = new boolean[MAX_ROCKS];    // Asteroid size flag.
  156.   int       asteroidsCounter;                            // Break-time counter.
  157.   int       asteroidsSpeed;                              // Asteroid speed.
  158.   int       asteroidsLeft;                               // Number of active asteroids.
  159.   // Explosion data.
  160.   int[] explosionCounter = new int[MAX_SCRAP];  // Time counters for explosions.
  161.   int   explosionIndex;                         // Next available explosion sprite.
  162.   // Sound clips.
  163.   AudioClip crashSound;
  164.   AudioClip explosionSound;
  165.   AudioClip fireSound;
  166.   AudioClip missleSound;
  167.   AudioClip saucerSound;
  168.   AudioClip thrustersSound;
  169.   AudioClip warpSound;
  170.   // Flags for looping sound clips.
  171.   boolean thrustersPlaying;
  172.   boolean saucerPlaying;
  173.   boolean misslePlaying;
  174.   // Values for the offscreen image.
  175.   Dimension offDimension;
  176.   Image offImage;
  177.   Graphics offGraphics;
  178.   // Font data.
  179.   Font font = new Font("Helvetica", Font.BOLD, 12);
  180.   FontMetrics fm;
  181.   int fontWidth;
  182.   int fontHeight;
  183.   // Applet information.
  184.   public String getAppletInfo() {
  185.     return("Asteroids, Copyright 1998 by Mike Hall.");
  186.   }
  187.   public void init() {
  188.     Graphics g;
  189.     Dimension d;
  190.     int i;
  191.     // Take credit.
  192.     System.out.println("Asteroids, Copyright 1998 by Mike Hall.");
  193.     // Find the size of the screen and set the values for sprites.
  194.     g = getGraphics();
  195.     d = size();
  196.     AsteroidsSprite.width = d.width;
  197.     AsteroidsSprite.height = d.height;
  198.     // Generate starry background.
  199.     numStars = AsteroidsSprite.width * AsteroidsSprite.height / 5000;
  200.     stars = new Point[numStars];
  201.     for (i = 0; i < numStars; i++)
  202.       stars[i] = new Point((int) (Math.random() * AsteroidsSprite.width), (int) (Math.random() * AsteroidsSprite.height));
  203.     // Create shape for the ship sprite.
  204.     ship = new AsteroidsSprite();
  205.     ship.shape.addPoint(0, -10);
  206.     ship.shape.addPoint(7, 10);
  207.     ship.shape.addPoint(-7, 10);
  208.     // Create shape for the photon sprites.
  209.     for (i = 0; i < MAX_SHOTS; i++) {
  210.       photons[i] = new AsteroidsSprite();
  211.       photons[i].shape.addPoint(1, 1);
  212.       photons[i].shape.addPoint(1, -1);
  213.       photons[i].shape.addPoint(-1, 1);
  214.       photons[i].shape.addPoint(-1, -1);
  215.     }
  216.     // Create shape for the flying saucer.
  217.     ufo = new AsteroidsSprite();
  218.     ufo.shape.addPoint(-15, 0);
  219.     ufo.shape.addPoint(-10, -5);
  220.     ufo.shape.addPoint(-5, -5);
  221.     ufo.shape.addPoint(-5, -9);
  222.     ufo.shape.addPoint(5, -9);
  223.     ufo.shape.addPoint(5, -5);
  224.     ufo.shape.addPoint(10, -5);
  225.     ufo.shape.addPoint(15, 0);
  226.     ufo.shape.addPoint(10, 5);
  227.     ufo.shape.addPoint(-10, 5);
  228.     // Create shape for the guided missle.
  229.     missle = new AsteroidsSprite();
  230.     missle.shape.addPoint(0, -4);
  231.     missle.shape.addPoint(1, -3);
  232.     missle.shape.addPoint(1, 3);
  233.     missle.shape.addPoint(2, 4);
  234.     missle.shape.addPoint(-2, 4);
  235.     missle.shape.addPoint(-1, 3);
  236.     missle.shape.addPoint(-1, -3);
  237.     // Create asteroid sprites.
  238.     for (i = 0; i < MAX_ROCKS; i++)
  239.       asteroids[i] = new AsteroidsSprite();
  240.     // Create explosion sprites.
  241.     for (i = 0; i < MAX_SCRAP; i++)
  242.       explosions[i] = new AsteroidsSprite();
  243.     // Set font data.
  244.     g.setFont(font);
  245.     fm = g.getFontMetrics();
  246.     fontWidth = fm.getMaxAdvance();
  247.     fontHeight = fm.getHeight();
  248.     // Initialize game data and put us in 'game over' mode.
  249.     highScore = 0;
  250.     sound = true;
  251.     detail = true;
  252.     initGame();
  253.     endGame();
  254.   }
  255.   public void initGame() {
  256.     // Initialize game data and sprites.
  257.     score = 0;
  258.     shipsLeft = MAX_SHIPS;
  259.     asteroidsSpeed = MIN_ROCK_SPEED;
  260.     newShipScore = NEW_SHIP_POINTS;
  261.     newUfoScore = NEW_UFO_POINTS;
  262.     initShip();
  263.     initPhotons();
  264.     stopUfo();
  265.     stopMissle();
  266.     initAsteroids();
  267.     initExplosions();
  268.     playing = true;
  269.     paused = false;
  270.   }
  271.   public void endGame() {
  272.     // Stop ship, flying saucer, guided missle and associated sounds.
  273.     playing = false;
  274.     stopShip();
  275.     stopUfo();
  276.     stopMissle();
  277.   }
  278.   public void start() {
  279.     if (loopThread == null) {
  280.       loopThread = new Thread(this);
  281.       loopThread.start();
  282.     }
  283.     if (!loaded && loadThread == null) {
  284.       loadThread = new Thread(this);
  285.       loadThread.start();
  286.     }
  287.   }
  288.   public void stop() {
  289.     if (loopThread != null) {
  290.       loopThread.stop();
  291.       loopThread = null;
  292.     }
  293.     if (loadThread != null) {
  294.       loadThread.stop();
  295.       loadThread = null;
  296.     }
  297.   }
  298.   public void run() {
  299.     int i, j;
  300.     long startTime;
  301.     // Lower this thread's priority and get the current time.
  302.     Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
  303.     startTime = System.currentTimeMillis();
  304.     // Run thread for loading sounds.
  305.     if (!loaded && Thread.currentThread() == loadThread) {
  306.       loadSounds();
  307.       loaded = true;
  308.       loadThread.stop();
  309.     }
  310.     // This is the main loop.
  311.     while (Thread.currentThread() == loopThread) {
  312.       if (!paused) {
  313.         // Move and process all sprites.
  314.         updateShip();
  315.         updatePhotons();
  316.         updateUfo();
  317.         updateMissle();
  318.         updateAsteroids();
  319.         updateExplosions();
  320.         // Check the score and advance high score, add a new ship or start the flying
  321.         // saucer as necessary.
  322.         if (score > highScore)
  323.           highScore = score;
  324.         if (score > newShipScore) {
  325.           newShipScore += NEW_SHIP_POINTS;
  326.           shipsLeft++;
  327.         }
  328.         if (playing && score > newUfoScore && !ufo.active) {
  329.           newUfoScore += NEW_UFO_POINTS;
  330.           ufoPassesLeft = UFO_PASSES;
  331.           initUfo();
  332.         }
  333.         // If all asteroids have been destroyed create a new batch.
  334.         if (asteroidsLeft <= 0)
  335.             if (--asteroidsCounter <= 0)
  336.               initAsteroids();
  337.       }
  338.       // Update the screen and set the timer for the next loop.
  339.       repaint();
  340.       try {
  341.         startTime += DELAY;
  342.         Thread.sleep(Math.max(0, startTime - System.currentTimeMillis()));
  343.       }
  344.       catch (InterruptedException e) {
  345.         break;
  346.       }
  347.     }
  348.   }
  349.   public void loadSounds() {
  350.     // Load all sound clips by playing and immediately stopping them.
  351.     try {
  352.       crashSound     = getAudioClip(new URL(getDocumentBase(), "crash.au"));
  353.       explosionSound = getAudioClip(new URL(getDocumentBase(), "explosion.au"));
  354.       fireSound      = getAudioClip(new URL(getDocumentBase(), "fire.au"));
  355.       missleSound    = getAudioClip(new URL(getDocumentBase(), "missle.au"));
  356.       saucerSound    = getAudioClip(new URL(getDocumentBase(), "saucer.au"));
  357.       thrustersSound = getAudioClip(new URL(getDocumentBase(), "thrusters.au"));
  358.       warpSound      = getAudioClip(new URL(getDocumentBase(), "warp.au"));
  359.     }
  360.     catch (MalformedURLException e) {}
  361.     crashSound.play();     crashSound.stop();
  362.     explosionSound.play(); explosionSound.stop();
  363.     fireSound.play();      fireSound.stop();
  364.     missleSound.play();    missleSound.stop();
  365.     saucerSound.play();    saucerSound.stop();
  366.     thrustersSound.play(); thrustersSound.stop();
  367.     warpSound.play();      warpSound.stop();
  368.   }
  369.   public void initShip() {
  370.     ship.active = true;
  371.     ship.angle = 0.0;
  372.     ship.deltaAngle = 0.0;
  373.     ship.currentX = 0.0;
  374.     ship.currentY = 0.0;
  375.     ship.deltaX = 0.0;
  376.     ship.deltaY = 0.0;
  377.     ship.render();
  378.     if (loaded)
  379.       thrustersSound.stop();
  380.     thrustersPlaying = false;
  381.     hyperCounter = 0;
  382.   }
  383.   public void updateShip() {
  384.     double dx, dy, limit;
  385.     if (!playing)
  386.       return;
  387.     // Rotate the ship if left or right cursor key is down.
  388.     if (left) {
  389.       ship.angle += Math.PI / 16.0;
  390.       if (ship.angle > 2 * Math.PI)
  391.         ship.angle -= 2 * Math.PI;
  392.     }
  393.     if (right) {
  394.       ship.angle -= Math.PI / 16.0;
  395.       if (ship.angle < 0)
  396.         ship.angle += 2 * Math.PI;
  397.     }
  398.     // Fire thrusters if up or down cursor key is down. Don't let ship go past
  399.     // the speed limit.
  400.     dx = -Math.sin(ship.angle);
  401.     dy =  Math.cos(ship.angle);
  402.     limit = 0.8 * MIN_ROCK_SIZE;
  403.     if (up) {
  404.       if (ship.deltaX + dx > -limit && ship.deltaX + dx < limit)
  405.         ship.deltaX += dx;
  406.       if (ship.deltaY + dy > -limit && ship.deltaY + dy < limit)
  407.         ship.deltaY += dy;
  408.     }
  409.     if (down) {
  410.       if (ship.deltaX - dx > -limit && ship.deltaX - dx < limit)
  411.         ship.deltaX -= dx;
  412.       if (ship.deltaY - dy > -limit && ship.deltaY - dy < limit)
  413.         ship.deltaY -= dy;
  414.     }
  415.     // Move the ship. If it is currently in hyperspace, advance the countdown.
  416.     if (ship.active) {
  417.       ship.advance();
  418.       ship.render();
  419.       if (hyperCounter > 0)
  420.         hyperCounter--;
  421.     }
  422.     // Ship is exploding, advance the countdown or create a new ship if it is
  423.     // done exploding. The new ship is added as though it were in hyperspace.
  424.     // (This gives the player time to move the ship if it is in imminent danger.)
  425.     // If that was the last ship, end the game.
  426.     else
  427.       if (--shipCounter <= 0)
  428.         if (shipsLeft > 0) {
  429.           initShip();
  430.           hyperCounter = HYPER_COUNT;
  431.         }
  432.         else
  433.           endGame();
  434.   }
  435.   public void stopShip() {
  436.     ship.active = false;
  437.     shipCounter = SCRAP_COUNT;
  438.     if (shipsLeft > 0)
  439.       shipsLeft--;
  440.     if (loaded)
  441.       thrustersSound.stop();
  442.     thrustersPlaying = false;
  443.   }
  444.   public void initPhotons() {
  445.     int i;
  446.     for (i = 0; i < MAX_SHOTS; i++) {
  447.       photons[i].active = false;
  448.       photonCounter[i] = 0;
  449.     }
  450.     photonIndex = 0;
  451.   }
  452.   public void updatePhotons() {
  453.     int i;
  454.     // Move any active photons. Stop it when its counter has expired.
  455.     for (i = 0; i < MAX_SHOTS; i++)
  456.       if (photons[i].active) {
  457.         photons[i].advance();
  458.         photons[i].render();
  459.         if (--photonCounter[i] < 0)
  460.           photons[i].active = false;
  461.       }
  462.   }
  463.   public void initUfo() {
  464.     double temp;
  465.     // Randomly set flying saucer at left or right edge of the screen.
  466.     ufo.active = true;
  467.     ufo.currentX = -AsteroidsSprite.width / 2;
  468.     ufo.currentY = Math.random() * AsteroidsSprite.height;
  469.     ufo.deltaX = MIN_ROCK_SPEED + Math.random() * (MAX_ROCK_SPEED - MIN_ROCK_SPEED);
  470.     if (Math.random() < 0.5) {
  471.       ufo.deltaX = -ufo.deltaX;
  472.       ufo.currentX = AsteroidsSprite.width / 2;
  473.     }
  474.     ufo.deltaY = MIN_ROCK_SPEED + Math.random() * (MAX_ROCK_SPEED - MIN_ROCK_SPEED);
  475.     if (Math.random() < 0.5)
  476.       ufo.deltaY = -ufo.deltaY;
  477.     ufo.render();
  478.     saucerPlaying = true;
  479.     if (sound)
  480.       saucerSound.loop();
  481.     // Set counter for this pass.
  482.     ufoCounter = (int) Math.floor(AsteroidsSprite.width / Math.abs(ufo.deltaX));
  483.   }
  484.   public void updateUfo() {
  485.     int i, d;
  486.     // Move the flying saucer and check for collision with a photon. Stop it when its
  487.     // counter has expired.
  488.     if (ufo.active) {
  489.       ufo.advance();
  490.       ufo.render();
  491.       if (--ufoCounter <= 0)
  492.         if (--ufoPassesLeft > 0)
  493.           initUfo();
  494.         else
  495.           stopUfo();
  496.       else {
  497.         for (i = 0; i < MAX_SHOTS; i++)
  498.           if (photons[i].active && ufo.isColliding(photons[i])) {
  499.             if (sound)
  500.               crashSound.play();
  501.             explode(ufo);
  502.             stopUfo();
  503.             score += UFO_POINTS;
  504.           }
  505.           // On occassion, fire a missle at the ship if the saucer is not
  506.           // too close to it.
  507.           d = (int) Math.max(Math.abs(ufo.currentX - ship.currentX), Math.abs(ufo.currentY - ship.currentY));
  508.           if (ship.active && hyperCounter <= 0 && ufo.active && !missle.active &&
  509.               d > 4 * MAX_ROCK_SIZE && Math.random() < .03)
  510.             initMissle();
  511.        }
  512.     }
  513.   }
  514.   public void stopUfo() {
  515.     ufo.active = false;
  516.     ufoCounter = 0;
  517.     ufoPassesLeft = 0;
  518.     if (loaded)
  519.       saucerSound.stop();
  520.     saucerPlaying = false;
  521.   }
  522.   public void initMissle() {
  523.     missle.active = true;
  524.     missle.angle = 0.0;
  525.     missle.deltaAngle = 0.0;
  526.     missle.currentX = ufo.currentX;
  527.     missle.currentY = ufo.currentY;
  528.     missle.deltaX = 0.0;
  529.     missle.deltaY = 0.0;
  530.     missle.render();
  531.     missleCounter = 3 * Math.max(AsteroidsSprite.width, AsteroidsSprite.height) / MIN_ROCK_SIZE;
  532.     if (sound)
  533.       missleSound.loop();
  534.     misslePlaying = true;
  535.   }
  536.   public void updateMissle() {
  537.     int i;
  538.     // Move the guided missle and check for collision with ship or photon. Stop it when its
  539.     // counter has expired.
  540.     if (missle.active) {
  541.       if (--missleCounter <= 0)
  542.         stopMissle();
  543.       else {
  544.         guideMissle();
  545.         missle.advance();
  546.         missle.render();
  547.         for (i = 0; i < MAX_SHOTS; i++)
  548.           if (photons[i].active && missle.isColliding(photons[i])) {
  549.             if (sound)
  550.               crashSound.play();
  551.             explode(missle);
  552.             stopMissle();
  553.             score += MISSLE_POINTS;
  554.           }
  555.         if (missle.active && ship.active && hyperCounter <= 0 && ship.isColliding(missle)) {
  556.           if (sound)
  557.             crashSound.play();
  558.           explode(ship);
  559.           stopShip();
  560.           stopUfo();
  561.           stopMissle();
  562.         }
  563.       }
  564.     }
  565.   }
  566.   public void guideMissle() {
  567.     double dx, dy, angle;
  568.     if (!ship.active || hyperCounter > 0)
  569.       return;
  570.     // Find the angle needed to hit the ship.
  571.     dx = ship.currentX - missle.currentX;
  572.     dy = ship.currentY - missle.currentY;
  573.     if (dx == 0 && dy == 0)
  574.       angle = 0;
  575.     if (dx == 0) {
  576.       if (dy < 0)
  577.         angle = -Math.PI / 2;
  578.       else
  579.         angle = Math.PI / 2;
  580.     }
  581.     else {
  582.       angle = Math.atan(Math.abs(dy / dx));
  583.       if (dy > 0)
  584.         angle = -angle;
  585.       if (dx < 0)
  586.         angle = Math.PI - angle;
  587.     }
  588.     // Adjust angle for screen coordinates.
  589.     missle.angle = angle - Math.PI / 2;
  590.     // Change the missle's angle so that it points toward the ship.
  591.     missle.deltaX = MIN_ROCK_SIZE / 3 * -Math.sin(missle.angle);
  592.     missle.deltaY = MIN_ROCK_SIZE / 3 *  Math.cos(missle.angle);
  593.   }
  594.   public void stopMissle() {
  595.     missle.active = false;
  596.     missleCounter = 0;
  597.     if (loaded)
  598.       missleSound.stop();
  599.     misslePlaying = false;
  600.   }
  601.   public void initAsteroids() {
  602.     int i, j;
  603.     int s;
  604.     double theta, r;
  605.     int x, y;
  606.     // Create random shapes, positions and movements for each asteroid.
  607.     for (i = 0; i < MAX_ROCKS; i++) {
  608.       // Create a jagged shape for the asteroid and give it a random rotation.
  609.       asteroids[i].shape = new Polygon();
  610.       s = MIN_ROCK_SIDES + (int) (Math.random() * (MAX_ROCK_SIDES - MIN_ROCK_SIDES));
  611.       for (j = 0; j < s; j ++) {
  612.         theta = 2 * Math.PI / s * j;
  613.         r = MIN_ROCK_SIZE + (int) (Math.random() * (MAX_ROCK_SIZE - MIN_ROCK_SIZE));
  614.         x = (int) -Math.round(r * Math.sin(theta));
  615.         y = (int)  Math.round(r * Math.cos(theta));
  616.         asteroids[i].shape.addPoint(x, y);
  617.       }
  618.       asteroids[i].active = true;
  619.       asteroids[i].angle = 0.0;
  620.       asteroids[i].deltaAngle = (Math.random() - 0.5) / 10;
  621.       // Place the asteroid at one edge of the screen.
  622.       if (Math.random() < 0.5) {
  623.         asteroids[i].currentX = -AsteroidsSprite.width / 2;
  624.         if (Math.random() < 0.5)
  625.           asteroids[i].currentX = AsteroidsSprite.width / 2;
  626.         asteroids[i].currentY = Math.random() * AsteroidsSprite.height;
  627.       }
  628.       else {
  629.         asteroids[i].currentX = Math.random() * AsteroidsSprite.width;
  630.         asteroids[i].currentY = -AsteroidsSprite.height / 2;
  631.         if (Math.random() < 0.5)
  632.           asteroids[i].currentY = AsteroidsSprite.height / 2;
  633.       }
  634.       // Set a random motion for the asteroid.
  635.       asteroids[i].deltaX = Math.random() * asteroidsSpeed;
  636.       if (Math.random() < 0.5)
  637.         asteroids[i].deltaX = -asteroids[i].deltaX;
  638.       asteroids[i].deltaY = Math.random() * asteroidsSpeed;
  639.       if (Math.random() < 0.5)
  640.         asteroids[i].deltaY = -asteroids[i].deltaY;
  641.       asteroids[i].render();
  642.       asteroidIsSmall[i] = false;
  643.     }
  644.     asteroidsCounter = STORM_PAUSE;
  645.     asteroidsLeft = MAX_ROCKS;
  646.     if (asteroidsSpeed < MAX_ROCK_SPEED)
  647.       asteroidsSpeed++;
  648.   }
  649.   public void initSmallAsteroids(int n) {
  650.     int count;
  651.     int i, j;
  652.     int s;
  653.     double tempX, tempY;
  654.     double theta, r;
  655.     int x, y;
  656.     // Create one or two smaller asteroids from a larger one using inactive asteroids. The new
  657.     // asteroids will be placed in the same position as the old one but will have a new, smaller
  658.     // shape and new, randomly generated movements.
  659.     count = 0;
  660.     i = 0;
  661.     tempX = asteroids[n].currentX;
  662.     tempY = asteroids[n].currentY;
  663.     do {
  664.       if (!asteroids[i].active) {
  665.         asteroids[i].shape = new Polygon();
  666.         s = MIN_ROCK_SIDES + (int) (Math.random() * (MAX_ROCK_SIDES - MIN_ROCK_SIDES));
  667.         for (j = 0; j < s; j ++) {
  668.           theta = 2 * Math.PI / s * j;
  669.           r = (MIN_ROCK_SIZE + (int) (Math.random() * (MAX_ROCK_SIZE - MIN_ROCK_SIZE))) / 2;
  670.           x = (int) -Math.round(r * Math.sin(theta));
  671.           y = (int)  Math.round(r * Math.cos(theta));
  672.           asteroids[i].shape.addPoint(x, y);
  673.         }
  674.         asteroids[i].active = true;
  675.         asteroids[i].angle = 0.0;
  676.         asteroids[i].deltaAngle = (Math.random() - 0.5) / 10;
  677.         asteroids[i].currentX = tempX;
  678.         asteroids[i].currentY = tempY;
  679.         asteroids[i].deltaX = Math.random() * 2 * asteroidsSpeed - asteroidsSpeed;
  680.         asteroids[i].deltaY = Math.random() * 2 * asteroidsSpeed - asteroidsSpeed;
  681.         asteroids[i].render();
  682.         asteroidIsSmall[i] = true;
  683.         count++;
  684.         asteroidsLeft++;
  685.       }
  686.       i++;
  687.     } while (i < MAX_ROCKS && count < 2);
  688.   }
  689.   public void updateAsteroids() {
  690.     int i, j;
  691.     // Move any active asteroids and check for collisions.
  692.     for (i = 0; i < MAX_ROCKS; i++)
  693.       if (asteroids[i].active) {
  694.         asteroids[i].advance();
  695.         asteroids[i].render();
  696.         // If hit by photon, kill asteroid and advance score. If asteroid is large,
  697.         // make some smaller ones to replace it.
  698.         for (j = 0; j < MAX_SHOTS; j++)
  699.           if (photons[j].active && asteroids[i].active && asteroids[i].isColliding(photons[j])) {
  700.             asteroidsLeft--;
  701.             asteroids[i].active = false;
  702.             photons[j].active = false;
  703.             if (sound)
  704.               explosionSound.play();
  705.             explode(asteroids[i]);
  706.             if (!asteroidIsSmall[i]) {
  707.               score += BIG_POINTS;
  708.               initSmallAsteroids(i);
  709.             }
  710.             else
  711.               score += SMALL_POINTS;
  712.           }
  713.         // If the ship is not in hyperspace, see if it is hit.
  714.         if (ship.active && hyperCounter <= 0 && asteroids[i].active && asteroids[i].isColliding(ship)) {
  715.           if (sound)
  716.             crashSound.play();
  717.           explode(ship);
  718.           stopShip();
  719.           stopUfo();
  720.           stopMissle();
  721.         }
  722.     }
  723.   }
  724.   public void initExplosions() {
  725.     int i;
  726.     for (i = 0; i < MAX_SCRAP; i++) {
  727.       explosions[i].shape = new Polygon();
  728.       explosions[i].active = false;
  729.       explosionCounter[i] = 0;
  730.     }
  731.     explosionIndex = 0;
  732.   }
  733.   public void explode(AsteroidsSprite s) {
  734.     int c, i, j;
  735.     // Create sprites for explosion animation. The each individual line segment of the given sprite
  736.     // is used to create a new sprite that will move outward  from the sprite's original position
  737.     // with a random rotation.
  738.     s.render();
  739.     c = 2;
  740.     if (detail || s.sprite.npoints < 6)
  741.       c = 1;
  742.     for (i = 0; i < s.sprite.npoints; i += c) {
  743.       explosionIndex++;
  744.       if (explosionIndex >= MAX_SCRAP)
  745.         explosionIndex = 0;
  746.       explosions[explosionIndex].active = true;
  747.       explosions[explosionIndex].shape = new Polygon();
  748.       explosions[explosionIndex].shape.addPoint(s.shape.xpoints[i], s.shape.ypoints[i]);
  749.       j = i + 1;
  750.       if (j >= s.sprite.npoints)
  751.         j -= s.sprite.npoints;
  752.       explosions[explosionIndex].shape.addPoint(s.shape.xpoints[j], s.shape.ypoints[j]);
  753.       explosions[explosionIndex].angle = s.angle;
  754.       explosions[explosionIndex].deltaAngle = (Math.random() * 2 * Math.PI - Math.PI) / 15;
  755.       explosions[explosionIndex].currentX = s.currentX;
  756.       explosions[explosionIndex].currentY = s.currentY;
  757.       explosions[explosionIndex].deltaX = -s.shape.xpoints[i] / 5;
  758.       explosions[explosionIndex].deltaY = -s.shape.ypoints[i] / 5;
  759.       explosionCounter[explosionIndex] = SCRAP_COUNT;
  760.     }
  761.   }
  762.   public void updateExplosions() {
  763.     int i;
  764.     // Move any active explosion debris. Stop explosion when its counter has expired.
  765.     for (i = 0; i < MAX_SCRAP; i++)
  766.       if (explosions[i].active) {
  767.         explosions[i].advance();
  768.         explosions[i].render();
  769.         if (--explosionCounter[i] < 0)
  770.           explosions[i].active = false;
  771.       }
  772.   }
  773.   public boolean keyDown(Event e, int key) {
  774.     // Check if any cursor keys have been pressed and set flags.
  775.     if (key == Event.LEFT)
  776.       left = true;
  777.     if (key == Event.RIGHT)
  778.       right = true;
  779.     if (key == Event.UP)
  780.       up = true;
  781.     if (key == Event.DOWN)
  782.       down = true;
  783.     if ((up || down) && ship.active && !thrustersPlaying) {
  784.       if (sound && !paused)
  785.         thrustersSound.loop();
  786.       thrustersPlaying = true;
  787.     }
  788.     // Spacebar: fire a photon and start its counter.
  789.     if (key == 32 && ship.active) {
  790.       if (sound & !paused)
  791.         fireSound.play();
  792.       photonIndex++;
  793.       if (photonIndex >= MAX_SHOTS)
  794.         photonIndex = 0;
  795.       photons[photonIndex].active = true;
  796.       photons[photonIndex].currentX = ship.currentX;
  797.       photons[photonIndex].currentY = ship.currentY;
  798.       photons[photonIndex].deltaX = MIN_ROCK_SIZE * -Math.sin(ship.angle);
  799.       photons[photonIndex].deltaY = MIN_ROCK_SIZE *  Math.cos(ship.angle);
  800.       photonCounter[photonIndex] = Math.min(AsteroidsSprite.width, AsteroidsSprite.height) / MIN_ROCK_SIZE;
  801.     }
  802.     // 'H' key: warp ship into hyperspace by moving to a random location and starting counter.
  803.     if (key == 104 && ship.active && hyperCounter <= 0) {
  804.       ship.currentX = Math.random() * AsteroidsSprite.width;
  805.       ship.currentX = Math.random() * AsteroidsSprite.height;
  806.       hyperCounter = HYPER_COUNT;
  807.       if (sound & !paused)
  808.         warpSound.play();
  809.     }
  810.     // 'P' key: toggle pause mode and start or stop any active looping sound clips.
  811.     if (key == 112) {
  812.       if (paused) {
  813.         if (sound && misslePlaying)
  814.           missleSound.loop();
  815.         if (sound && saucerPlaying)
  816.           saucerSound.loop();
  817.         if (sound && thrustersPlaying)
  818.           thrustersSound.loop();
  819.       }
  820.       else {
  821.         if (misslePlaying)
  822.           missleSound.stop();
  823.         if (saucerPlaying)
  824.           saucerSound.stop();
  825.         if (thrustersPlaying)
  826.           thrustersSound.stop();
  827.       }
  828.       paused = !paused;
  829.     }
  830.     // 'M' key: toggle sound on or off and stop any looping sound clips.
  831.     if (key == 109 && loaded) {
  832.       if (sound) {
  833.         crashSound.stop();
  834.         explosionSound.stop();
  835.         fireSound.stop();
  836.         missleSound.stop();
  837.         saucerSound.stop();
  838.         thrustersSound.stop();
  839.         warpSound.stop();
  840.       }
  841.       else {
  842.         if (misslePlaying && !paused)
  843.           missleSound.loop();
  844.         if (saucerPlaying && !paused)
  845.           saucerSound.loop();
  846.         if (thrustersPlaying && !paused)
  847.           thrustersSound.loop();
  848.       }
  849.       sound = !sound;
  850.     }
  851.     // 'D' key: toggle graphics detail on or off.
  852.     if (key == 100)
  853.       detail = !detail;
  854.     // 'S' key: start the game, if not already in progress.
  855.     if (key == 115 && loaded && !playing)
  856.       initGame();
  857.     return true;
  858.   }
  859.   public boolean keyUp(Event e, int key) {
  860.     // Check if any cursor keys where released and set flags.
  861.     if (key == Event.LEFT)
  862.       left = false;
  863.     if (key == Event.RIGHT)
  864.       right = false;
  865.     if (key == Event.UP)
  866.       up = false;
  867.     if (key == Event.DOWN)
  868.       down = false;
  869.     if (!up && !down && thrustersPlaying) {
  870.       thrustersSound.stop();
  871.       thrustersPlaying = false;
  872.     }
  873.     return true;
  874.   }
  875.   public void paint(Graphics g) {
  876.     update(g);
  877.   }
  878.   public void update(Graphics g) {
  879.     Dimension d = size();
  880.     int i;
  881.     int c;
  882.     String s;
  883.     // Create the offscreen graphics context, if no good one exists.
  884.     if (offGraphics == null || d.width != offDimension.width || d.height != offDimension.height) {
  885.       offDimension = d;
  886.       offImage = createImage(d.width, d.height);
  887.       offGraphics = offImage.getGraphics();
  888.     }
  889.     // Fill in background and stars.
  890.     offGraphics.setColor(Color.black);
  891.     offGraphics.fillRect(0, 0, d.width, d.height);
  892.     if (detail) {
  893.       offGraphics.setColor(Color.white);
  894.       for (i = 0; i < numStars; i++)
  895.         offGraphics.drawLine(stars[i].x, stars[i].y, stars[i].x, stars[i].y);
  896.     }
  897.     // Draw photon bullets.
  898.     offGraphics.setColor(Color.white);
  899.     for (i = 0; i < MAX_SHOTS; i++)
  900.       if (photons[i].active)
  901.         offGraphics.drawPolygon(photons[i].sprite);
  902.     // Draw the guided missle, counter is used to quickly fade color to black when near expiration.
  903.     c = Math.min(missleCounter * 24, 255);
  904.     offGraphics.setColor(new Color(c, c, c));
  905.     if (missle.active) {
  906.       offGraphics.drawPolygon(missle.sprite);
  907.       offGraphics.drawLine(missle.sprite.xpoints[missle.sprite.npoints - 1], missle.sprite.ypoints[missle.sprite.npoints - 1],
  908.                            missle.sprite.xpoints[0], missle.sprite.ypoints[0]);
  909.     }
  910.     // Draw the asteroids.
  911.     for (i = 0; i < MAX_ROCKS; i++)
  912.       if (asteroids[i].active) {
  913.         if (detail) {
  914.           offGraphics.setColor(Color.black);
  915.           offGraphics.fillPolygon(asteroids[i].sprite);
  916.         }
  917.         offGraphics.setColor(Color.white);
  918.         offGraphics.drawPolygon(asteroids[i].sprite);
  919.         offGraphics.drawLine(asteroids[i].sprite.xpoints[asteroids[i].sprite.npoints - 1], asteroids[i].sprite.ypoints[asteroids[i].sprite.npoints - 1],
  920.                              asteroids[i].sprite.xpoints[0], asteroids[i].sprite.ypoints[0]);
  921.       }
  922.     // Draw the flying saucer.
  923.     if (ufo.active) {
  924.       if (detail) {
  925.         offGraphics.setColor(Color.black);
  926.         offGraphics.fillPolygon(ufo.sprite);
  927.       }
  928.       offGraphics.setColor(Color.white);
  929.       offGraphics.drawPolygon(ufo.sprite);
  930.       offGraphics.drawLine(ufo.sprite.xpoints[ufo.sprite.npoints - 1], ufo.sprite.ypoints[ufo.sprite.npoints - 1],
  931.                            ufo.sprite.xpoints[0], ufo.sprite.ypoints[0]);
  932.     }
  933.     // Draw the ship, counter is used to fade color to white on hyperspace.
  934.     c = 255 - (255 / HYPER_COUNT) * hyperCounter;
  935.     if (ship.active) {
  936.       if (detail && hyperCounter == 0) {
  937.         offGraphics.setColor(Color.black);
  938.         offGraphics.fillPolygon(ship.sprite);
  939.       }
  940.       offGraphics.setColor(new Color(c, c, c));
  941.       offGraphics.drawPolygon(ship.sprite);
  942.       offGraphics.drawLine(ship.sprite.xpoints[ship.sprite.npoints - 1], ship.sprite.ypoints[ship.sprite.npoints - 1],
  943.                            ship.sprite.xpoints[0], ship.sprite.ypoints[0]);
  944.     }
  945.     // Draw any explosion debris, counters are used to fade color to black.
  946.     for (i = 0; i < MAX_SCRAP; i++)
  947.       if (explosions[i].active) {
  948.         c = (255 / SCRAP_COUNT) * explosionCounter [i];
  949.         offGraphics.setColor(new Color(c, c, c));
  950.         offGraphics.drawPolygon(explosions[i].sprite);
  951.       }
  952.     // Display status and messages.
  953.     offGraphics.setFont(font);
  954.     offGraphics.setColor(Color.white);
  955.     offGraphics.drawString("Score: " + score, fontWidth, fontHeight);
  956.     offGraphics.drawString("Ships: " + shipsLeft, fontWidth, d.height - fontHeight);
  957.     s = "High: " + highScore;
  958.     offGraphics.drawString(s, d.width - (fontWidth + fm.stringWidth(s)), fontHeight);
  959.     if (!sound) {
  960.       s = "Mute";
  961.       offGraphics.drawString(s, d.width - (fontWidth + fm.stringWidth(s)), d.height - fontHeight);
  962.     }
  963.     
  964.     if (!playing) {
  965.       s = "A S T E R O I D S";
  966.       offGraphics.drawString(s, (d.width - fm.stringWidth(s)) / 2, d.height / 2);
  967.       s = "Copyright 1998 by Mike Hall";
  968.       offGraphics.drawString(s, (d.width - fm.stringWidth(s)) / 2, d.height / 2 + fontHeight);
  969.       if (!loaded) {
  970.         s = "Loading sounds...";
  971.         offGraphics.drawString(s, (d.width - fm.stringWidth(s)) / 2, d.height / 4);
  972.       }
  973.       else {
  974.         s = "Game Over";
  975.         offGraphics.drawString(s, (d.width - fm.stringWidth(s)) / 2, d.height / 4);
  976.         s = "'S' to Start";
  977.         offGraphics.drawString(s, (d.width - fm.stringWidth(s)) / 2, d.height / 4 + fontHeight);
  978.       }
  979.     }
  980.     else if (paused) {
  981.       s = "Game Paused";
  982.       offGraphics.drawString(s, (d.width - fm.stringWidth(s)) / 2, d.height / 4);
  983.     }
  984.     // Copy the off screen buffer to the screen.
  985.     g.drawImage(offImage, 0, 0, this);
  986.   }
  987. }