Source code for Mike Hall and Rudy Rucker's Asteroids Alive Applet.

/************************************************************************************************
Asteroids.java
Original code by Mike Hall, www.brainjar.com, 1998.
Altered by Rudy Rucker, rucker@mathcs.sjsu.edu, 1999.

Code last revised December 23, 1999, by Rudy Rucker.

This is the code for a combined Application and Applet named Asteroids.class.
The applet code is pure Java 1.0, except for some optional code for loading
sounds from a jar file in the Asteroids loudSound function. Also we have
to switch between using "inside" or "contains" Polygon metods. We build the
1.0 multiple files version and the 1.1 jar file version for Web deployment.
An annoying thing about Java's lack of #ifdef is that this means we have to
manually comment some code in and out. The Java 1.0 "method not found"
errors or the Java 1.1 "deprecated" warnings will show you where, but they
won't alert you to change the Asteroids loadSound code. The key things
you have to put back in for 1.1 are (a) fix the version number in the JDK
class, (b) comment in the "contains" code in the isTouching method, (c)
comment in the jar soundloading code.

Minimal web pages for the multiple file and jar file version are,
respectively,
<applet code="Asteroids.class" width=w height=h></applet>
<applet archive="Asteroids.jar" code="Asteroids.class" width=w height=h></applet>

In order to make a jar file, run this command line in a directory where
your class and au files are present. Double-check that all au file names
have the same spellings that you use in loadSounds.

jar cf Asteroids.jar *.class *.au

All the application code is manually commented out for the applet build.
The application code consists of the Asteroids main method and all the
AsteroidsFrame class code. This application code must be compiled with
Java 1.1 to work at all, and must be compiled with Java 1.2 for the sounds
to work in the application.

When changing your JDK, remember to change the JDK.version setting in the
first class below, also remember to make changes to the Asteroids loadSound code.

Program Keyboard Controls:
B            - Toggle Black/White background
C            - Toggle Collisions
D - Toggle Graphics Detail
H - Hyperspace jump
M - Toggle Sound
S - Start Game
P - Pause Game
W            - Toggle Bounce/Wrap
Cursor Left - Rotate Left
Cursor Right- Rotate Right Cursor Down - Fire Retro Thrusters
Cursor Up - Fire Thrusters
Spacebar - Fire Cannon

Revision Log.
Started log December 14, 1999.
Actually started revising in November, 1999. Revisions include. Making
an OOP architecture with Sprite and derived sublclasses. Putting a
spritevector in AsteroidsGame. Making into an application and an applet.
Adding collision detection and energy&momentum-conserving collisions. Giving
asteroids an avoidance routine. Made scale indpendent for resizable application
windows. Added wrap/bounce toggle. Added white/black backgroung toggle.
December 14, 1999. Made explosions fancier by drawing the center points of
the offcenter explosion lines. Unified all space and time dependent params
into a Units class, preperatory to making it update timestep independent and
having an adaptive update speed. Made the bullets die when they hit the edge
of the screen.
Dec 17, 1999. Ver 10. Made time units be in seconds. Added circular mode with diskflag.
Dec 20, 1999. Some more debugging and tweaks.
Dec 23, 1999. Ver 11. Some big fixes. I had a bunch of Asteroids variables static,
and this was causing crashes when I left an applet and returned to it.
Having the AudioClip variables statics was a particularly dumb idea. Live
and learn. Now I think you can flip in and out of an applet page without
a crash. There was still an intermittent hang problem, but I think maybe
getting rid of some other static values and cleaning up the sound checking
code fixed this. A desperation move is to set the Asteroids
triedgettingsounds field to true at start prevent any attempts at using
sounds, but I don't think I need this. I think my hang problem was in
fact only a graphical hang (the program still ran in background) becuase
I'd moved the offGraphics initialization away from the right spot.
*********************************************/

import java.awt.*;
import java.net.*;
import java.util.*;
import java.applet.*;
//BEGIN COMMENT OUT FOR JDK 1.0 ============

//Need this to build as application, need WindowAdapter in AsteroidsFrame.
import java.awt.event.*;

//End COMMENT OUT FOR JDK 1.0 ============


//START================JDK CODE===============
/** Use this class to hold a number to signal which
version of the JDK I'm using here: 0 for 1.0, 1 for 1.1,
2 for 1.2. I do this to try and mimic some of the
effect of a C++ #ifdef. This works inside the loadSounds
code, but not really. You still need to manually comment some code
in and out in loadSound. Only set version to 1 if you are using the jar file.*/
class JDK
{
/** Can be 0, 1, or 2 for JDK 1.0, 1.1, or 1.2 (also known as JDK 2). */
    public final static int version = 1;
}
//END===================JDK CODE===============
//START=================VECTOR2 CODE===================
/** A helper class used for pairs of real numbers. */
class Vector2
{
public double x;
public double y;
    static public Vector2 sum(Vector2 u, Vector2 v)
    {
        return new Vector2(u.x + v.x, u.y + v.y);
    }
    static public Vector2 difference(Vector2 u, Vector2 v)
    {
        return new Vector2(u.x - v.x, u.y - v.y);
    }
    /** Scalar product. */
    static public Vector2 product(double r, Vector2 u)
    {
        return new Vector2(r*u.x, r*u.y);
    }
    static public double dotproduct(Vector2 u, Vector2 v)
    { return u.x*v.x + u.y*v.y;}
    /** Returns an angle between -PI and PI expressing the turn from
    u to v. That is, u.turn(Vector2.angle(u,v)) points in the same
    direction as v.*/
    static public double angle(Vector2 u, Vector2 v)
    {
        /* Old way was nondirectional
        if (!u.nonzero() || !v.nonzero())
            return 0.0;
        double cosine = Vector2.dotproduct(u, v) /
            (u.magnitude() * v.magnitude());
        return Math.acos(cosine);
        */
        double turnangle = v.angle() - u.angle();
        if (turnangle > Math.PI)
            turnangle -= 2.0*Math.PI;
        if (turnangle < Math.PI)
            turnangle += 2.0*Math.PI;
        return turnangle;
    }
    /** Returns the actual angle between -PI/2 and 3PI/2 of the vector's
    direction. */
    public double angle()
    {
        double atanangle;
        if (!nonzero())
            return 0;
        if (x == 0)
        {
            if (y < 0)
                return -Math.PI / 2;
            else
                return Math.PI / 2;
        }
        else
        {
            atanangle = Math.atan(y/x); //Ranges -PI/2 to PI/2.
            if (x < 0)
                return (atanangle + Math.PI); //Ranges PI/2 to 3PI/2.
            return atanangle;
        }
    }
    public Vector2(){x = 0.0; y = 0.0;}
    public Vector2(double ix, double iy){x=ix; y=iy;}
    public Vector2(Vector2 u){x=u.x; y=u.y;}
    public Vector2(double angle){x=Math.cos(angle);y=Math.sin(angle);}
    public boolean nonzero(){return (x != 0.0 && y != 0.0);}
    public void copy(Vector2 v){x = v.x; y = v.y;}
    /** Shorter name for existing Point2D setLocation method. */
    public void set(double ix, double iy){x=ix; y=iy;}
    public void set(double angle){x=Math.cos(angle);y=Math.sin(angle);}
    public double magnitude(){return Math.sqrt(x*x + y*y);}
    public double distanceTo(Vector2 u)
{Vector2 dv = Vector2.difference(this,u); return dv.magnitude();}
    public double setMagnitude(double newmag) //Returns old magnitude
    {
    double oldmag = magnitude();
        double multiplier;
        if (oldmag == 0.0)
    set(newmag, 0.0);
        else
        {
            multiplier = newmag/oldmag;
            multiply(multiplier);
        }
        return oldmag;
    }
    public double normalize()//Makes a unit vector, returns old magnitude.
    {
        return setMagnitude(1.0);
    }
    public void setzero(){set(0.0, 0.0);}
    public void add(Vector2 v){x += v.x; y += v.y;}
    public void subtract(Vector2 v){x -= v.x; y -= v.y;}
    public void multiply(double r){ x*=r; y*= r;}
    public void turn(double angle)
    {
        double c = Math.cos(angle), s = Math.sin(angle);
        double newx; //Need this so that you use the "old" x in the y turn line.
        newx = c*x - s*y;
        y = s*x + c*y;
        x = newx;
    }
    public String toString()
    {
        return "(" + (int)x + ", " + (int)y + ")";
    }
}
//END=================VECTOR2 CODE===================

//START=================Vector2Colored CODE===================

/** The Vector2Colored class defines a little colored cross, to use for a star */
class Vector2Colored extends Vector2
{
    Color fillcolor;
    public Vector2Colored(){super();}
    public Vector2Colored(Randomizer rand, double width, double height)
    {
        x = rand.nextDouble(-width/2, width/2);
        y = rand.nextDouble(-height/2, height/2);
        fillcolor = rand.nextColor(0.25, 0.75);
    }
public void draw(Graphics g, RealPixelConverter rtopc)
{
Vector2 pixelpoint = rtopc.transform(this);
int pixelx = (int)pixelpoint.x;
int pixely = (int)pixelpoint.y;
g.setColor(fillcolor);
    g.drawLine(pixelx-1, pixely, pixelx+1, pixely);
    g.drawLine(pixelx, pixely-1, pixelx, pixely+1);
}
}
//END=================Vector2Colored CODE===================

//BEGIN==============REALPIXELCONVERTER CODE===============
/** This class is designed to transform a real-number-sized window into a
pixel window. If were using Java 1.2, it could extend AffineTransform */
class RealPixelConverter
{
//Members
    private int widthpixel = 600, heightpixel = 480;
    private double widthreal = 4.0, heightreal = 3.0;
    private double m00, m02, m11, m12; /* As in an affine transformation where
        where x' = m00 x + m01 y + m02, y' = m10 x + m11 y + m12.*/

//Methods
    public RealPixelConverter()
    {
        fixAffineTransform();
    }
    public RealPixelConverter(double iwidthreal, double iheightreal,
        int iwidthpixel, int iheightpixel)
    {
        widthreal = iwidthreal;
        heightreal = iheightreal;
        widthpixel = iwidthpixel;
        heightpixel = iheightpixel;
        fixAffineTransform();
    }
    private void fixAffineTransform()
    {
        double scalex = (double)widthpixel/widthreal;
        double scaley = (double)heightpixel/heightreal;
        double scale = Math.min(scalex, scaley);
        m00 = scale; m02 = widthpixel/2.0;
        m11 = -scale; m12 = heightpixel/2.0;
/* Like the AffineTransform setTransform(scale, 0.0, 0.0, -scale,
widthpixel/2.0, heightpixel/2.0); The arguments are in the order m00, m10,
m01, m11, m02, m12. */
    }
    public double widthreal(){return widthreal;}
    public double heightreal() {return heightreal;}
    public double widthpixel() {return widthpixel;}
    public double heightpixel() {return heightpixel;}
    public double scale(){return m00;}

    public void setPixelWindow(int width, int height)
    {
        widthpixel = width; heightpixel = height;
        fixAffineTransform();
    }
    public void setRealWindow(double width, double height)
    {
        widthreal = width; heightreal = height; fixAffineTransform();
    }
    public void transform(Vector2 in, Vector2 out)
    {
        out.set(m00*in.x + m02, m11*in.y + m12);
    }
    public Vector2 transform(Vector2 in)
    {
        Vector2 out = new Vector2();
        transform(in, out);
        return out;
    }
    public double transform(double realx)
    {
        return m00*realx;
    }
    public void inverseTransform(Vector2 in, Vector2 out)
    {
        if (m00 == 0.0 || m11 == 0.0)
            return;
        out.set((in.x - m02)/m00, (in.y - m12)/m11);
    }
    public Vector2 inverseTransform(Vector2 in)
    {
        Vector2 out = new Vector2();
        inverseTransform(in, out);
        return out;
    }
    public double inverseTransform(double realx)
    {
        if (m00 == 0.0)
            return realx;
        return realx/m00;
    }

/* Useful method, but we don't currently need it for this program.
Must be commented out for JDK 1.0 in any case. */
/*
    public Vector2 mousePick(MouseEvent evt)
    {
        Vector2 pixelpick = new Vector2(evt.getX(), evt.getY());
        return inverseTransform(pixelpick);
    }
*/

}
//END===================CONVERTER CODE===========
//START=================RANDOMIZER CODE============
/** Add a few useful methods to the standard Random class. This is Java 1.0
compatible, so I had to reimplement a few of the Java 1.2 Random additions.*/
class Randomizer extends Random
{
    public Randomizer(){super();}
    public Randomizer(long seed){super(seed);}
    /** Return a double in the range 0 to hi exclusive of hi. */
    public double nextDouble(double hi){return nextDouble()*hi;}
    /** Return a double in the range lo to hi exclusive of hi. */
    public double nextDouble(double lo, double hi){return lo + nextDouble(hi - lo);}
    /** This is a copy of the code used for the Random nextBoolean() mehtod
    added in the JDK 1.2. We put it here so that we can use our class with
    earlier JDK. It returns a true or a false with equal likelihood.*/
    public boolean nextBoolean() {return nextDouble() < 0.5;}
    /** This is a copy of the code used for the Random nextInt(int) mehtod
    added in the JDK 1.2. We put it here so that we can use our class with
    earlier JDK. It returns an integer between 0 and n, exclusive of n.*/
    public int nextInt(int n)
    {
        int sign = 1; //The code depends on n being positive.
    if (n<=0) {n = -n; sign = -1;}
    int bits, val;
bits = nextInt();
    val = bits % n;
    return sign*val;
    }
    /** Return an int in the range lo to hi inclusive. */
    public int nextInt(int lo, int hi){return lo + nextInt(hi+1-lo);}
    /** Return a Color in which the red, green and blue each have a
    a brightness between lo and hi percent of the maximum value. */
    public Color nextColor(double lo, double hi)
    {
        return new Color((float)nextDouble(lo, hi),     (float)nextDouble(lo, hi),
            (float)nextDouble(lo, hi));
    }
}
//END=================RANDOMZER CODE===================

//START================UNITS CODE====================
/** The Units clas is a catch-all for holding a lot of the spatial size,
temporal duration, and speed constants used in the Asteroids program.
These statics could just as well live in the individual classes, but since
they interact with each other, it's more perspicacious to have them in one
spot. All the times are specified in seconds. The size units are arbitrary,
we generally state them in terms of SIZE_WORLD, which is the diameter of the
world where the asteroids live. A speed of k * SIZE_WORLD means that the
object travels k* SIZE_WORLD in one second, and that the object takes
1/k seconds to travel all the way across the world. Thus a speed of 0.25 * SIZE_WORLD
means the object travels a quarter wau across in one second, and takes four seconds
to travel all the way across.*/
class Units
{
//Asteroids main units===============================
/** Seconds between screen updates. Used in the Asteroids main function's
update as part of an argument to a wait call. We have 0.04, which means we
try for 25 updates a second. I still need to look into
exactly how to handle the case when the processor can't go this fast. */
static final double TIMEUNIT = 0.04;
/** The width of the unchanging real world in which the Asteroids move, the
document size, rather than the view size. Used in Asteroids.*/
static final double WIDTH_WORLD = 510;
/** The height of the unchanging real world in which the Asteroids move, the
document size, rather than the view size. Used in Asteroids.*/
static final double HEIGHT_WORLD = 510;
/** The minimum cross diameter of the world. Used for the rest of the Unit
definitions. */
static final double SIZE_WORLD = Math.min(WIDTH_WORLD, HEIGHT_WORLD);
/** Useful for disk wrap. */
static final double RADIUS_WORLD = SIZE_WORLD/2.0;
//Asteroid Sprite Units======================
/** Number of updates to pause after an old wave of asteroids before a new one.
Was 30.*/
static final double STORM_PAUSE = 1.0;
//Sprite Units======================
/** Default Sprite maxspeed, crosses world in 1.2 seconds. */
static final double MAX_SPEED_SPRITE = 0.8 * SIZE_WORLD;
//SpriteAsteroid Units======================
/** Min speed of asteroid motion in world units per update. 0.1 */
static final double MIN_SPEED_ASTEROID = 0.1 * SIZE_WORLD;
/* Max speed for asteroid. 0.3 */
static final double MAX_SPEED_ASTEROID = 0.4 * SIZE_WORLD;
/** Increment to the maxspeed (short of MAX_SPEED_ASTEROID) to be used
when starting a new board. */
static final double SPEED_INCREMENT_ASTEROIDS = 0.04 * SIZE_WORLD;
/* Min size of asteroid side in units defined using the game doc. */
static final double MIN_SIZE_ASTEROID = 0.04 * SIZE_WORLD;
/** Max size of asteroid side in world units. */
static final double MAX_SIZE_ASTEROID = 0.065 * SIZE_WORLD;
/** Max speed of asteroid rotation, in radians per second. */
static final double MAX_TURN_SPEED_ASTEROID = 0.3 * Math.PI;
/** Acceleration used by asteroid to avoid the nearest bullet. */
static final double ACCELERATION_ASTEROID_DART = 2.0 * SIZE_WORLD;
/** Factor by which a darting asteroid is allowed to exceed its
normal maxspeed. */
static final double DARTING_SPEEDUP = 2.0;
/** Acceleration towards the ship. */
static final double ACCELERATION_ASTEROID_TOSHIP = 0.002 * SIZE_WORLD;
//UFO Units======================
/** Min speed of Ufo motion in units per update. Was 3. */
static final double MIN_SPEED_UFO = 0.012 * SIZE_WORLD;
/** Speed in world units per second. */
static final double START_MAX_SPEED_UFO = 0.72 * SIZE_WORLD;
/** Seconds that a UFO lives */
static final double LIFETIME_UFO = 2.0;
/** Seconds between missile shots, so the missles aren't too often. */
static final double FIRE_WAIT_UFO = 2.5;
//==========Ship Units======================
/** Ship's maximum speed. */
static final double MAX_SPEED_SHIP = 0.64 * SIZE_WORLD;
/** Number of seconds that a ship is immune while doing a hyperjump. */
static final double HYPER_WAIT_SHIP = 3.0;
/** Seconds between bullet shots, so the bullets aren't too close together. */
static final double FIRE_WAIT_SHIP = 0.035;
/** Size of ship's rotation speed when you hold down the arrow key.
Needs to be small so you can aim accurately enough to hit a missile, on the
other hand it needs to be large so you can spin and fire off a big sweep of
bullets. So we have two kinds of TURN_STEP. Speed is radians per second. 0.6 */
static final double TURN_STEP_SLOW_SHIP = 0.6 * Math.PI;
/** See TURN_STEP_SLOW. Radians per second. 1.2*/
static final double TURN_STEP_FAST_SHIP = 1.8 * Math.PI;
/** Acceleration in world widths per second per second. 1.0 */
static final double ACCELERATION_THRUST = 1.0 * SIZE_WORLD;
//Missile Units===============================
/** Speed the missile uses to pursue a ship. Worlds per second. 0.27. */
static final double SPEED_MISSILE = 0.25 * SIZE_WORLD;
/** Missile lives long enough to cross the screen once. */
static final double LIFETIME_MISSILE = SIZE_WORLD/SPEED_MISSILE;
//Explosion Units===============================
/** Multiply this times a shape vertex's distance from the origin to get
the outward speed of an explosion fragment. 4.0*/
static final double SPEED_MULTIPLIER_EXPLOSION = 4.0;
/** Max speed of explosion rotation. Radians per second. 1.3 */
static final double MAX_TURN_SPEED_EXPLOSION = 1.3 * Math.PI;
/** Number of seconds that explosion lines live. 1.5*/
static final double LIFETIME_EXPLOSION = 1.5;
//Bullet Units===============================
/**Let Bullet go faster than the ship can go. */
static final double MAX_SPEED_BULLET = 1.5 * MAX_SPEED_SHIP;
/** Bullet speed in worlds per second. 0.8 */
static final double SPEED_BULLET = 1.0 * SIZE_WORLD;
/** life in seconds. Make sure it lives long enough to reach the edge of
the screen. 2.0*/
static final double LIFETIME_BULLET = 1.0;
}
//END==================UNITS CODE============

//START=================SPRITE CODE===================
/**
The Sprite class defines a game object, including its shape, position, movement and
rotation. It also can detemine if two objects collide. We have a number of
derived classes for the special kinds of sprites.
*/

abstract class Sprite
{
// Fields:
/** The basic shape which gets translated, rotated, and pixeld.*/
Polygon shape;
/** A flag to indicate if the sprite is currently alive and visible. */
boolean active;
/** Measure the sprite's age in seconds to time things like explosions,
hyperjumps and waits between bullet shots. */
double age;
/** What to do when the asteroids hit the edge of the screen - initially false*/
public boolean bounce = true;
/** The current rotation aspect. */
double angle;
/** The size of the thing. Make this private so nobody chagnes it directly,
must be chagned using setRadius, so as to keep mass in synch. */
private double radius;
/** We need an accessor for radius. */
public double radius(){return radius;}
/** Use this to set a fake radius to make something hard to hit. */
public void setFakeRadius(double fakeradius){radius = fakeradius;}
/** We'll maintain the mass as the cube of the radius, setting it in
the same fixRadius function that fixes the radius. */
private double mass;
/** Accessor */
public double mass(){return mass;}
/** The "acceleration" or
speed at which the sprite is rotating. That is, this is d angle/ dt.*/
double angularAcceleration;
/** The screen pixel position.*/
Vector2 position;
/** The pixel position. */
Vector2 positiontransform;
/** The velocity vector. */
Vector2 velocity;
/** The maximum speed. */
double maxspeed;
/** The acceleration vector. */
Vector2 acceleration;
/** The transformed shape polygon, after the rotation and the translation. */
Polygon shapetransform;
/** Whether or not to fill the sprite in. Default true. Set false for the
Spritebullet only. */
boolean fillflag;
/** The color to fill in the sprite with. */
Color fillcolor;
/** The color to use for the sprite's outline. Normally white. */
Color edgecolor;
/** Sprites can use the updateCounter to time things like explosions or
a fade to black. UFO and missiles use this to time how long they live.*/
double ageDone;
/** Is the sound currently on?*/
boolean soundplaying;
/** The "owner" applet or application. We need this as a way of referring
to the AudioClip sounds and the sound flags. Also we use it to refer
to the game member of Asteroids as a way to see the other
sprites. */
Asteroids ownerApp;

/** The Sprite constructor sets everything to 0, except for the fillcolor,
which gets randomized. */
public Sprite(Asteroids owner)
{
    ownerApp = owner;
    shape = new Polygon();
    shapetransform = new Polygon();
    active = false;
    age = 0.0;
    angle = 0.0;
    angularAcceleration = 0.0;
    radius = 0.0;
    mass = 0.0;
    position = new Vector2();
    positiontransform = new Vector2();
    velocity = new Vector2();
    acceleration = new Vector2();
    randomizeFillcolor();
    maxspeed = Units.MAX_SPEED_SPRITE;
    position.set(0.0, 0.0);
    velocity.set(0.0, 0.0);
    acceleration.set(0.0, 0.0);
    edgecolor = Color.white;
    fillflag = true;
}

public void randomizeFillcolor()
{
    fillcolor = ownerApp.randomizer.nextColor(0.5, 1.0);
}

// Methods:

/** First sets the centroid to be the average of the shape vertices,
then translatees the shape so that its centroid is at the origin,
then sets the radius value as the maximum distance from the origin to one
of the shape vertices. */
public void fixRadius()
{
    radius = 0;
    if (shape.npoints == 0)
        return;
    Vector2[] Vector2points = new Vector2[shape.npoints];
    double distance;
    Vector2 centroid = new Vector2();
    for (int i=0; i<Vector2points.length; i++)
    {
        Vector2points[i] = new Vector2(shape.xpoints[i], shape.ypoints[i]);
        centroid.add(Vector2points[i]);
    }
    centroid.multiply(1/shape.npoints);
    for (int i=0; i<Vector2points.length; i++)
    {
        Vector2points[i].subtract(centroid);
        shape.xpoints[i] = (int)Vector2points[i].x;
    shape.ypoints[i] = (int)Vector2points[i].y;
        distance = Vector2points[i].magnitude();
        if (distance > radius)
            radius = distance;
    }
    mass = Math.pow(radius, 3);
}


/** Update the rotation and position coordinates based on the delta
values. If the coordinates move off the edge of the screen, they are wrapped
around to the other side. */
public boolean advance(double dt)
{
    age += dt;
    angle += dt * angularAcceleration;
    velocity.add(Vector2.product(dt, acceleration));
    if (velocity.magnitude() > maxspeed)
        velocity.setMagnitude(maxspeed);
    position.add(Vector2.product(dt, velocity));
    return wrapandbounce();
}

/** Handles the wrapping or bouncing. We have four possibilites: wrap or
bounce in either a disk world or a rectangular world.*/
public boolean wrapandbounce()
{
    boolean outcode = false;
//Now wrap things.
    if (angle < 0)
        angle += 2 * Math.PI;
    if (angle > 2 * Math.PI)
        angle -= 2 * Math.PI;
//DISK code
    double mag = position.magnitude();
    /* If you're at the center, return now, as you might get into trouble
    trying to work with a zero magnitude vector. */
    if (mag == 0.0)
        return false;
    double limit = Units.RADIUS_WORLD - radius;
    if (ownerApp.game.diskflag) //Think of world as a disk.
    {
        if (mag > limit)
        {
            outcode = true;
    /* The disk bounce uses the idea that you are effectively
    reflecting the velocity in a line perpendicular to the radial line to
    the postion. You can do this by rotating velocity to the position
    rotating through PI radians, and then rotating the same amount and
    sense that you rotated to get to the position direction. */
            if (bounce)
            {
                double turnangle = Vector2.angle(velocity, position);
                velocity.turn(Math.PI + 2.0*turnangle);
                position.multiply(limit/mag);
            }
            else //wrap
                position.multiply(-limit/mag);
        }
    } //End disk case.
    else //!ownerApp.game.diskflag means rectangular world
    {
        if (bounce)
        { //bounce off walls
            if (position.x < radius-Units.WIDTH_WORLD / 2)
{    velocity.x *= -1;
                position.x = radius-Units.WIDTH_WORLD / 2;
                outcode = true;
            }
            if (position.x > -radius+Units.WIDTH_WORLD / 2)
{    velocity.x *= -1;
                position.x = -radius+Units.WIDTH_WORLD / 2;
                outcode = true;
            }
            if (position.y < radius-Units.HEIGHT_WORLD / 2)
{    velocity.y *= -1;
                position.y = radius-Units.HEIGHT_WORLD / 2;
                outcode = true;
            }
            if (position.y > -radius+Units.HEIGHT_WORLD / 2)
            {     velocity.y *= -1;
                position.y = -radius+Units.HEIGHT_WORLD / 2;
                outcode = true;
            }
        }
        else
        { //wrap to other side
            if (position.x < -Units.WIDTH_WORLD / 2)
                {position.x += Units.WIDTH_WORLD;
                outcode = true;}
            if (position.x > Units.WIDTH_WORLD / 2)
                {position.x -= Units.WIDTH_WORLD;
                outcode = true;}
            if (position.y < -Units.HEIGHT_WORLD / 2)
                {position.y += Units.HEIGHT_WORLD;
                outcode = true;}
            if (position.y > Units.HEIGHT_WORLD / 2)
            {position.y -= Units.HEIGHT_WORLD;
                outcode = true;}
        }
    } //End rectangular world
    return outcode;
}



/** Applies the rotation and the position translation to the shape to get the
shapetransform polygon. The shape is rotated angle degrees around the origin
and then translated by the amount pixel*position + (width/2, height/2).*/
public void render()
{
    int i;
    RealPixelConverter rtopc = ownerApp.realpixelconverter;
    shapetransform = new Polygon();
    rtopc.transform(position, positiontransform);
    double scale = rtopc.scale();
    double scalecos = scale*Math.cos(angle);
    double scalesin = scale*Math.sin(angle);
    for (i = 0; i < shape.npoints; i++)
/* I write out the transform operation by hand here to make it faster.
I am doing three things to each vertex of the polygon (1) rotate it
around the origin, (2) carry out the RealPixelConverter transform, which
means multiplying each term by scale or -scale, (3) add the point to
the positiontransform location.
I need to do Math.max, because the IE 4 browser treats negative pixel
coordinates a large positives, making a poly with a vertex offscreen to top
or left be drawn as if that vertex were at positive infinity, making a
horizontal or vertical line. IE 5 and Netscape don't do this. Java: write
once debug everywhere. */
        shapetransform.addPoint(
            Math.max(0,(int)Math.round(
            scalecos*shape.xpoints[i] -
            scalesin*shape.ypoints[i] +
            positiontransform.x)),
        Math.max(0, (int)Math.round(
            -(scalecos*shape.ypoints[i] + //Flip sign because pixel y runs down.
        scalesin*shape.xpoints[i]) +
            positiontransform.y)));
}

/** Just look at the distances between the positions of the two and compare to
the sum of the radii. This is a faster method than isTouching, though
less accurate. We use this for asteroid-asteroid collisions only. It is
good for this purpose becuase it makes the collision back-off correction look
smooth. */
public boolean isColliding(Sprite s)
{
    return (position.distanceTo(s.position) < radius + s.radius);
}

/** This is more accurate than isColliding, but a bit slower.
We use it for the ship-asteroid collisions, for ship-bullet, and for
bullet-missile collisions. You have to use inside for JDK 1.0, but don't
use it for JDK 1.1 as it doesn't work there. */
public boolean isTouching(Sprite s)
{
    int i;
    if (JDK.version == 0)
    {
        for (i = 0; i < s.shapetransform.npoints; i++)
            if (shapetransform.inside( //contains method for JDK 1.1
                s.shapetransform.xpoints[i],s.shapetransform.ypoints[i]))
                return true;
        for (i = 0; i < shapetransform.npoints; i++)
            if (s.shapetransform.inside( //contains method for JDK 1.1
                shapetransform.xpoints[i], shapetransform.ypoints[i]))
                return true;
    }
else
    {
//BEGIN COMMENT OUT FOR JDK 1.0=========

    for (i = 0; i < s.shapetransform.npoints; i++)
            if (shapetransform.contains(
                s.shapetransform.xpoints[i],s.shapetransform.ypoints[i]))
                return true;
        for (i = 0; i < shapetransform.npoints; i++)
            if (s.shapetransform.contains(
                shapetransform.xpoints[i], shapetransform.ypoints[i]))
                return true;

//END COMMENT OUT FOR JDK 1.0=======
    }
    return false;
}

/** The abstract reset method is called by the child sprite constructors, and
may also be called when resetting a sprite's state.*/
public abstract void reset();

/** The update method can be overloaded by the child sprite. */
public abstract void update();

/** The base method bails out if the the Asteroids soundsloaded isn't
true, and it won't turn on a sound if Asteroids soundflag is off. */
public boolean loopsound(boolean onoff)
{
    if (ownerApp == null)
        return false;
    if (!(ownerApp.triedgettingsounds && ownerApp.soundsloaded))
        return false;
    if (onoff && !ownerApp.soundflag)
        return false;
    soundplaying = onoff; //The value you'd like to do.
    return true;
}

/** The stop method sets active to false and turns off the sound. */
public void stop()
{
    active = false;
    ageDone = age;
    loopsound(false);
}
/** Create sprites for explosion animation. Each individual line segment
of the given shapetransform is used to create a new shapetransform that will
move outward from the shapetransform's original position with a random
rotation. We deliberately don't call setRadius for the explosions because
we want their centers not to be in the middle of their segments. Instead
we set their radii by hand. */
public void explode()
{
    int skip, i, j;
    SpriteExplosion explosion;
    skip = 1;
    if (shape.npoints >= 12)
        skip = 2;
    for (i = 0; i < shape.npoints; i += skip)
    {
    explosion = ownerApp.game.nextExplosion();
        explosion.active = true;
        explosion.shape = new Polygon();
        explosion.shape.addPoint(shape.xpoints[i], shape.ypoints[i]);
        j = i + 1;
        if (j >= shape.npoints)
            j -= shape.npoints;
        explosion.shape.addPoint(shape.xpoints[j], shape.ypoints[j]);
        explosion.angle = angle;
        explosion.radius = radius;
        explosion.angularAcceleration = ownerApp.randomizer.nextDouble(
        -Units.MAX_TURN_SPEED_EXPLOSION, Units.MAX_TURN_SPEED_EXPLOSION);
        explosion.position.copy(position);
        explosion.velocity.set(-shape.xpoints[i], -shape.ypoints[i]);
        explosion.velocity.multiply(Units.SPEED_MULTIPLIER_EXPLOSION);
        explosion.ageDone = explosion.age + Units.LIFETIME_EXPLOSION;
        explosion.render();
    }
}
/** Use the fillPolygon and drawPolygon functions to draw. */
public void draw(Graphics g, boolean detail)
{
    if (active)
    {
        if (fillflag && detail)
        {
            g.setColor(fillcolor);
            g.fillPolygon(shapetransform);
        }
        g.setColor(edgecolor);
        g.drawPolygon(shapetransform);
/*
//Draw the bounding box
        Rectangle box = shapetransform.getBounds();
        g.setColor(edgecolor);
        g.drawRect(box.x, box.y, box.width, box.height);
//Draw the radius circle and center point
        int centerx = (int)positiontransform.x;
        int centery = (int)positiontransform.y;
        int pixeldradius = (int)(Asteroids.realpixelconverter.scale()*radius());
    g.drawOval(centerx - pixeldradius,
            centery - pixeldradius,
            2*pixeldradius, 2*pixeldradius);
        g.drawLine(centerx - 1, centery,
            centerx + 1, centery);
        g.drawLine(centerx, centery-1,
            centerx, centery+1);
*/
    }
}
} //end of Sprite class

/** The player sprite, controlled by the keyboard. */
class SpriteShip extends Sprite
{
/** How many turn step updates before you shift into fast turning? */
static final int TURN_STEP_THRESHOLD = 8;
/** Track how many turn steps you've done on this key press.*/
int turnstepcount = 0;
/** A function that returns the slow or fast TURN_STEP depending on
turnstepcount. */
double turnstep()
{
if (turnstepcount<TURN_STEP_THRESHOLD) return Units.TURN_STEP_SLOW_SHIP;
else return Units.TURN_STEP_FAST_SHIP;
}
/** Next score needed to get a new ship. */
static int newShipScore;
/** Number of ships left in game */
static int shipsLeft;
/** Key flag for left arrow key pressed. */
boolean left = false;
/** Key flag for right arrow key pressed. */
boolean right = false;
/** Key flag for up arrow key pressed. */
boolean up = false;
/** Key flag for down arrow key pressed. */
boolean down = false;
/** Machine-gun firing key. */
boolean firing = false;
/** A ship needs an ageDoneHyperjump as well as the Sprite ageDone,
uses it for fading out of hyperjumps.*/
double ageDoneHyperjump;
/** Use the ageShootwait to time the wait between bullets shot. */
double ageDoneShootwait;
/** Use this flag to mean that your ship never dies. */
static boolean godmode = false;

/** Constructor sets colors, calls reset(). */
public SpriteShip(Asteroids owner)
{
    super(owner);
    maxspeed = Units.MAX_SPEED_SHIP;
    fillcolor = Color.black;
    shape.addPoint(10, 0);
    shape.addPoint(-10, 7);
    shape.addPoint(-10, -7);
    fixRadius();
    ageDoneHyperjump = age;
    reset();
}
/** Puts the ship motionless in the middle of the screen. */
public void reset()
{
    active = true;
    angle = Math.PI/2;
    angularAcceleration = 0.0;
    position.setzero();
    velocity.setzero();
    render();
    loopsound(false);
    ageDone = age;
    ageDoneShootwait = age;
}

/** Toggles the thrustersSound. */
public boolean loopsound(boolean onoff)
{
    if (!super.loopsound(onoff))
        return false;
    if (!soundplaying)
        ownerApp.thrustersSound.stop();
    else
        ownerApp.thrustersSound.loop();
    return true;
}
/** The ship update method rotates the ship if left or right cursor key is down,
Fires thrusters if up or down arrow key is down, calls advance and render,
counts down the hyperspace counter if ship is in hyperspace. If 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. */
public void update()
{
    double dx, dy;
    if (!ownerApp.game.gameonflag)
        return;
    angularAcceleration = 0.0;
// Rotate the ship if left or right cursor key is down.
    if (left)
    {    angularAcceleration = turnstep();
    turnstepcount++;
    }
    if (right)
    {    angularAcceleration = -turnstep();
        turnstepcount++;
    }
/* Fire thrusters if up or down cursor key is down. */
    acceleration.set(0.0, 0.0);
    Vector2 thrust = new Vector2(Math.cos(angle), Math.sin(angle));
    thrust.multiply(Units.ACCELERATION_THRUST);
    if (up)
        acceleration = thrust;
    if (down)
        acceleration = Vector2.product(-1.0, thrust);
/* Try to shoot if firing*/
    if (firing)
        fire();
/* 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.*/
    if (!active)
    {
        if (age > ageDone)
        {
            if(shipsLeft > 0)
            {
                reset();
                ageDoneHyperjump = age + Units.HYPER_WAIT_SHIP;
            }
            else
                ownerApp.game.stop();
        }
    }
}

public void stop()
{
    if (godmode)
        return;
    super.stop();
    ageDone = age + Units.LIFETIME_EXPLOSION;
    ageDoneShootwait = ageDone;
    if (shipsLeft > 0)
        shipsLeft--;
}

/** Make a sound and get a blank bullet slot or the oldest
bullet slot and put a new bullet in there, aimed in your direction
and starting at your location. */
public void fire()
{
    if(age <= ageDoneShootwait)
        return;
    SpriteBullet bullet = ownerApp.game.nextBullet();
    if (ownerApp.soundflag && ownerApp.soundsloaded && !ownerApp.game.paused)
        ownerApp.fireSound.play();
bullet.initialize(this);
    ageDoneShootwait = age + Units.FIRE_WAIT_SHIP;
}

/** Randomize your location and give yourself an "immunity" for
a few cycles. */
public void hyperjump()
{
    position.x = ownerApp.randomizer.nextDouble(-Units.WIDTH_WORLD/2,
Units.WIDTH_WORLD/2);
    position.y = ownerApp.randomizer.nextDouble(-Units.HEIGHT_WORLD/2,
Units.HEIGHT_WORLD/2);
    ageDoneHyperjump = age + Units.HYPER_WAIT_SHIP;
    if (ownerApp.soundflag && ownerApp.soundsloaded && !ownerApp.game.paused)
        ownerApp.warpSound.play();
}
/** Process the arrow keys and spacebar. */
public void keyPressed(int key)
{
// Arrowkeys: Set flags and use them.
    if (key == Event.LEFT)
        left = true;
    if (key == Event.RIGHT)
        right = true;
    if (key == Event.UP)
        up = true;
    if (key == Event.DOWN)
        down = true;
//Use arrowkey flags.
    if ((up || down) && active && !soundplaying)
    {    if (ownerApp.soundflag && !ownerApp.game.paused)
            loopsound(true);
    }
// Spacebar start firing.
    if (key == 32 && active)
        firing = true;
// 'H' key: warp ship into hyperspace by moving to a random location.
    if (key == 104 && active && age > ageDoneHyperjump)
        hyperjump();
}

/** Process the arrow keys and spacebar. */
public void keyReleased(int key)
{
// Check if any cursor keys where released and set flags.
    if (key == Event.LEFT)
{
        left = false;
turnstepcount = 0;
}
    if (key == Event.RIGHT)
{
        right = false;
turnstepcount = 0;
}
    if (key == Event.UP)
        up = false;
    if (key == Event.DOWN)
        down = false;
    if (key == 32)
        firing = false;
    if (!up && !down)
        loopsound(false);
}

/** This fades the edge from black to white when you have a black fill,
as is customary with a black background. It fades the edge from white to black
otherwise. */
public void draw(Graphics g, boolean detail)
{
int brightness;
if (age > ageDoneHyperjump)
brightness = 255;
else
    brightness = 255 -
(int)((255-64)*(ageDoneHyperjump - age)/Units.HYPER_WAIT_SHIP);
if (ownerApp.backgroundcolor != Color.black)
    brightness = 255 - brightness;
    edgecolor = new Color(brightness, brightness, brightness);
    super.draw(g, detail);
}
}

/** Our jagged asteroid sprites, they come in two sizes. */
class SpriteAsteroid extends Sprite
{
/** Min number of asteroid sides. 9. */
static final int MIN_SIDES = 9;
/** Max number of asteroid sides. 13. */
static final int MAX_SIDES = 13;
/** Points for shooting a big asteroid. 25.*/
static final int BIG_POINTS = 25;
/** Points for shooting a small asteroid. 50.*/
static final int SMALL_POINTS = 50;
/** Number of fragments to split an asteroid into. */
static final int FRAG_COUNT = 2;
/** Flag used to distinguish between big and small asteroids. */
boolean asteroidIsSmall;
/** I use the same speed for all the asteroids, so this is static. */
//static int speed;
/**Number of asteroids remaining.*/
static int asteroidsLeft;
/**The number of updates to pause between waves of asteroids. */
static double ageDoneStormpause;
/**The age of the whole flock of asteroids */
static double agegame = 0.0;
/** Use this to allow temporary speedup when running away from a bullet. */
boolean darting = false;

public SpriteAsteroid(Asteroids owner)
{
    super(owner);
    maxspeed = Units.MAX_SPEED_ASTEROID;
    reset();
}

/** Creates a random jagged shape and movement. Places asteroid
at an edge of the screen. Randomizes the fillcolor.*/
public void reset()
{
    int i, j;
    int sides;
    double theta, r;
    int x, y;

    randomizeFillcolor();
// Create a jagged shape for the asteroid and give it a random rotation.
    shape = new Polygon();
    sides = + ownerApp.randomizer.nextInt(MIN_SIDES, MAX_SIDES);
    for (j = 0; j < sides; j ++)
        {    theta = j* (2.0 * Math.PI) / sides;
            r = ownerApp.randomizer.nextDouble(Units.MIN_SIZE_ASTEROID, Units.MAX_SIZE_ASTEROID);
            x = (int) -Math.round(r * Math.sin(theta));
            y = (int)     Math.round(r * Math.cos(theta));
            shape.addPoint(x, y);
        }
    fixRadius();
    angle = 0.0;
angularAcceleration = ownerApp.randomizer.nextDouble(
-Units.MAX_TURN_SPEED_ASTEROID, Units.MAX_TURN_SPEED_ASTEROID);
// Place the asteroid at one edge of the screen.
    if (ownerApp.randomizer.nextBoolean())
    {    position.x = -Units.WIDTH_WORLD / 2;
        if (ownerApp.randomizer.nextBoolean())
            position.x = Units.WIDTH_WORLD / 2;
        position.y = ownerApp.randomizer.nextDouble(-Units.HEIGHT_WORLD/2,
            Units.HEIGHT_WORLD/2);
    }
    else
    {    position.x = ownerApp.randomizer.nextDouble(-Units.WIDTH_WORLD/2,
            Units.WIDTH_WORLD/2);
        position.y = -Units.HEIGHT_WORLD / 2;
        if (ownerApp.randomizer.nextBoolean())
            position.y = Units.HEIGHT_WORLD / 2;
    }
// Set a random motion for the asteroid.
    double direction = ownerApp.randomizer.nextDouble(2.0 * Math.PI);
    velocity.set(Math.cos(direction), Math.sin(direction));
    double speed = ownerApp.randomizer.nextDouble(Units.MIN_SPEED_ASTEROID, maxspeed);
    velocity.setMagnitude(speed);
    render();
    asteroidIsSmall = false;
    if (active == false)
    {
        active = true;
        asteroidsLeft++;
    }
}

/** Create one or more 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. */
public void split(int fragcount)
{
    double oldX = position.x;
    double oldY = position.y;
    Vector2 oldvelocity = new Vector2(velocity);
    Color oldfillcolor = new Color(fillcolor.getRGB());
    double oldmaxspeed = maxspeed;
    int sides;
    double theta, r;
    int x, y;
    SpriteAsteroid smallasteroid;
//First replace this asteroid by a smaller one.
    smallasteroid = this;
    for (int i=0; i<fragcount; i++)
    {
        smallasteroid.shape = new Polygon();
        sides = ownerApp.randomizer.nextInt(MIN_SIDES, MAX_SIDES);
        for (int j = 0; j < sides; j++)
        {    theta = j * (2.0 * Math.PI) / sides;
            r =     ownerApp.randomizer.nextDouble(Units.MIN_SIZE_ASTEROID,
Units.MAX_SIZE_ASTEROID) / fragcount;
            x = (int) -Math.round(r * Math.sin(theta));
            y = (int) Math.round(r * Math.cos(theta));
            smallasteroid.shape.addPoint(x, y);
        }
        smallasteroid.fixRadius();
        smallasteroid.active = true;
        smallasteroid.angle = 0.0;
        smallasteroid.angularAcceleration = ownerApp.randomizer.nextDouble(
-Units.MAX_TURN_SPEED_ASTEROID, Units.MAX_TURN_SPEED_ASTEROID);
        smallasteroid.position.x = oldX;
        smallasteroid.position.y = oldY;
        smallasteroid.fillcolor = oldfillcolor;
        smallasteroid.velocity.copy(oldvelocity);
        smallasteroid.maxspeed = oldmaxspeed;
        /* Turn between -90 and 90 degrees */
        smallasteroid.velocity.turn(ownerApp.randomizer.nextDouble(-Math.PI/2.0, Math.PI/2.0));
        smallasteroid.velocity.multiply(ownerApp.randomizer.nextDouble(0.5, 1.5));
        smallasteroid.render();
        smallasteroid.asteroidIsSmall = true;
        asteroidsLeft++;
        smallasteroid = ownerApp.game.nextAsteroid();
        if (smallasteroid == null)
            break;
    }
}

/** Give the asteroids an ability to speed up for darting. */
public boolean advance(double dt)
{
    angle += dt*angularAcceleration;
    velocity.add(Vector2.product(dt, acceleration));
    if (darting)
    {
        if (velocity.magnitude() > Units.DARTING_SPEEDUP * maxspeed)
            velocity.setMagnitude(Units.DARTING_SPEEDUP * maxspeed);
//        System.out.println("accel " + acceleration + "vel " + velocity);
    }
    else if (velocity.magnitude() > maxspeed)
        velocity.setMagnitude(maxspeed);
    position.add(Vector2.product(dt, velocity));
    return wrapandbounce();
}

/** The idea in this algorithm is that the caller asteroid C and the argument asteroid
A will do some exchange of their velocities. We'll think of them as pointlike,
so that all that matters it the components of their velocities which lie along the
line connecting their two positions. We calculate these two vectors as
C's give and A's receive vector.
    The way this works is that (a) the momentum is conserved and (b) the energy is
conserved. We get (a) Ma*newVa + Mb*newVb == Ma*Va + Mb*Vb, and
(b) (1/2)*((Ma*newVa^2 + Mb*newVb^2)) == (1/2)*(Ma*Va^2 + Mb*Vb^2).
This is the intersection of a line and an ellipse, which is two points.
I ran this through Mathematica and came up with the trivial solution
newVa = Va and newVb = Vb and the one we'll use:
newVa = (Ma*Va - Mb*Va + 2*Mb*Vb) / (Ma + Mb)
new Vb =( 2*Ma*Va - Ma*Vb + Mb*Vb)/ (Ma + Mb).
If I divide both numerators and denominators by Ma, and call Mb/Ma "massratio" I get
newVa = (Va - massratio*Va + 2*massratio*Vb) / (1 + massratio)
newVb =( 2*Va - Vb + massratio*Vb)/ (1 + massratio).
Simplifying a little more, we get
newVa = ((1-massratio)*Va + 2*massratio*Vb) / (1 + massratio)
newVb =( 2*Va + (massratio - 1)*Vb)/ (1 + massratio).
Note that if massratio is 1, then this is simply newVa = Vb and newVb = Va.
If a has a huge (infinite) mass compared to b, then massratio is about 0 and
we get newVa = Va, newVb = 2Va - Vb, so if a is motionless, this is a simple bounce.
    We want contact point to be on the line between the two centers.
Rather than making it the midpoint, we weight it so that it divides
this line in the same ratio as radius and pother->radius(). That
is, we need to pick a sublength "contactdistance" of the "distance"
length between the two centers so that
radius/radius+otheradius = contactdistance/distance.
Multiply both sides of this equation to solve for contactdistance. */
public void bounceOff(SpriteAsteroid asteroid)
{
//Exchange momenta.
    Vector2 toOther = Vector2.difference(asteroid.position, position);
    double distance = toOther.normalize(); //Make unit vector towards other asteroid.
    double myComponent = Vector2.dotproduct(velocity, toOther);
    //Give up all your velocity that lies along the line connecting centers.
    Vector2 give = Vector2.product(myComponent, toOther);
    double asteroidComponent = Vector2.dotproduct(asteroid.velocity, toOther);
    Vector2 receive = Vector2.product(asteroidComponent, toOther);
    if (radius() == 0.0)
        return;
    //Think of mass as proportional to the cube of the radius.
    double massratio = asteroid.mass() / mass();
    double massdivisor = (1.0/(1.0 + massratio));
    velocity.subtract(give); //Give up your momentum
    //Take a mass-weighted version of the other's momentum.
    velocity.add(Vector2.product(massdivisor*2.0*massratio, receive));
    velocity.add(Vector2.product(massdivisor*(1.0 - massratio), give));
    asteroid.velocity.subtract(receive); //Other gives up his momemtum
    asteroid.velocity.add(Vector2.product(massdivisor*2.0, give));
    asteroid.velocity.add(Vector2.product(massdivisor*(massratio - 1.0), receive));
    //Takes yrs
    //Move away from each other to prevent double collision.
    Vector2 contactpoint = Vector2.sum(position,
        Vector2.product(((radius()*distance)/(radius()+asteroid.radius())),toOther));
    position.copy(Vector2.difference(contactpoint,
        Vector2.product(radius(),toOther)));
    asteroid.position.copy(Vector2.sum(contactpoint,
        Vector2.product(asteroid.radius(), toOther)));
}

/** Check if you're colliding with any other asteroids, and if you are,
update your velocity and position accordingly. Do the symmetric update to
the other guy's veloicty and postion at the same time. Always just check
against the guys with index greater than you, that way we only handle each
pair once. This all takes time, and we can toggle it off with the AsteroidsGame
collideflag. */
public void collideAsteroids()
{
    if (!ownerApp.game.collideflag)
        return;
    SpriteAsteroid[] asteroids = ownerApp.game.asteroids;
    boolean pastmyindex = false;
    int j;
    for (j = 0; j < asteroids.length; j++)
    {
if (asteroids[j] == this)
            pastmyindex = true;
        if (pastmyindex && asteroids[j].active && asteroids[j] != this &&
            isColliding(asteroids[j]))
        {
            bounceOff(asteroids[j]);
            break;
        }
    }
}


/** If hit by bullet, kill asteroid and advance score. If asteroid is large,
make some smaller ones to    replace it. We'll avoid the bullets here by
setting acceleration. */
public void update()
{
    if (!active)
        return;
    acceleration.set(0.0, 0.0);
    collideAsteroids();
    //Set up some variables to avoid bullets.
    SpriteBullet[] bullets = ownerApp.game.bullets;
    double closestbulletdistance = 1000000000.0; /* Just a big number so that the
        first bullet you look at will be closer than this. */
    SpriteBullet closestbullet = null;
    Vector2 frombullet;

    for (int j = 0; j < bullets.length; j++)
    {
        if (bullets[j].active && active)
        {
    if (isTouching(bullets[j]))
            {
                asteroidsLeft--;
                active = false;
                bullets[j].active = false;
                if (ownerApp.soundflag && ownerApp.soundsloaded)
                    ownerApp.explosionSound.play();
                explode();
                if (!asteroidIsSmall)
                {
                    ownerApp.game.score += BIG_POINTS;
                    split(FRAG_COUNT);
                }
                else
                    ownerApp.game.score += SMALL_POINTS;
}
else //See if this is the closest bullet and if it's heading my way
{
                frombullet = Vector2.difference(position, bullets[j].position);
                double bulletdistance = frombullet.magnitude();
                /* Am I getting closer to this bullet heading towards me AND
                    is it closer than any other bullets heading towards me?
                    I imagine myself motionless by subtracting my velocity from
                    everything, and then look at how the bullet moves. */
Vector2 relativevelocity = Vector2.difference(
                    bullets[j].velocity, velocity);
if ( Vector2.dotproduct(relativevelocity, frombullet) > 0.0 &&
                    bulletdistance < closestbulletdistance)
                {
                    closestbullet = bullets[j];
                    closestbulletdistance = bulletdistance;
                }
        }
    }// end of j loop on bullets
    if (closestbullet != null &&
        closestbulletdistance < Units.HEIGHT_WORLD / 2)
    {
darting = true;
        frombullet = Vector2.difference(position, closestbullet.position);
        frombullet.setMagnitude(Units.ACCELERATION_ASTEROID_DART);
        acceleration.add(frombullet);
        angle = acceleration.angle(); //So you notice that they're fleeing.
/* The next few lines are optional. The idea is to to make asteroid not be
like a rabbit running down a train-track away from an oncoming train. This
doesn't seem to have that big an effect, which is why it's optional. */
Vector2 relativevelocity =
            Vector2.difference(bullets[j].velocity, velocity);
        double turnangle;
        if (Math.abs(Vector2.angle(frombullet, relativevelocity))
            < Math.PI/16.0 ||
            Math.abs(Vector2.angle(Vector2.product(-1.0,frombullet),velocity))
            < Math.PI/16.0)
        {
            if (ownerApp.randomizer.nextBoolean())
                turnangle = Math.PI/2.0;
            else
                turnangle = -Math.PI/2.0;
            velocity.turn(turnangle);
        }
//End of the rabbit-avoiding-train code.
    }
else //Head for ship
{
darting = false;
    Vector2 toship = Vector2.difference(ownerApp.game.ship.position, position);
    toship.setMagnitude(Units. ACCELERATION_ASTEROID_TOSHIP);
    acceleration.add(toship);
}

// If the ship is not in hyperspace, see if it is hit.
    if (ownerApp.game.ship.active &&
    ownerApp.game.ship.age > ownerApp.game.ship.ageDoneHyperjump &&
        active &&
        isTouching(ownerApp.game.ship))
    {
    if (ownerApp.soundflag && ownerApp.soundsloaded)
        ownerApp.crashSound.play();
    ownerApp.game.ship.explode();
    ownerApp.game.ship.stop();
    ownerApp.game.ufo.stop();
    ownerApp.game.missile.stop();
    } // end of if (ship.active...)
}
}//end of if (active...), also end of i loop.}
}

class SpriteUfo extends Sprite
{
/** Number of times a Ufo gets to cross the screen. 5.*/
static final int UFO_PASSES = 5;
/** Points for shooting a Ufo. 250.*/
static final int UFO_POINTS = 250;
/** The ufo gets a fixed number of passes across the screen. */
int ufoPassesLeft;
/** The next score the player has to pass to make a new Ufo come to life. */
static int newUfoScore;
/** Next time you can shoot a missile. */
double ageDoneShootwait;

public SpriteUfo(Asteroids owner)
{
    super(owner);
    maxspeed = Units.START_MAX_SPEED_UFO;
    fillcolor = Color.gray;
    shape.addPoint(-15, 0);
    shape.addPoint(-10, 5);
    shape.addPoint(-5, 5);
    shape.addPoint(-5, 9);
    shape.addPoint(5, 9);
    shape.addPoint(5, 5);
    shape.addPoint(10, 5);
    shape.addPoint(15, 0);
    shape.addPoint(10, -5);
    shape.addPoint(-10, -5);
    fixRadius();
    ageDoneShootwait = 0;
    reset();
}
/**Randomly set flying saucer at an edge of the screen. */
public void reset()
{
    double temp;
    active = true;
    position.x = -Units.WIDTH_WORLD / 2;
    position.y = -Units.HEIGHT_WORLD/2 +
        ownerApp.randomizer.nextDouble() * Units.HEIGHT_WORLD;
    velocity.x = ownerApp.randomizer.nextDouble(Units.MIN_SPEED_UFO, maxspeed);
    if (ownerApp.randomizer.nextBoolean())
    {    velocity.x = -velocity.x;
        position.x = Units.WIDTH_WORLD / 2;
    }
    velocity.y = ownerApp.randomizer.nextDouble(Units.MIN_SPEED_UFO, maxspeed);
    if (ownerApp.randomizer.nextBoolean())
        velocity.y = -velocity.y;
    render();
    loopsound(true);
    // Set ageDone timer for this pass.
    ageDone = age + Units.LIFETIME_UFO;
}

/** Toggles the saucerSound. */
public boolean loopsound(boolean onoff)
{
    if (!super.loopsound(onoff))
    return false;
    if (!soundplaying)
        ownerApp.saucerSound.stop();
    else
        ownerApp.saucerSound.loop();
    return true;
}

public void update()
{
    int i, distancetoship;
    SpriteBullet[] bullets = ownerApp.game.bullets;
/* Move the flying saucer and check for collision with a bullet. Stop it when
    its past ageDone. */
    if (active)
    {
        if (age > ageDone)
    {
            if (--ufoPassesLeft > 0)
                reset();
            else
                stop();
    }
        else //normalupdate
        {
    for (i = 0; i < bullets.length; i++)
    if (bullets[i].active &&
            isTouching(bullets[i]))
{
    if (ownerApp.soundflag && ownerApp.soundsloaded)
                ownerApp.crashSound.play();
            explode();
            stop();
            ownerApp.game.score += UFO_POINTS;
}
fire();
        } //end normal update
    } //end if active
}

public void fire()
{
    if(age <= ageDoneShootwait)
    return;
    double distancetoship =
        (Vector2.difference(position, ownerApp.game.ship.position)).magnitude();
    if (ownerApp.game.ship.active && active && !ownerApp.game.missile.active &&
        distancetoship > 10.0 * ownerApp.game.ship.radius() && //Not point-blank.
        ownerApp.game.ship.age > ownerApp.game.ship.ageDoneHyperjump)
    {
        ownerApp.game.missile.reset();
        if (ownerApp.soundflag && ownerApp.soundsloaded)
            ownerApp.game.missile.loopsound(true);
        ownerApp.game.missile.position.copy(position);
        ageDoneShootwait = age + Units.FIRE_WAIT_UFO;
    }
}

/** Turn off the UFO, set ufoPassesLeft to 0. */
public void stop()
{
    super.stop();
    ufoPassesLeft = 0;
}
}

/** A missile is the kind of "bullet" that the Ufo shoots. It's guided, that is,
it heads towards the ship.*/
class SpriteMissile extends Sprite
{
/** Points for shooting a Missile. 500.*/
static final int MISSILE_POINTS = 500;
public SpriteMissile(Asteroids owner)
{
    super(owner);
    shape.addPoint(5, 0);
    shape.addPoint(3, 2);
    shape.addPoint(-3, 2 );
    shape.addPoint(-5, 3 );
    shape.addPoint(-5, -3 );
    shape.addPoint(-3, -2 );
    shape.addPoint(3, -2 );
    fixRadius();
    fillcolor = Color.yellow;
}

/** Toggles the missileSound. */
public boolean loopsound(boolean onoff)
{
    if (!super.loopsound(onoff))
    return false;
    if (!soundplaying)
        ownerApp.missileSound.stop();
    else
        ownerApp.missileSound.loop();
    return true;
}

public void reset()
{
    active = true;
    angle = 0.0;
    angularAcceleration = 0.0;
    velocity.setzero();
    render();
    ageDone = age + Units.LIFETIME_MISSILE;
    loopsound(true);
}

/** At present this method is only used by the missile, but we might
consider letting the asteroids use it too! The boolean flag tells whether
to just menacingly aim yoursef (false) or to actually move this way (true). */
public void headTowardsSprite(Sprite target, boolean moving)
{
    Vector2 toTarget;
// Find the angle needed to hit the ship.
    toTarget = Vector2.difference(target.position, position);
    angle = toTarget.angle();
    if (moving)
    {
        velocity.set(angle); //Unit vector in direction angle.
        velocity.multiply(Units.SPEED_MISSILE);
    }
}

/** Move the guided missile and check for collision with ship or bullet.
Stop it when its too old. Use isTouching so its hard to hit missile. */
public void update()
{
    int i;
    SpriteBullet[] bullets = ownerApp.game.bullets;
    boolean moving = false;

    if (active)
    {
        if (age > ageDone)
            stop();
        else
        {
            if (ownerApp.game.ship.active && ownerApp.game.ship.age > ownerApp.game.ship.ageDoneHyperjump)
                moving = true;
            headTowardsSprite(ownerApp.game.ship, moving); //just aim, or aim & move.
            for (i = 0; i < bullets.length; i++)
                if (bullets[i].active && isTouching(bullets[i]))
                {     if (ownerApp.soundflag && ownerApp.soundsloaded)
                        ownerApp.crashSound.play();
                    explode();
                    stop();
                    ownerApp.game.score += MISSILE_POINTS;
                }
            if (active && ownerApp.game.ship.active &&
        ownerApp.game.ship.age > ownerApp.game.ship.ageDoneHyperjump &&
                isTouching(ownerApp.game.ship))
            {
                if (ownerApp.soundflag && ownerApp.soundsloaded)
                    ownerApp.crashSound.play();
                ownerApp.game.ship.explode();
                ownerApp.game.ship.stop();
                ownerApp.game.ufo.stop();
                stop();
            }
        } //end else
    } //end active
}

}

class SpriteExplosion extends Sprite
{

public SpriteExplosion(Asteroids owner)
{
    super(owner);
}

public void reset()
{
    shape = new Polygon();
    active = false;
    ageDone = age;
}

public void update()
{
    if (age > ageDone)
        active = false;
}

/* We don't call the fixRadius for the SpriteExplosion, and this means
that the position point lies a bit off the line that is the
explosion object. This makes for nicer tumbling, and it gives
us the chance to enhance the explosion appearnce by adding a
little cross at the positon point. */
public void draw(Graphics g, boolean detail)
{
int brightness;
if (age >= ageDone)
brightness = 0;
else
    brightness = 64 + (int)((255-64)*
((ageDone - age)/Units.LIFETIME_EXPLOSION));
    if (ownerApp.backgroundcolor != Color.black)
        brightness = 255 - brightness;
    edgecolor = new Color(brightness, brightness, brightness);
    super.draw(g, detail);
    if (active)
    {
        int centerx = (int)positiontransform.x;
        int centery = (int)positiontransform.y;
        g.drawLine(centerx - 1, centery, centerx + 1, centery);
        g.drawLine(centerx, centery-1, centerx, centery+1);
    }
}
}

class SpriteBullet extends Sprite
{
public SpriteBullet(Asteroids owner)
{
    super(owner);
    fillflag = false;
    shape.addPoint(2, 2);
    shape.addPoint(2, -2);
    shape.addPoint(-2, 2);
    shape.addPoint(-2, -2);
    fixRadius();
    maxspeed = Units.MAX_SPEED_BULLET;
    reset();
}
public void reset()
{
    active = false;
    ageDone = age;
}

void initialize(Sprite shooter)
{
    active = true;
    position.copy(shooter.position);
    Vector2 gunoffset = new Vector2(shooter.angle);
    gunoffset.multiply(shooter.radius());
    position.add(gunoffset);
    velocity.set(shooter.angle); //Unit vector in this direction.
    velocity.multiply(Units.SPEED_BULLET);
    ageDone = age + Units.LIFETIME_BULLET;
    render();
        //Make sure you get the shapetransform fixed right away.
}

/** We'll deactivate a bullet if it wraps or bounces.*/
public boolean advance(double dt)
{
    boolean outcode = super.advance(dt);
    if (outcode)//&& !bounce) //If you like you can allow bounce
        active = false;
    return outcode;
}

/** We'll deactivate a bullet if its lifetime runs out. */
public void update()
{
    if (age > ageDone)
        active = false;
}

}//End SpriteBullet

/** Use a SpriteWall to visually show when the bounce flag is on, we'll
trigger it using the ship.bounce inside AsteroidsGame draw. We make
it a Sprite so we can use the render function to keep resizing it to
the current window size. */
class SpriteWall extends Sprite
{
    public SpriteWall(Asteroids owner)
    {
        super(owner);
fillflag = false;
        active = true;
        edgecolor = Color.white;
        fillcolor = Color.black;
/* Two of the rectangle edges won't show unless we make the box a bit
smaller. Also we need to keep in mind that our coordinates draw thigns
upside down. */
        int halfwidth = (int)(owner.realpixelconverter.widthreal()/2);
        int halfheight = (int)(owner.realpixelconverter.heightreal()/2);
shape.addPoint(-halfwidth, halfheight-1);
shape.addPoint(-halfwidth, -halfheight);
shape.addPoint(halfwidth-1, -halfheight);
shape.addPoint(halfwidth-1, halfheight-1);
    }
    /** Do-nothing advance method means don't move. */
    public boolean advance(double dt){age += dt; return false;}
    /** Do-nothing update method means don't look at other sprites. */
    public void update(){}
    /** Do-nothing reset method means there's nothing to reset.*/
    public void reset(){}
    /** Draw method draws it as a disk if ownerApp.game.diskflag. If bouncing
    we use the edgecolor (which can be either white or black). Otherwise
    we use gray to show a soft, wrapping edge. */
    public void draw(Graphics g, boolean detail)
    {
        Color oldedgecolor = new Color(edgecolor.getRGB());
        if (!ownerApp.game.ship.bounce)
            edgecolor = Color.gray;
        else
            edgecolor = Color.yellow;
        if (!ownerApp.game.diskflag)
            super.draw(g, detail);
        else //disk case
        {
            Rectangle box = new Rectangle();
box.x = shapetransform.xpoints[0];
box.y = shapetransform.ypoints[0];
box.width = shapetransform.xpoints[2] - shapetransform.xpoints[0];
box.height = shapetransform.ypoints[2] - shapetransform.ypoints[0];
/* In Java 1.1 I'd just do box = shapetransform.getBounds(); */
            g.setColor(ownerApp.backgroundcolor);
g.fillOval(box.x, box.y, box.width, box.height);
            g.setColor(edgecolor);
            g.drawOval(box.x, box.y, box.width, box.height);
        }
        edgecolor = oldedgecolor;
    }
}
//END=================SPRITE CODE===================

//START=================ASTEROIDSGAME CODE===================

/** This container class holds a spritevector with all the sprites, and keeps
the SpriteBullet and SpriteExplosion in separate arrays so we can conveniently
replace the oldest one when we want to make a new one. */
class AsteroidsGame
{
// Constants
/**Starting number of ships per game. 3. */
static final int MAX_SHIPS = 3;
/** Number of active bullets allowed. 8. */
static final int MAX_BULLETS = 8; //8;
/** Number of asteroids in each wave. 8. */
static final int START_ASTEROIDS = 8;
/** Number of asteroids that you can get by splitting up.*/
static final int MAX_ASTEROIDS = (int)(1.5 * START_ASTEROIDS);
/** Total number of lines used to represent exploding objects. 30. */
static final int MAX_EXPLOSIONS = 30;
/** Number of additional points needed to earn a new ship. */
static final int NEW_SHIP_POINTS = 5000;
/** Number of additional points before a fresh Ufo attacks. */
static final int NEW_UFO_POINTS = 1500; //2750;
/** Number of stars per square world unit. We'll have about 250,000 area
    at present, so keep it low. */
static double STARDENSITY = 0.0004;
/** Owner Asteroids object, where the randomizer lives. */
Asteroids ownerApp;
/** Array for background stars */
Vector2Colored[] stars;
/** Game score. */
static int score;
/** High score for this play session. */
static int highScore;
/** Flag to indicate if game is started and not yet over. */
boolean gameonflag;
/** Flag for paused game. */
boolean paused;
/** Flag for whether or not to calculate collisions between asteroids. */
boolean collideflag = true;
/** Flag for whether to use a rectangle or a disk for your world.*/
boolean diskflag = true;


/** Have a SpriteWall to show when the bounce is on for the ship. We don't
add this to the spritevector as it won't be colliding like the others,
etc. We will use it in the AsteroidsGame draw method. */
SpriteWall wall;
/** The spritevector holds all of the sprites; we walk this array for our
updates. */
Vector spritevector;
/**It's useful to have a distinguished name for the ship. */
SpriteShip ship;
/**Use a phtons array so that we can always replace the oldest bullet by the
newest bullet. */
SpriteBullet[] bullets;
/* Next available slot to put a new bullet into. */
int bulletIndex;
/** The flying saucer. */
SpriteUfo ufo;
/** The missile. */
SpriteMissile missile;
/** The asteroids. */
SpriteAsteroid[] asteroids;
/**So that we can always replace the oldest explosion line by the newest one,
we need an array for the explosions. */
SpriteExplosion[] explosions;
/* Next available slot to put a new explosion into. */
int explosionIndex = 0;

/** Construct all your sprites and add them to spritevector. The first things
get drawn first and thus appear furthest in the background. So add the wall
first and the ship last. */
public AsteroidsGame(Asteroids owner)
{

    int i;
    ownerApp = owner; /* Set this right away, as makenewstars uses it. */
    highScore = 0;
    collideflag = true;
// Generate starry background.
    makenewstars();
// Get the vector ready.
    spritevector = new Vector();
// Make the wall
    wall = new SpriteWall(owner);
//spritevector.addElement(wall);
/* I want to update wall, stars, then sprites, so I don't put wall in
spritevector, since the stars aren't. Probably the stars should be and
then the wall could be too. */
// Create asteroid sprites.
    asteroids = new SpriteAsteroid[MAX_ASTEROIDS];
    for (i = 0; i < MAX_ASTEROIDS; i++)
    {
        asteroids[i] = new SpriteAsteroid(owner);
        spritevector.addElement(asteroids[i]);
    }
// Create explosion sprites.
    explosions = new SpriteExplosion[MAX_EXPLOSIONS];
    explosionIndex = 0;
    for (i=0; i<MAX_EXPLOSIONS; i++)
    {
        explosions[i] = new SpriteExplosion(owner);
        spritevector.addElement(explosions[i]);
    }
// Create shape for the flying saucer.
    ufo = new SpriteUfo(owner);
    spritevector.addElement(ufo);
// Create shape for the guided missile.
    missile = new SpriteMissile(owner);
    spritevector.addElement(missile);
// Create shape for the ship shapetransform.
    ship = new SpriteShip(owner);
    spritevector.addElement(ship);
/* Create shape for the bullet sprites. There's an odd bug that I had
for awhile. If I add the bullets to the spritevector before the ship,
then they have lower indices and they get updated and rendered after
the ship. This will make a problem as the ship's update will change
the bullet's positions with the fire function. If I don't hvae the
bullets after the ship in my master spritevector then the bullets
will be drawn at the wrong position for one update. */
    bullets = new SpriteBullet[MAX_BULLETS];
    bulletIndex = 0;
    for (i=0; i<MAX_BULLETS; i++)
    {
        bullets[i] = new SpriteBullet(owner);<