Download this source file
/************************************************************************************************
Asteroids.java
Usage:
<applet code="Asteroids.class" width="w" height="h"></applet>
Keyboard Controls:
S - Start Game P - Pause Game
Cursor Left - Rotate Left Cursor Up - Fire Thrusters
Cursor Right - Rotate Right Cursor Down - Fire Retro Thrusters
Spacebar - Fire Cannon H - Hyperspace
M - Toggle Sound D - Toggle Graphics Detail
************************************************************************************************/
import java.awt.*;
import java.net.*;
import java.util.*;
import java.applet.Applet;
import java.applet.AudioClip;
/************************************************************************************************
The AsteroidsSprite class defines a game object, including it's shape, position, movement and
rotation. It also can detemine if two objects collide.
************************************************************************************************/
class AsteroidsSprite {
// Fields:
static int width; // Dimensions of the graphics area.
static int height;
Polygon shape; // Initial sprite shape, centered at the origin (0,0).
boolean active; // Active flag.
double angle; // Current angle of rotation.
double deltaAngle; // Amount to change the rotation angle.
double currentX, currentY; // Current position on screen.
double deltaX, deltaY; // Amount to change the screen position.
Polygon sprite; // Final location and shape of sprite after applying rotation and
// moving to screen position. Used for drawing on the screen and
// in detecting collisions.
// Constructors:
public AsteroidsSprite() {
this.shape = new Polygon();
this.active = false;
this.angle = 0.0;
this.deltaAngle = 0.0;
this.currentX = 0.0;
this.currentY = 0.0;
this.deltaX = 0.0;
this.deltaY = 0.0;
this.sprite = new Polygon();
}
// Methods:
public void advance() {
// Update the rotation and position of the sprite based on the delta values. If the sprite
// moves off the edge of the screen, it is wrapped around to the other side.
this.angle += this.deltaAngle;
if (this.angle < 0)
this.angle += 2 * Math.PI;
if (this.angle > 2 * Math.PI)
this.angle -= 2 * Math.PI;
this.currentX += this.deltaX;
if (this.currentX < -width / 2)
this.currentX += width;
if (this.currentX > width / 2)
this.currentX -= width;
this.currentY -= this.deltaY;
if (this.currentY < -height / 2)
this.currentY += height;
if (this.currentY > height / 2)
this.currentY -= height;
}
public void render() {
int i;
// Render the sprite's shape and location by rotating it's base shape and moving it to
// it's proper screen position.
this.sprite = new Polygon();
for (i = 0; i < this.shape.npoints; i++)
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,
(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);
}
public boolean isColliding(AsteroidsSprite s) {
int i;
// Determine if one sprite overlaps with another, i.e., if any vertice
// of one sprite lands inside the other.
for (i = 0; i < s.sprite.npoints; i++)
if (this.sprite.inside(s.sprite.xpoints[i], s.sprite.ypoints[i]))
return true;
for (i = 0; i < this.sprite.npoints; i++)
if (s.sprite.inside(this.sprite.xpoints[i], this.sprite.ypoints[i]))
return true;
return false;
}
}
/************************************************************************************************
Main applet code.
************************************************************************************************/
public class Asteroids extends Applet implements Runnable {
// Thread control variables.
Thread loadThread;
Thread loopThread;
// Constants
static final int DELAY = 50; // Milliseconds between screen updates.
static final int MAX_SHIPS = 3; // Starting number of ships per game.
static final int MAX_SHOTS = 6; // Maximum number of sprites for photons,
static final int MAX_ROCKS = 8; // asteroids and explosions.
static final int MAX_SCRAP = 20;
static final int SCRAP_COUNT = 30; // Counter starting values.
static final int HYPER_COUNT = 60;
static final int STORM_PAUSE = 30;
static final int UFO_PASSES = 3;
static final int MIN_ROCK_SIDES = 8; // Asteroid shape and size ranges.
static final int MAX_ROCK_SIDES = 12;
static final int MIN_ROCK_SIZE = 20;
static final int MAX_ROCK_SIZE = 40;
static final int MIN_ROCK_SPEED = 2;
static final int MAX_ROCK_SPEED = 12;
static final int BIG_POINTS = 25; // Points for shooting different objects.
static final int SMALL_POINTS = 50;
static final int UFO_POINTS = 250;
static final int MISSLE_POINTS = 500;
static final int NEW_SHIP_POINTS = 5000; // Number of points needed to earn a new ship.
static final int NEW_UFO_POINTS = 2750; // Number of points between flying saucers.
// Background stars.
int numStars;
Point[] stars;
// Game data.
int score;
int highScore;
int newShipScore;
int newUfoScore;
boolean loaded = false;
boolean paused;
boolean playing;
boolean sound;
boolean detail;
// Key flags.
boolean left = false;
boolean right = false;
boolean up = false;
boolean down = false;
// Sprite objects.
AsteroidsSprite ship;
AsteroidsSprite ufo;
AsteroidsSprite missle;
AsteroidsSprite[] photons = new AsteroidsSprite[MAX_SHOTS];
AsteroidsSprite[] asteroids = new AsteroidsSprite[MAX_ROCKS];
AsteroidsSprite[] explosions = new AsteroidsSprite[MAX_SCRAP];
// Ship data.
int shipsLeft; // Number of ships left to play, including current one.
int shipCounter; // Time counter for ship explosion.
int hyperCounter; // Time counter for hyperspace.
// Photon data.
int[] photonCounter = new int[MAX_SHOTS]; // Time counter for life of a photon.
int photonIndex; // Next available photon sprite.
// Flying saucer data.
int ufoPassesLeft; // Number of flying saucer passes.
int ufoCounter; // Time counter for each pass.
// Missle data.
int missleCounter; // Counter for life of missle.
// Asteroid data.
boolean[] asteroidIsSmall = new boolean[MAX_ROCKS]; // Asteroid size flag.
int asteroidsCounter; // Break-time counter.
int asteroidsSpeed; // Asteroid speed.
int asteroidsLeft; // Number of active asteroids.
// Explosion data.
int[] explosionCounter = new int[MAX_SCRAP]; // Time counters for explosions.
int explosionIndex; // Next available explosion sprite.
// Sound clips.
AudioClip crashSound;
AudioClip explosionSound;
AudioClip fireSound;
AudioClip missleSound;
AudioClip saucerSound;
AudioClip thrustersSound;
AudioClip warpSound;
// Flags for looping sound clips.
boolean thrustersPlaying;
boolean saucerPlaying;
boolean misslePlaying;
// Values for the offscreen image.
Dimension offDimension;
Image offImage;
Graphics offGraphics;
// Font data.
Font font = new Font("Helvetica", Font.BOLD, 12);
FontMetrics fm;
int fontWidth;
int fontHeight;
// Applet information.
public String getAppletInfo() {
return("Asteroids, Copyright 1998 by Mike Hall.");
}
public void init() {
Graphics g;
Dimension d;
int i;
// Take credit.
System.out.println("Asteroids, Copyright 1998 by Mike Hall.");
// Find the size of the screen and set the values for sprites.
g = getGraphics();
d = size();
AsteroidsSprite.width = d.width;
AsteroidsSprite.height = d.height;
// Generate starry background.
numStars = AsteroidsSprite.width * AsteroidsSprite.height / 5000;
stars = new Point[numStars];
for (i = 0; i < numStars; i++)
stars[i] = new Point((int) (Math.random() * AsteroidsSprite.width),
(int) (Math.random() * AsteroidsSprite.height));
// Create shape for the ship sprite.
ship = new AsteroidsSprite();
ship.shape.addPoint(0, -10);
ship.shape.addPoint(7, 10);
ship.shape.addPoint(-7, 10);
// Create shape for the photon sprites.
for (i = 0; i < MAX_SHOTS; i++) {
photons[i] = new AsteroidsSprite();
photons[i].shape.addPoint(1, 1);
photons[i].shape.addPoint(1, -1);
photons[i].shape.addPoint(-1, 1);
photons[i].shape.addPoint(-1, -1);
}
// Create shape for the flying saucer.
ufo = new AsteroidsSprite();
ufo.shape.addPoint(-15, 0);
ufo.shape.addPoint(-10, -5);
ufo.shape.addPoint(-5, -5);
ufo.shape.addPoint(-5, -9);
ufo.shape.addPoint(5, -9);
ufo.shape.addPoint(5, -5);
ufo.shape.addPoint(10, -5);
ufo.shape.addPoint(15, 0);
ufo.shape.addPoint(10, 5);
ufo.shape.addPoint(-10, 5);
// Create shape for the guided missle.
missle = new AsteroidsSprite();
missle.shape.addPoint(0, -4);
missle.shape.addPoint(1, -3);
missle.shape.addPoint(1, 3);
missle.shape.addPoint(2, 4);
missle.shape.addPoint(-2, 4);
missle.shape.addPoint(-1, 3);
missle.shape.addPoint(-1, -3);
// Create asteroid sprites.
for (i = 0; i < MAX_ROCKS; i++)
asteroids[i] = new AsteroidsSprite();
// Create explosion sprites.
for (i = 0; i < MAX_SCRAP; i++)
explosions[i] = new AsteroidsSprite();
// Set font data.
g.setFont(font);
fm = g.getFontMetrics();
fontWidth = fm.getMaxAdvance();
fontHeight = fm.getHeight();
// Initialize game data and put us in 'game over' mode.
highScore = 0;
sound = true;
detail = true;
initGame();
endGame();
}
public void initGame() {
// Initialize game data and sprites.
score = 0;
shipsLeft = MAX_SHIPS;
asteroidsSpeed = MIN_ROCK_SPEED;
newShipScore = NEW_SHIP_POINTS;
newUfoScore = NEW_UFO_POINTS;
initShip();
initPhotons();
stopUfo();
stopMissle();
initAsteroids();
initExplosions();
playing = true;
paused = false;
}
public void endGame() {
// Stop ship, flying saucer, guided missle and associated sounds.
playing = false;
stopShip();
stopUfo();
stopMissle();
}
public void start() {
if (loopThread == null) {
loopThread = new Thread(this);
loopThread.start();
}
if (!loaded && loadThread == null) {
loadThread = new Thread(this);
loadThread.start();
}
}
public void stop() {
if (loopThread != null) {
loopThread.stop();
loopThread = null;
}
if (loadThread != null) {
loadThread.stop();
loadThread = null;
}
}
public void run() {
int i, j;
long startTime;
// Lower this thread's priority and get the current time.
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
startTime = System.currentTimeMillis();
// Run thread for loading sounds.
if (!loaded && Thread.currentThread() == loadThread) {
loadSounds();
loaded = true;
loadThread.stop();
}
// This is the main loop.
while (Thread.currentThread() == loopThread) {
if (!paused) {
// Move and process all sprites.
updateShip();
updatePhotons();
updateUfo();
updateMissle();
updateAsteroids();
updateExplosions();
// Check the score and advance high score, add a new ship or start the flying
// saucer as necessary.
if (score > highScore)
highScore = score;
if (score > newShipScore) {
newShipScore += NEW_SHIP_POINTS;
shipsLeft++;
}
if (playing && score > newUfoScore && !ufo.active) {
newUfoScore += NEW_UFO_POINTS;
ufoPassesLeft = UFO_PASSES;
initUfo();
}
// If all asteroids have been destroyed create a new batch.
if (asteroidsLeft <= 0)
if (--asteroidsCounter <= 0)
initAsteroids();
}
// Update the screen and set the timer for the next loop.
repaint();
try {
startTime += DELAY;
Thread.sleep(Math.max(0, startTime - System.currentTimeMillis()));
}
catch (InterruptedException e) {
break;
}
}
}
public void loadSounds() {
// Load all sound clips by playing and immediately stopping them.
try {
crashSound = getAudioClip(new URL(getDocumentBase(), "crash.au"));
explosionSound = getAudioClip(new URL(getDocumentBase(), "explosion.au"));
fireSound = getAudioClip(new URL(getDocumentBase(), "fire.au"));
missleSound = getAudioClip(new URL(getDocumentBase(), "missle.au"));
saucerSound = getAudioClip(new URL(getDocumentBase(), "saucer.au"));
thrustersSound = getAudioClip(new URL(getDocumentBase(), "thrusters.au"));
warpSound = getAudioClip(new URL(getDocumentBase(), "warp.au"));
}
catch (MalformedURLException e) {}
crashSound.play(); crashSound.stop();
explosionSound.play(); explosionSound.stop();
fireSound.play(); fireSound.stop();
missleSound.play(); missleSound.stop();
saucerSound.play(); saucerSound.stop();
thrustersSound.play(); thrustersSound.stop();
warpSound.play(); warpSound.stop();
}
public void initShip() {
ship.active = true;
ship.angle = 0.0;
ship.deltaAngle = 0.0;
ship.currentX = 0.0;
ship.currentY = 0.0;
ship.deltaX = 0.0;
ship.deltaY = 0.0;
ship.render();
if (loaded)
thrustersSound.stop();
thrustersPlaying = false;
hyperCounter = 0;
}
public void updateShip() {
double dx, dy, limit;
if (!playing)
return;
// Rotate the ship if left or right cursor key is down.
if (left) {
ship.angle += Math.PI / 16.0;
if (ship.angle > 2 * Math.PI)
ship.angle -= 2 * Math.PI;
}
if (right) {
ship.angle -= Math.PI / 16.0;
if (ship.angle < 0)
ship.angle += 2 * Math.PI;
}
// Fire thrusters if up or down cursor key is down. Don't let ship go past
// the speed limit.
dx = -Math.sin(ship.angle);
dy = Math.cos(ship.angle);
limit = 0.8 * MIN_ROCK_SIZE;
if (up) {
if (ship.deltaX + dx > -limit && ship.deltaX + dx < limit)
ship.deltaX += dx;
if (ship.deltaY + dy > -limit && ship.deltaY + dy < limit)
ship.deltaY += dy;
}
if (down) {
if (ship.deltaX - dx > -limit && ship.deltaX - dx < limit)
ship.deltaX -= dx;
if (ship.deltaY - dy > -limit && ship.deltaY - dy < limit)
ship.deltaY -= dy;
}
// Move the ship. If it is currently in hyperspace, advance the countdown.
if (ship.active) {
ship.advance();
ship.render();
if (hyperCounter > 0)
hyperCounter--;
}
// Ship is exploding, advance the countdown or create a new ship if it is
// done exploding. The new ship is added as though it were in hyperspace.
// (This gives the player time to move the ship if it is in imminent danger.)
// If that was the last ship, end the game.
else
if (--shipCounter <= 0)
if (shipsLeft > 0) {
initShip();
hyperCounter = HYPER_COUNT;
}
else
endGame();
}
public void stopShip() {
ship.active = false;
shipCounter = SCRAP_COUNT;
if (shipsLeft > 0)
shipsLeft--;
if (loaded)
thrustersSound.stop();
thrustersPlaying = false;
}
public void initPhotons() {
int i;
for (i = 0; i < MAX_SHOTS; i++) {
photons[i].active = false;
photonCounter[i] = 0;
}
photonIndex = 0;
}
public void updatePhotons() {
int i;
// Move any active photons. Stop it when its counter has expired.
for (i = 0; i < MAX_SHOTS; i++)
if (photons[i].active) {
photons[i].advance();
photons[i].render();
if (--photonCounter[i] < 0)
photons[i].active = false;
}
}
public void initUfo() {
double temp;
// Randomly set flying saucer at left or right edge of the screen.
ufo.active = true;
ufo.currentX = -AsteroidsSprite.width / 2;
ufo.currentY = Math.random() * AsteroidsSprite.height;
ufo.deltaX = MIN_ROCK_SPEED + Math.random() * (MAX_ROCK_SPEED - MIN_ROCK_SPEED);
if (Math.random() < 0.5) {
ufo.deltaX = -ufo.deltaX;
ufo.currentX = AsteroidsSprite.width / 2;
}
ufo.deltaY = MIN_ROCK_SPEED + Math.random() * (MAX_ROCK_SPEED - MIN_ROCK_SPEED);
if (Math.random() < 0.5)
ufo.deltaY = -ufo.deltaY;
ufo.render();
saucerPlaying = true;
if (sound)
saucerSound.loop();
// Set counter for this pass.
ufoCounter = (int) Math.floor(AsteroidsSprite.width / Math.abs(ufo.deltaX));
}
public void updateUfo() {
int i, d;
// Move the flying saucer and check for collision with a photon. Stop it when its
// counter has expired.
if (ufo.active) {
ufo.advance();
ufo.render();
if (--ufoCounter <= 0)
if (--ufoPassesLeft > 0)
initUfo();
else
stopUfo();
else {
for (i = 0; i < MAX_SHOTS; i++)
if (photons[i].active && ufo.isColliding(photons[i])) {
if (sound)
crashSound.play();
explode(ufo);
stopUfo();
score += UFO_POINTS;
}
// On occassion, fire a missle at the ship if the saucer is not
// too close to it.
d = (int) Math.max(Math.abs(ufo.currentX - ship.currentX),
Math.abs(ufo.currentY - ship.currentY));
if (ship.active && hyperCounter <= 0 && ufo.active
&& !missle.active &&
d > 4 * MAX_ROCK_SIZE && Math.random() < .03)
initMissle();
}
}
}
public void stopUfo() {
ufo.active = false;
ufoCounter = 0;
ufoPassesLeft = 0;
if (loaded)
saucerSound.stop();
saucerPlaying = false;
}
public void initMissle() {
missle.active = true;
missle.angle = 0.0;
missle.deltaAngle = 0.0;
missle.currentX = ufo.currentX;
missle.currentY = ufo.currentY;
missle.deltaX = 0.0;
missle.deltaY = 0.0;
missle.render();
missleCounter = 3 * Math.max(AsteroidsSprite.width, AsteroidsSprite.height) / MIN_ROCK_SIZE;
if (sound)
missleSound.loop();
misslePlaying = true;
}
public void updateMissle() {
int i;
// Move the guided missle and check for collision with ship or photon. Stop it when its
// counter has expired.
if (missle.active) {
if (--missleCounter <= 0)
stopMissle();
else {
guideMissle();
missle.advance();
missle.render();
for (i = 0; i < MAX_SHOTS; i++)
if (photons[i].active && missle.isColliding(photons[i])) {
if (sound)
crashSound.play();
explode(missle);
stopMissle();
score += MISSLE_POINTS;
}
if (missle.active && ship.active && hyperCounter <= 0
&& ship.isColliding(missle)) {
if (sound)
crashSound.play();
explode(ship);
stopShip();
stopUfo();
stopMissle();
}
}
}
}
public void guideMissle() {
double dx, dy, angle;
if (!ship.active || hyperCounter > 0)
return;
// Find the angle needed to hit the ship.
dx = ship.currentX - missle.currentX;
dy = ship.currentY - missle.currentY;
if (dx == 0 && dy == 0)
angle = 0;
if (dx == 0) {
if (dy < 0)
angle = -Math.PI / 2;
else
angle = Math.PI / 2;
}
else {
angle = Math.atan(Math.abs(dy / dx));
if (dy > 0)
angle = -angle;
if (dx < 0)
angle = Math.PI - angle;
}
// Adjust angle for screen coordinates.
missle.angle = angle - Math.PI / 2;
// Change the missle's angle so that it points toward the ship.
missle.deltaX = MIN_ROCK_SIZE / 3 * -Math.sin(missle.angle);
missle.deltaY = MIN_ROCK_SIZE / 3 * Math.cos(missle.angle);
}
public void stopMissle() {
missle.active = false;
missleCounter = 0;
if (loaded)
missleSound.stop();
misslePlaying = false;
}
public void initAsteroids() {
int i, j;
int s;
double theta, r;
int x, y;
// Create random shapes, positions and movements for each asteroid.
for (i = 0; i < MAX_ROCKS; i++) {
// Create a jagged shape for the asteroid and give it a random rotation.
asteroids[i].shape = new Polygon();
s = MIN_ROCK_SIDES + (int) (Math.random() * (MAX_ROCK_SIDES - MIN_ROCK_SIDES));
for (j = 0; j < s; j ++) {
theta = 2 * Math.PI / s * j;
r = MIN_ROCK_SIZE + (int) (Math.random() * (MAX_ROCK_SIZE - MIN_ROCK_SIZE));
x = (int) -Math.round(r * Math.sin(theta));
y = (int) Math.round(r * Math.cos(theta));
asteroids[i].shape.addPoint(x, y);
}
asteroids[i].active = true;
asteroids[i].angle = 0.0;
asteroids[i].deltaAngle = (Math.random() - 0.5) / 10;
// Place the asteroid at one edge of the screen.
if (Math.random() < 0.5) {
asteroids[i].currentX = -AsteroidsSprite.width / 2;
if (Math.random() < 0.5)
asteroids[i].currentX = AsteroidsSprite.width / 2;
asteroids[i].currentY = Math.random() * AsteroidsSprite.height;
}
else {
asteroids[i].currentX = Math.random() * AsteroidsSprite.width;
asteroids[i].currentY = -AsteroidsSprite.height / 2;
if (Math.random() < 0.5)
asteroids[i].currentY = AsteroidsSprite.height / 2;
}
// Set a random motion for the asteroid.
asteroids[i].deltaX = Math.random() * asteroidsSpeed;
if (Math.random() < 0.5)
asteroids[i].deltaX = -asteroids[i].deltaX;
asteroids[i].deltaY = Math.random() * asteroidsSpeed;
if (Math.random() < 0.5)
asteroids[i].deltaY = -asteroids[i].deltaY;
asteroids[i].render();
asteroidIsSmall[i] = false;
}
asteroidsCounter = STORM_PAUSE;
asteroidsLeft = MAX_ROCKS;
if (asteroidsSpeed < MAX_ROCK_SPEED)
asteroidsSpeed++;
}
public void initSmallAsteroids(int n) {
int count;
int i, j;
int s;
double tempX, tempY;
double theta, r;
int x, y;
// Create one or two smaller asteroids from a larger one using inactive asteroids. The new
// asteroids will be placed in the same position as the old one but will have a new, smaller
// shape and new, randomly generated movements.
count = 0;
i = 0;
tempX = asteroids[n].currentX;
tempY = asteroids[n].currentY;
do {
if (!asteroids[i].active) {
asteroids[i].shape = new Polygon();
s = MIN_ROCK_SIDES + (int) (Math.random() * (MAX_ROCK_SIDES - MIN_ROCK_SIDES));
for (j = 0; j < s; j ++) {
theta = 2 * Math.PI / s * j;
r = (MIN_ROCK_SIZE + (int) (Math.random() * (MAX_ROCK_SIZE - MIN_ROCK_SIZE))) / 2;
x = (int) -Math.round(r * Math.sin(theta));
y = (int) Math.round(r * Math.cos(theta));
asteroids[i].shape.addPoint(x, y);
}
asteroids[i].active = true;
asteroids[i].angle = 0.0;
asteroids[i].deltaAngle = (Math.random() - 0.5) / 10;
asteroids[i].currentX = tempX;
asteroids[i].currentY = tempY;
asteroids[i].deltaX = Math.random() * 2 * asteroidsSpeed - asteroidsSpeed;
asteroids[i].deltaY = Math.random() * 2 * asteroidsSpeed - asteroidsSpeed;
asteroids[i].render();
asteroidIsSmall[i] = true;
count++;
asteroidsLeft++;
}
i++;
} while (i < MAX_ROCKS && count < 2);
}
public void updateAsteroids() {
int i, j;
// Move any active asteroids and check for collisions.
for (i = 0; i < MAX_ROCKS; i++)
if (asteroids[i].active) {
asteroids[i].advance();
asteroids[i].render();
// If hit by photon, kill asteroid and advance score. If asteroid is large,
// make some smaller ones to replace it.
for (j = 0; j < MAX_SHOTS; j++)
if (photons[j].active && asteroids[i].active
&& asteroids[i].isColliding(photons[j])) {
asteroidsLeft--;
asteroids[i].active = false;
photons[j].active = false;
if (sound)
explosionSound.play();
explode(asteroids[i]);
if (!asteroidIsSmall[i]) {
score += BIG_POINTS;
initSmallAsteroids(i);
}
else
score += SMALL_POINTS;
}
// If the ship is not in hyperspace, see if it is hit.
if (ship.active && hyperCounter <= 0 && asteroids[i].active
&& asteroids[i].isColliding(ship)) {
if (sound)
crashSound.play();
explode(ship);
stopShip();
stopUfo();
stopMissle();
}
}
}
public void initExplosions() {
int i;
for (i = 0; i < MAX_SCRAP; i++) {
explosions[i].shape = new Polygon();
explosions[i].active = false;
explosionCounter[i] = 0;
}
explosionIndex = 0;
}
public void explode(AsteroidsSprite s) {
int c, i, j;
// Create sprites for explosion animation. The each individual line segment of the given sprite
// is used to create a new sprite that will move outward from the sprite's original position
// with a random rotation.
s.render();
c = 2;
if (detail || s.sprite.npoints < 6)
c = 1;
for (i = 0; i < s.sprite.npoints; i += c) {
explosionIndex++;
if (explosionIndex >= MAX_SCRAP)
explosionIndex = 0;
explosions[explosionIndex].active = true;
explosions[explosionIndex].shape = new Polygon();
explosions[explosionIndex].shape.addPoint(s.shape.xpoints[i], s.shape.ypoints[i]);
j = i + 1;
if (j >= s.sprite.npoints)
j -= s.sprite.npoints;
explosions[explosionIndex].shape.addPoint(s.shape.xpoints[j], s.shape.ypoints[j]);
explosions[explosionIndex].angle = s.angle;
explosions[explosionIndex].deltaAngle = (Math.random() * 2 * Math.PI - Math.PI) / 15;
explosions[explosionIndex].currentX = s.currentX;
explosions[explosionIndex].currentY = s.currentY;
explosions[explosionIndex].deltaX = -s.shape.xpoints[i] / 5;
explosions[explosionIndex].deltaY = -s.shape.ypoints[i] / 5;
explosionCounter[explosionIndex] = SCRAP_COUNT;
}
}
public void updateExplosions() {
int i;
// Move any active explosion debris. Stop explosion when its counter has expired.
for (i = 0; i < MAX_SCRAP; i++)
if (explosions[i].active) {
explosions[i].advance();
explosions[i].render();
if (--explosionCounter[i] < 0)
explosions[i].active = false;
}
}
public boolean keyDown(Event e, int key) {
// Check if any cursor keys have been pressed and set flags.
if (key == Event.LEFT)
left = true;
if (key == Event.RIGHT)
right = true;
if (key == Event.UP)
up = true;
if (key == Event.DOWN)
down = true;
if ((up || down) && ship.active && !thrustersPlaying) {
if (sound && !paused)
thrustersSound.loop();
thrustersPlaying = true;
}
// Spacebar: fire a photon and start its counter.
if (key == 32 && ship.active) {
if (sound & !paused)
fireSound.play();
photonIndex++;
if (photonIndex >= MAX_SHOTS)
photonIndex = 0;
photons[photonIndex].active = true;
photons[photonIndex].currentX = ship.currentX;
photons[photonIndex].currentY = ship.currentY;
photons[photonIndex].deltaX = MIN_ROCK_SIZE * -Math.sin(ship.angle);
photons[photonIndex].deltaY = MIN_ROCK_SIZE * Math.cos(ship.angle);
photonCounter[photonIndex] = Math.min(AsteroidsSprite.width,
AsteroidsSprite.height) / MIN_ROCK_SIZE;
}
// 'H' key: warp ship into hyperspace by moving to a random location and starting counter.
if (key == 104 && ship.active && hyperCounter <= 0) {
ship.currentX = Math.random() * AsteroidsSprite.width;
ship.currentX = Math.random() * AsteroidsSprite.height;
hyperCounter = HYPER_COUNT;
if (sound & !paused)
warpSound.play();
}
// 'P' key: toggle pause mode and start or stop any active looping sound clips.
if (key == 112) {
if (paused) {
if (sound && misslePlaying)
missleSound.loop();
if (sound && saucerPlaying)
saucerSound.loop();
if (sound && thrustersPlaying)
thrustersSound.loop();
}
else {
if (misslePlaying)
missleSound.stop();
if (saucerPlaying)
saucerSound.stop();
if (thrustersPlaying)
thrustersSound.stop();
}
paused = !paused;
}
// 'M' key: toggle sound on or off and stop any looping sound clips.
if (key == 109 && loaded) {
if (sound) {
crashSound.stop();
explosionSound.stop();
fireSound.stop();
missleSound.stop();
saucerSound.stop();
thrustersSound.stop();
warpSound.stop();
}
else {
if (misslePlaying && !paused)
missleSound.loop();
if (saucerPlaying && !paused)
saucerSound.loop();
if (thrustersPlaying && !paused)
thrustersSound.loop();
}
sound = !sound;
}
// 'D' key: toggle graphics detail on or off.
if (key == 100)
detail = !detail;
// 'S' key: start the game, if not already in progress.
if (key == 115 && loaded && !playing)
initGame();
return true;
}
public boolean keyUp(Event e, int key) {
// Check if any cursor keys where released and set flags.
if (key == Event.LEFT)
left = false;
if (key == Event.RIGHT)
right = false;
if (key == Event.UP)
up = false;
if (key == Event.DOWN)
down = false;
if (!up && !down && thrustersPlaying) {
thrustersSound.stop();
thrustersPlaying = false;
}
return true;
}
public void paint(Graphics g) {
update(g);
}
public void update(Graphics g) {
Dimension d = size();
int i;
int c;
String s;
// Create the offscreen graphics context, if no good one exists.
if (offGraphics == null || d.width != offDimension.width || d.height != offDimension.height) {
offDimension = d;
offImage = createImage(d.width, d.height);
offGraphics = offImage.getGraphics();
}
// Fill in background and stars.
offGraphics.setColor(Color.black);
offGraphics.fillRect(0, 0, d.width, d.height);
if (detail) {
offGraphics.setColor(Color.white);
for (i = 0; i < numStars; i++)
offGraphics.drawLine(stars[i].x, stars[i].y, stars[i].x, stars[i].y);
}
// Draw photon bullets.
offGraphics.setColor(Color.white);
for (i = 0; i < MAX_SHOTS; i++)
if (photons[i].active)
offGraphics.drawPolygon(photons[i].sprite);
// Draw the guided missle, counter is used to quickly fade color to black when near expiration.
c = Math.min(missleCounter * 24, 255);
offGraphics.setColor(new Color(c, c, c));
if (missle.active) {
offGraphics.drawPolygon(missle.sprite);
offGraphics.drawLine(missle.sprite.xpoints[missle.sprite.npoints - 1],
missle.sprite.ypoints[missle.sprite.npoints - 1],
missle.sprite.xpoints[0], missle.sprite.ypoints[0]);
}
// Draw the asteroids.
for (i = 0; i < MAX_ROCKS; i++)
if (asteroids[i].active) {
if (detail) {
offGraphics.setColor(Color.black);
offGraphics.fillPolygon(asteroids[i].sprite);
}
offGraphics.setColor(Color.white);
offGraphics.drawPolygon(asteroids[i].sprite);
offGraphics.drawLine(asteroids[i].sprite.xpoints[asteroids[i].sprite.npoints - 1],
asteroids[i].sprite.ypoints[asteroids[i].sprite.npoints - 1],
asteroids[i].sprite.xpoints[0], asteroids[i].sprite.ypoints[0]);
}
// Draw the flying saucer.
if (ufo.active) {
if (detail) {
offGraphics.setColor(Color.black);
offGraphics.fillPolygon(ufo.sprite);
}
offGraphics.setColor(Color.white);
offGraphics.drawPolygon(ufo.sprite);
offGraphics.drawLine(ufo.sprite.xpoints[ufo.sprite.npoints - 1],
ufo.sprite.ypoints[ufo.sprite.npoints - 1],
ufo.sprite.xpoints[0], ufo.sprite.ypoints[0]);
}
// Draw the ship, counter is used to fade color to white on hyperspace.
c = 255 - (255 / HYPER_COUNT) * hyperCounter;
if (ship.active) {
if (detail && hyperCounter == 0) {
offGraphics.setColor(Color.black);
offGraphics.fillPolygon(ship.sprite);
}
offGraphics.setColor(new Color(c, c, c));
offGraphics.drawPolygon(ship.sprite);
offGraphics.drawLine(ship.sprite.xpoints[ship.sprite.npoints - 1],
ship.sprite.ypoints[ship.sprite.npoints - 1],
ship.sprite.xpoints[0], ship.sprite.ypoints[0]);
}
// Draw any explosion debris, counters are used to fade color to black.
for (i = 0; i < MAX_SCRAP; i++)
if (explosions[i].active) {
c = (255 / SCRAP_COUNT) * explosionCounter [i];
offGraphics.setColor(new Color(c, c, c));
offGraphics.drawPolygon(explosions[i].sprite);
}
// Display status and messages.
offGraphics.setFont(font);
offGraphics.setColor(Color.white);
offGraphics.drawString("Score: " + score, fontWidth, fontHeight);
offGraphics.drawString("Ships: " + shipsLeft, fontWidth, d.height - fontHeight);
s = "High: " + highScore;
offGraphics.drawString(s, d.width - (fontWidth + fm.stringWidth(s)), fontHeight);
if (!sound) {
s = "Mute";
offGraphics.drawString(s, d.width - (fontWidth + fm.stringWidth(s)), d.height - fontHeight);
}
if (!playing) {
s = "A S T E R O I D S";
offGraphics.drawString(s, (d.width - fm.stringWidth(s)) / 2, d.height / 2);
s = "Copyright 1998 by Mike Hall";
offGraphics.drawString(s, (d.width - fm.stringWidth(s)) / 2, d.height / 2 + fontHeight);
if (!loaded) {
s = "Loading sounds...";
offGraphics.drawString(s, (d.width - fm.stringWidth(s)) / 2, d.height / 4);
}
else {
s = "Game Over";
offGraphics.drawString(s, (d.width - fm.stringWidth(s)) / 2, d.height / 4);
s = "'S' to Start";
offGraphics.drawString(s, (d.width - fm.stringWidth(s)) / 2, d.height / 4 + fontHeight);
}
}
else if (paused) {
s = "Game Paused";
offGraphics.drawString(s, (d.width - fm.stringWidth(s)) / 2, d.height / 4);
}
// Copy the off screen buffer to the screen.
g.drawImage(offImage, 0, 0, this);
}
}