/* StarBase.java */

/* 
 * Copyright (C) 1995 Mark Boyns <boyns@sdsu.edu>
 *
 * StarBase
 * <URL:http://www.sdsu.edu/~boyns/java/starbase/>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

import java.applet.*;
import java.awt.*;
import java.util.Enumeration;
import java.util.Vector;


/* Abstract class used for moving objects. */
abstract class Thing extends Thread
{
    StarBase parent;
    int x;
    int y;
    
    abstract void paint (Graphics g);
    abstract void erase (Graphics g);
    abstract void explode ();
}


/* A star located at x,y with depth z. */
class Star
{
    int x;
    int y;
    int z;

    Star (int x, int y, int z)
    {
	this.x = x;
	this.y = y;
	this.z = z;
    }
}


/* The moving starfield. */
class Starfield extends Thing
{
    private final int SPEED = 3;
    private final int DELAY = 100;
    
    boolean paintme = false;
    int max_x;
    int max_y;
    int max_z;
    int max_stars;
    int dir;
    Star stars[];
    Color colors[];
    int delay;
    
    Starfield (StarBase parent, int max_stars, int dir, int max_x, int max_y, int max_z)
    {
	this.parent = parent;
	this.max_stars = max_stars;
	this.max_x = max_x;
	this.max_y = max_y;
	this.max_z = max_z;
	start ();
    }

    public void run ()
    {
	int i;
	int c;

	Thread.currentThread().setPriority (Thread.MIN_PRIORITY);

	delay = DELAY;

	/* Create shades of white/grey used for stars. */
	colors = new Color[max_z];
	for (i = 0, c = 255; i < max_z; i++)
	{
	    colors[i] = new Color (c, c, c);
	    c -= 10;
	    if (c < 50)
	    {
		c = 50;
	    }
	}
			   
	/* Create the first set of stars. */
	stars = new Star[max_stars];
	for (i = 0; i < max_stars; i++)
	{
	    stars[i] = new Star ((int)(Math.random () * max_x),
				 (int)(Math.random () * max_y),
				 (int)(Math.random () * max_z));
	}
	
	for (;;)
	{
	    paintme = true;
	    parent.repaint ();
	    
	    try 
	    {
		Thread.sleep (delay);
	    }
	    catch (InterruptedException e)
            {
            }
	}
    }

    public void fast ()
    {
	delay = 1;
    }

    public void paint (Graphics g)
    {
	int i;
	int f;
	
	if (!paintme)
	{
	    return;
	}

	/* Move the stars. */
	for (i = 0; i < max_stars; i++)
	{
	    g.setColor (Color.black);
	    g.drawLine (stars[i].x, stars[i].y, stars[i].x+1, stars[i].y+1);
	    g.drawLine (stars[i].x+1, stars[i].y, stars[i].x, stars[i].y+1);

	    f = 1 + (int)(SPEED*(((float)max_z - (float)stars[i].z)/(float)max_z));
	    dir = parent.curr_dir;
	    switch (dir)
	    {
	    case 0:
		stars[i].y += f;
		break;

	    case 1:
		stars[i].x += f;
		break;

	    case 2:
		stars[i].y -= f;
		break;

	    case 3:
		stars[i].x -= f;
		break;
	    }

	    /* Create a new star. */
	    if (stars[i].x < 0 || stars[i].x > max_x || stars[i].y < 0 || stars[i].y > max_y)
	    {
		int nx = 0;
		int ny = 0;

		switch (dir)
		{
		case 0:
		    nx = (int)(Math.random () * max_x);
		    ny = 0;
		    break;

		case 1:
		    nx = 0;
		    ny = (int)(Math.random () * max_y);
		    break;

		case 2:
		    nx = (int)(Math.random () * max_x);
		    ny = max_y;
		    break;

		case 3:
		    nx = max_x;
		    ny = (int)(Math.random () * max_y);
		    break;		    
		}
		
		stars[i] = new Star (nx, ny, (int)(Math.random () * max_z));
	    }
	
	    g.setColor (colors[stars[i].z]);
	    g.drawLine (stars[i].x, stars[i].y, stars[i].x+1, stars[i].y+1);
	    g.drawLine (stars[i].x+1, stars[i].y, stars[i].x, stars[i].y+1);
	}
	
	paintme = false;
    }

    public void erase (Graphics g)
    {
    }

    public void explode ()
    {
	stop ();
    }
}


/* Display an explosion image for 1/2 a second. */
class Explosion extends Thing
{
    Image image;
    
    Explosion (StarBase parent, Image image, int x, int y)
    {
	this.parent = parent;
	this.x = x;
	this.y = y;
	this.image = image;
	start ();
    }

    public void run ()
    {
	Thread.currentThread().setPriority (Thread.MIN_PRIORITY);
	parent.repaint ();
	    
	try 
	{
	    Thread.sleep (500);
	}
	catch (InterruptedException e)
	{
	}   
    }

    public void paint (Graphics g)
    {
	g.drawImage (image, x - 16, y - 16, parent);
    }

    public void erase (Graphics g)
    {
	g.setColor (Color.black);
	g.fillRect (x - 16, y - 16, 32, 32);
    }

    public void explode ()
    {
	stop ();
    }
}


/* A moving spaceship which moves from x,y to max_x,max_y. */
class UFO extends Thing
{
    boolean paintme = false;
    int dir;
    int max_x;
    int max_y;
    int speed;
    public Image image;
    boolean hit = false;
    
    UFO (StarBase parent, Image image, int x, int y, int dir, int max_x, int max_y, int speed)
    {
	this.parent = parent;
	this.image = image;
	this.x = x;
	this.y = y;
	this.dir = dir;
	this.max_x = max_x;
	this.max_y = max_y;
	this.speed = speed;
	start ();
    }

    public void run ()
    {
	Thread.currentThread().setPriority (Thread.MIN_PRIORITY);

	do
	{
	    paintme = true;
	    parent.repaint ();
	    
	    try 
	    {
		Thread.sleep (100);
	    }
	    catch (InterruptedException e)
            {
            }   
	}
	while (!hit);
    }

    public boolean collision (int cx, int cy)
    {
	return (cy >= y - 16) && (cy <= y + 16) && (cx >= x - 16) && (cx <= x + 16);
    }
    
    public void paint (Graphics g)
    {
	if (!paintme)
	{
	    return;
	}

	g.setColor (Color.black);
	g.fillRect (x - 16, y - 16, 32, 32);
	
	switch (dir)
	{
	case 0:
	    y += speed;
	    hit = y >= max_y;
	    break;

	case 1:
	    x += speed;
	    hit = x >= max_x;
	    break;

	case 2:
	    y -= speed;
	    hit = y <= max_y;
	    break;

	case 3:
	    x -= speed;
	    hit = x <= max_x;
	    break;
	}
	
	g.drawImage (image, x - 16, y - 16, parent);
	paintme = false;
    }
    
    public void erase (Graphics g)
    {
	g.setColor (Color.black);
	g.fillRect (x - 16, y - 16, 32, 32);
    }

    public void explode ()
    {
	stop ();
    }
}


/* Laser pulse which moves from x,y to max_x,max_y. */
class Laser extends Thing
{
    private final int SPEED = 20;
    
    Color color;
    boolean paintme = false;
    int dir;
    int max_x;
    int max_y;
    int length;
    int start_x;
    int start_y;
    
    Laser (StarBase parent, int x, int y, int length, int dir, int max_x, int max_y)
    {
	this.parent = parent;
	this.color = Color.red;
	start_x = x;
	start_y = y;
	this.length = length;
	this.dir = dir;
	this.max_x = max_x;
	this.max_y = max_y;
	start ();
    }

    public void run ()
    {
	int i;

	Thread.currentThread().setPriority (Thread.MIN_PRIORITY);

	x = start_x;
	y = start_y;
	
	do
	{
	    paintme = true;
	    parent.repaint ();
	    
	    try 
	    {
		Thread.sleep (100);
	    }
	    catch (InterruptedException e)
            {
            }   
	}
	while (x >= 0 && x <= max_x && y >= 0 && y <= max_y);
    }

    public void paint (Graphics g)
    {
	if (!paintme)
	{
	    return;
	}

	switch (dir)
	{
	case 0:
	    if (y <= start_y - length)
	    {
		g.setColor (Color.black);
		g.fillRect (x - 1, y, 3, length);
		y -= SPEED;
		g.setColor (color);
		g.fillRect (x - 1, y, 3, length);
	    }
	    else
	    {
		y -= SPEED;
	    }
	    break;

	case 1:
	    if (x <= start_x - length)
	    {
		g.setColor (Color.black);
		g.fillRect (x, y - 1, length, 3);
		x -= SPEED;
		g.setColor (color);
		g.fillRect (x, y - 1, length, 3);
	    }
	    else
	    {
		x -= SPEED;
	    }
	    break;

	case 2:
	    if (y >= start_y + length)
	    {
		g.setColor (Color.black);
		g.fillRect (x - 1, y - length, 3, length);
		y += SPEED;
		g.setColor (color);
		g.fillRect (x - 1, y - length, 3, length);
	    }
	    else
	    {
		y += SPEED;
	    }
	    break;
		
	case 3:
	    if (x >= start_x + length)
	    {
		g.setColor (Color.black);
		g.fillRect (x - length, y - 1, length, 3);
		x += SPEED;
		g.setColor (color);
		g.fillRect (x - length, y - 1, length, 3);
	    }
	    else
	    {
		x += SPEED;
	    }
	    break;
	}

	paintme = false;
    }

    public void erase (Graphics g)
    {
	switch (dir)
	{
	case 0:
	    g.setColor (Color.black);
	    g.fillRect (x - 1, y, 3, length);
	    break;

	case 1:
	    g.setColor (Color.black);
	    g.fillRect (x, y - 1, length, 3);
	    break;

	case 2:
	    g.setColor (Color.black);
	    g.fillRect (x - 1, y - length, 3, length);
	    break;
		
	case 3:
	    g.setColor (Color.black);
	    g.fillRect (x - length, y - 1, length, 3);
	    break;
	}
    }

    public void explode ()
    {
	stop ();
    }
}


/* The applet. */
public class StarBase extends java.applet.Applet implements Runnable
{
    /* Screen size. */
    private final int SCREEN_WIDTH = 501;
    private final int SCREEN_HEIGHT = 501;

    /* Base and cannon size. */
    private final int BASE_WIDTH = 13;
    private final int CANNON_WIDTH = 5;
    private final int CANNON_HEIGHT = 8;

    /* Laser parameters. */
    private final int LASER_ENERGY = 4;
    private final int LASER_LENGTH = 20;
    private final int MAX_LASERS = 8;

    /* UFO parameters. */
    private final int UFO_POINTS = 100;
    private final int UFO_DAMAGE = 10;
    private final int MAX_UFOS = 8;

    /* Game speed. */
    private final int DELAY_MIN = 1000;
    private final int DELAY_MAX = 3000;
    private final int DELAY_DECAY = 100;
    private final int MIN_SPEED = 1;
    private final int MAX_SPEED = 8;

    /* Keys */
    private int key_clockwise = 'k';
    private int key_counter_clockwise = 'j';
    private int key_fire = ' ';

    /* Current and next cannon direction. */
    public int curr_dir = 0;
    private int new_dir = 0;

    /* Polygons which represent the 4 cannons. */
    private Polygon cannons[];

    /* Fonts */
    private Font font;
    private FontMetrics fontMetrics;

    /* Game parameters */
    private int score = 0;
    private int energy = 100;
    private int shield = 100;
    private int laserCount = 0;
    private int ufoCount = 0;
    private boolean dead = true;
    private boolean clearScreen = false;

    /* Vector of moving Things. */
    private Vector things = null;

    /* The main applet's thread. */
    private Thread thread = null;

    /* Images */
    private MediaTracker tracker;
    Image images[];
    String image_files[] =
    {
	"earth.gif",
	"jupiter.gif",
	"mars.gif",
	"mercury.gif",
	"neptune.gif",
	"pluto.gif",
	"saturn.gif",
	"uranus.gif",
	"venus.gif"
    };
    Image energyImage;
    Image deathImage;
    Image shieldImage;
    Image explosionImage;

    /* Strings */
    private final String scoreString = "Score: ";
    private final String energyString = "Energy: ";
    private final String shieldString = "Shield: ";
    private final String welcomeString = "<< Insert Coin >>";
    private final String titleString = "Welcome to StarBase";


    /* Initialize the applet. */
    public void init ()
    {
	font = new Font ("TimesRoman", Font.BOLD, 24);
	fontMetrics = getFontMetrics (font);
	setFont (font);

	setBackground (Color.black);
	
	/* Create the cannons. */
	createCannons ();

	/* Load all images with MediaTracker. */
	tracker = new MediaTracker (this);
	images = new Image[image_files.length];
	for (int i = 0; i < images.length; i++)
	{
	    images[i] = getImage (getDocumentBase (), "images/" + image_files[i]);
	    tracker.addImage (images[i], 0);
	}
	energyImage = getImage (getDocumentBase (), "images/netscape.gif");
	tracker.addImage (energyImage, 0);
	deathImage = getImage (getDocumentBase (), "images/skull.gif");
	tracker.addImage (deathImage, 0);
	shieldImage = getImage (getDocumentBase (), "images/mozilla.gif");
	tracker.addImage (shieldImage, 0);
	explosionImage = getImage (getDocumentBase (), "images/boom.gif");
	tracker.addImage (explosionImage, 0);

	resize (SCREEN_WIDTH, SCREEN_HEIGHT);
    }


    /* Start a new game. */
    public synchronized void doit ()
    {
	stopThings ();
	thread = new Thread (this);
	thread.start ();
    }


    /* The game engine. */
    public void run ()
    {
	int level = 0;
	int delay = DELAY_MAX;
	int min_speed = MIN_SPEED;
	int max_speed = 2;
	int speed = 0;
	int dir;
	int nufos;
	int i;
	
	dead = false;
	energy = 100;
	shield = 100;
	score = 0;
	laserCount = 0;
	ufoCount = 0;

	clearScreen = true;
	repaint ();

	/* Wait for the images to load. */
	try
	{
	    showStatus ("Loading images...");
	    tracker.waitForAll ();
	}
	catch (InterruptedException e)
	{
	    return;
	}
	showStatus ("");

	while (!dead)
	{
	    /* wait */
	    try 
	    {
		Thread.sleep (delay);
	    }
	    catch (InterruptedException e)
            {
            }

	    nufos = 1 + (int)(Math.random () * 4);
	    for (i = 0; i < nufos; i++)
	    {
		/* Pick a random direction. */
		dir = (int)(Math.random () * 4);

		/* Pick a random speed. */
		speed = min_speed + (int)(Math.random () * max_speed);
		if (speed > MAX_SPEED)
		{
		    speed = MAX_SPEED;
		}

		/* Pick a UFO. */
		if (Math.random () <= 0.10)
		{
		    createUFO (deathImage, dir, speed);
		}
		else if (Math.random () <= 0.30)
		{
		    createUFO (Math.random () > 0.5 ? energyImage : shieldImage, dir, speed);
		}
		else
		{
		    createUFO (images[(int)(Math.random () * images.length)], dir, speed);
		}
	    }
	    
	    level++;
	    if ((level % 5) == 0)
	    {
		delay -= DELAY_DECAY;
		if (delay < DELAY_MIN)
		{
		    delay = DELAY_MIN;
		}
	    }
	    if ((level % 10) == 0)
	    {
		min_speed += 1;
		max_speed += 2;
	    }
	}

	clearScreen = true;
	repaint ();
    }

    
    /* Create a starfield with 20 stars and a depth of 20. */
    public void createStarfield ()
    {
	things.addElement (new Starfield (this, 50, curr_dir, SCREEN_WIDTH, SCREEN_HEIGHT, 20));
    }

    
    /* Create the list of polygons which represent the cannons. */
    public void createCannons ()
    {
	int x = SCREEN_WIDTH / 2;
	int y = SCREEN_HEIGHT / 2;
	int extra = 1;

	cannons = new Polygon[4];

	cannons[0] = new Polygon ();
	cannons[0].addPoint (x - CANNON_WIDTH, y - BASE_WIDTH - extra);
	cannons[0].addPoint (x, y - BASE_WIDTH - CANNON_HEIGHT);
	cannons[0].addPoint (x + CANNON_WIDTH, y - BASE_WIDTH - extra);

	cannons[1] = new Polygon ();
	cannons[1].addPoint (x - BASE_WIDTH - extra, y - CANNON_WIDTH);
	cannons[1].addPoint (x - BASE_WIDTH - CANNON_HEIGHT, y);
	cannons[1].addPoint (x - BASE_WIDTH - extra, y + CANNON_WIDTH);	    

	cannons[2] = new Polygon ();
	cannons[2].addPoint (x - CANNON_WIDTH, y + BASE_WIDTH + extra);
	cannons[2].addPoint (x, y + BASE_WIDTH + CANNON_HEIGHT);
	cannons[2].addPoint (x + CANNON_WIDTH, y + BASE_WIDTH + extra);

	cannons[3] = new Polygon ();
	cannons[3].addPoint (x + BASE_WIDTH + extra, y - CANNON_WIDTH);
	cannons[3].addPoint (x + BASE_WIDTH + CANNON_HEIGHT, y);
	cannons[3].addPoint (x + BASE_WIDTH + extra, y + CANNON_WIDTH);

    }


    /* Create a UFO. */
    public void createUFO (Image image, int dir, int speed)
    {
	int x = 0;
	int y = 0;
	int max_x = 0;
	int max_y = 0;
	int w = SCREEN_WIDTH/2;
	int h = SCREEN_HEIGHT/2;

	if (ufoCount == MAX_UFOS)
	{
	    return;
	}

	switch (dir)
	{
	case 0:
	    x = cannons[dir].xpoints[1];
	    y = cannons[dir].ypoints[1] - h;
	    break;

	case 1:
	    x = cannons[dir].xpoints[1] - w;
	    y = cannons[dir].ypoints[1];
	    break;

	case 2:
	    x = cannons[dir].xpoints[1];
	    y = cannons[dir].ypoints[1] + h;
	    break;

	case 3:
	    x = cannons[dir].xpoints[1] + w;
	    y = cannons[dir].ypoints[1];
	    break;
	}

	max_x = cannons[dir].xpoints[1];
	max_y = cannons[dir].ypoints[1];
	
	things.addElement (new UFO (this, image, x, y, dir, max_x, max_y, speed));

	ufoCount++;
    }


    /* BOOM! */
    public void createExplosion (int x, int y)
    {
	things.addElement (new Explosion (this, explosionImage, x, y));
    }

    
    /* Create a laser moving in DIR direction. */
    public void createLaser (int dir)
    {
	if (laserCount == MAX_LASERS || energy < LASER_ENERGY)
	{
	    return;
	}
	
	things.addElement (new Laser (this,
				      cannons[dir].xpoints[1],
				      cannons[dir].ypoints[1],
				      LASER_LENGTH,
				      dir,
				      SCREEN_WIDTH, SCREEN_HEIGHT));
	laserCount++;

	energy -= LASER_ENERGY;
	if (energy < 0)
	{
	    energy = 0;
	}
    }

    
    /* Handle mouse events. */
    public boolean mouseDown (Event e, int x, int y)
    {
	if (dead)
	{
	    doit ();
	}
	return true;
    }

    
    /* Handle keyboard events. */
    public boolean keyDown (Event e, int key)
    {
	if (dead)
	{
	    return true;
	}
	
	if (key == key_clockwise) /* move cannon clockwise */
	{
	    new_dir = curr_dir + 1;
	    if (new_dir == 4)
	    {
		new_dir = 0;
	    }
	    repaint ();
	}
	else if (key == key_counter_clockwise) /* move cannon counter-clockwise */
	{
	    new_dir = curr_dir - 1;
	    if (new_dir == -1)
	    {
		new_dir = 3;
	    }
	    repaint ();
	}
	else if (key == key_fire) /* fire a laser pulse */
	{
	    createLaser (curr_dir);
	}
	return true;
    }


    /* See if this laser hits anything. */
    public void laserCollision (Thing laser)
    {
	Enumeration e;
	boolean collision = false;

	e = things.elements ();
	while (e.hasMoreElements ())
	{
	    Thing thing = (Thing) e.nextElement ();
	    if (thing.isAlive () && thing instanceof UFO)
	    {
		UFO ufo = (UFO) thing;
		if (ufo.collision (laser.x, laser.y))
		{
		    ufo.explode ();
		    collision = true;
		    score += UFO_POINTS;
		    createExplosion (ufo.x, ufo.y);
		}
	    }
	}

	if (collision)
	{
	    laser.explode ();
	}
    }

    
    /* Stop this applet. */
    public void stop ()
    {
	stopAll ();
	if (thread != null)
	{
	    thread.stop ();
	    thread = null;
	}
    }

    
    /* Stop all threads. */
    public void stopAll ()
    {
	if (things != null)
	{
	    Enumeration e;
	    e = things.elements ();
	    while (e.hasMoreElements ())
	    {
		Thing thing = (Thing) e.nextElement ();
		thing.explode ();
	    }
	}
    }

    
    /* Stop everything except the starfield. */
    public void stopThings ()
    {
	if (things != null)
	{
	    Enumeration e;
	    e = things.elements ();
	    while (e.hasMoreElements ())
	    {
		Thing thing = (Thing) e.nextElement ();
		if (!(thing instanceof Starfield))
		{
		    thing.explode ();
		}
	    }
	}
    }


    /* Update all moving objects. */
    public void updateThings (Graphics g)
    {
	if (things == null || things.size () == 0)
	{
	    return;
	}
	
	Enumeration e = things.elements ();
	while (e.hasMoreElements ())
	{
	    Thing thing = (Thing) e.nextElement ();
	    if (thing.isAlive ())
	    {
		thing.paint (g);
		if (thing instanceof Laser)
		{
		    laserCollision (thing);
		}
	    }
	    else
	    {
		if (thing instanceof UFO)
		{
		    ufoCount--;
		    
		    UFO ufo = (UFO) thing;
		    if (ufo.hit && !dead)
		    {
			if (ufo.image == energyImage)
			{
			    energy += 25;
			    if (energy > 100)
			    {
				energy = 100;
			    }
			}
			else if (ufo.image == shieldImage)
			{
			    shield += 25;
			    if (shield > 100)
			    {
				shield = 100;
			    }
			}
			else if (ufo.image == deathImage)
			{
			    dead = true;
			}
			else
			{
			    createExplosion (ufo.x, ufo.y);
			    shield -= UFO_DAMAGE;
			    if (shield <= 0)
			    {
				shield = 0;
				dead = true;
			    }
			}
		    }
		}
		
		if (thing instanceof Laser)
		{
		    laserCount--;
		}
		
		thing.erase (g);
		things.removeElement (thing);
	    }
	}
    }


    /* Draw the energy and shield meters. */
    void paintMeters (Graphics g)
    {
	int w;
	int h = fontMetrics.getHeight ();
	int l = 100;
	int x = 3;
	int y = SCREEN_HEIGHT - 50;

	g.setColor (Color.white);
	w = fontMetrics.stringWidth (energyString);
	g.drawString (energyString, x, y + h);
	g.drawRect (x + w, y, l, h);
	paintMeter (g, x + w + 2, y + 2, l - 3, h - 3, energy);

	x = SCREEN_WIDTH - l - w;
	
	g.setColor (Color.white);
	w = fontMetrics.stringWidth (shieldString);
	g.drawString (shieldString, x, y + h);
	g.drawRect (x + w, y, l, h);
	paintMeter (g, x + w + 2, y + 2, l - 3, h - 3, shield);
    }


    /* Draw a meter slider. */
    void paintMeter (Graphics g, int x, int y, int w, int h, int value)
    {
	int m;
	
	if (value > 75)
	{
	    g.setColor (Color.blue);
	}
	else if (value > 50)
	{
	    g.setColor (Color.green);
	}
	else if (value > 25)
	{
	    g.setColor (Color.yellow);
	}
	else
	{
	    g.setColor (Color.red);
	}
	m = (int)(w*((100f - (100-value))/100f));
	if (m < 0)
	{
	    m = 0;
	}
	g.clearRect (x, y, w, h);
	g.fillRect (x, y, m, h);
    }

    
    /* Paint the cannon. */
    void paintCannon (Graphics g, int dir, Color color)
    {
	g.setColor (color);
	g.fillPolygon (cannons[dir]);
    }

    
    /* Paint the base. */
    public void paintBase (Graphics g)
    {
	int x = SCREEN_WIDTH / 2;
	int y = SCREEN_HEIGHT / 2;

	if (dead)
	{
	    int w, h;

	    h = fontMetrics.getHeight ();
	    
	    w = fontMetrics.stringWidth (titleString);
	    g.setColor (Color.green);
	    g.drawString (titleString, x - w/2, 2*h);
	    
	    w = fontMetrics.stringWidth ("XXXX");
	    paintCannon (g, curr_dir, Color.black);
	    if (tracker.checkAll (true))
	    {
		g.drawImage (deathImage, x - 16, y - 16, this);
	    }
	    g.setColor (Color.red);
	    g.drawString ("GAME", x - w/2, y - 32);
	    g.drawString ("OVER", x - w/2, y + 32 + h);

	    w = fontMetrics.stringWidth (welcomeString);
	    g.setColor (Color.green);
	    g.drawString (welcomeString, x - w/2, SCREEN_HEIGHT - h);
	}
	else
	{
	    g.setColor (Color.green);
	    g.fillRect (x - BASE_WIDTH, y - BASE_WIDTH, BASE_WIDTH*2, BASE_WIDTH*2);
	    if (curr_dir != new_dir)
	    {
		paintCannon (g, curr_dir, Color.black);
	    }
	    curr_dir = new_dir;
	    paintCannon (g, curr_dir, Color.green);
	}
    }

    
    /* Create a number prefixed with zeros. */
    String zeroNumberString (int number, int length)
    {
	StringBuffer s;

	s = new StringBuffer (Integer.toString (number));
	while (s.length () < length)
	{
	    s.insert (0, "0");
	}

	return s.toString ();
    }


    /* Paint the current score. */
    public void paintScore (Graphics g)
    {
	int sw = fontMetrics.stringWidth (scoreString);
	int w = fontMetrics.stringWidth ("00000");
	int h = fontMetrics.getHeight ();
	int x = 0;
	int y = h;

	g.setColor (Color.white);
	g.drawString (scoreString, SCREEN_WIDTH - w - sw - 5, y);
	x = SCREEN_WIDTH - w - 5;
	g.setColor (Color.black);
	g.fillRect (x, y - h, w, h);
	g.setColor (Color.white);
	g.drawString (zeroNumberString (score, 5), x, y);
    }


    /* Don't clear the screen; just call paint. */
    public void update (Graphics g)
    {
	paint (g);
    }


    /* Paint the screen. */
    public void paint (Graphics g)
    {
	if (things == null || things.size () == 0)
	{
	    things = new Vector (32);
	    createStarfield ();
	}

	if (clearScreen)
	{
	    g.setColor (Color.black);
	    g.fillRect (0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
	    clearScreen = false;
	}
	
	updateThings (g);
	if (!dead)
	{
	    paintMeters (g);
	}
	if (!dead || score > 0)
	{
	    paintScore (g);
	}
	paintBase (g);
    }
}

/*
Local variables:
eval: (progn (make-local-variable 'compile-command) (setq compile-command (concat "javac " buffer-file-name)))
End:
*/
