i wrote a little game called Aim, Fire! just to screw around.
its immense complexity involves shooting little targets that fly across the screen.
you can see it at http://www.brianmaissy.com/applets/aimfire

i made a class representing a target that paints itself and is its own thread.
its kinda nifty in a OOP sort of way: all i have to do is instantiate a target; and it takes care of the rest, notifying the driver program of anything important (like it getting shot)
the problem, however, is the speed changes significantly depending on how many targets are active, and the graphics are very flickery.

i think the problem may be that having multiple threads is slow.
i also tried buffering my graphics but it didnt work very well.
however that may just be because i did it wrong.
in fact i probably did a lot of things wrong - please notify me of anything you see that i did badly

so tell me what you think about the design of the program, and any suggestions you have to restructure it. would i do better just to stick everything in one thread, or even all in one class?

thanks very much!

here is the code: (also found at http://www.brianmaissy.com/applets/a...e/AimFire.java and http://www.brianmaissy.com/applets/a...re/Target.java)

Java Code:
package aimFire;

import java.applet.Applet;
import java.awt.Color;
import java.awt.Event;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Random;

public class AimFire extends Applet{

	public static final int SPEED_CONSTANT = 500;
	public static final int MIN_X = 0;
	public static final int MIN_Y = 21;
	public static final int MAX_X = 399;
	public static final int MAX_Y = 399;
	
	Image ammoImage;

	private Random rand;
	
	private static final int CLIP = 10;
	private static final int TOTAL_TARGETS = 20;

	private int toBeAdded;
	private ArrayList<Target> targets;
	private int targetsKilled;
	
	private int ammo;
	private int score;
	private boolean gameActive;
	
	public void init(){
		setSize(400, 400);
		setBackground(Color.black);
        setCursor(getToolkit().createCustomCursor(getImage(this.getClass().getResource("crosshair.gif")), new Point(16, 16), "crosshair"));
        ammoImage = getImage(this.getClass().getResource("ammo.gif"));
        rand = new Random();
        ammo = CLIP;
        score = 0;
        gameActive = true;
        targetsKilled = 0;
        toBeAdded = TOTAL_TARGETS;
        targets = new ArrayList<Target>();
        sendRandom();
        sendRandom();
	}
	
	public boolean mouseDown(Event e, int x, int y){
		if(gameActive && ammo > 0){
			ammo--;
			for(int count = 0; count < targets.size(); count++){
				score += targets.get(count).hit(x, y);
			}
			repaint();
		}
		return true;
	}
	
	public boolean keyDown(Event e, int key){
		if((char)key == ' ' && gameActive){
			ammo = CLIP;
			repaint();
		}
		return true;
	}
	
	private void sendRandom(){
        toBeAdded--;
		Target t = new Target(this, getGraphics(), rand);
		targets.add(t);
        new Thread(t).start();
        repaint();
	}
	
	public void notifyOfDeath(Target t){
		targetsKilled++;
		targets.remove(t);
		if(toBeAdded > 0){
			sendRandom();
		}
		if(targets.size()==0){
			gameActive = false;
		}
		repaint();
	}
	
	public void paint(Graphics g){
		g.setColor(Color.gray);
        g.drawLine(0, 20, 399, 20);
		for(int count = 0; count < ammo; count++){
			g.drawImage(ammoImage, count*10 + 3, 3, this);
		}
		if(ammo == 0){
			g.drawString("press space to reload", 3, 13);
		}
        g.drawString("Targets: " + targetsKilled + "/" + TOTAL_TARGETS, 230, 13);
        g.drawString("Score: " + score, 340, 13);
		if(!gameActive){
			g.setColor(Color.gray);
			g.drawString("Game Over", 170, 200);
			g.drawString("Score: " + score, 170, 230);
		}
	}
	
}
Java Code:
package aimFire;

import java.awt.Color;
import java.awt.Graphics;
import java.util.Random;

public class Target implements Runnable{
	
	private AimFire a;
	private Graphics g;
	
	private int radius;
	private int xLocation;
	private int yLocation;
	private int xDirection;
	private int yDirection;
	private int xSpeed;
	private int ySpeed;
	
	public Target(AimFire driver, Graphics graphics, Random rand){
		this(driver, graphics, 3*(rand.nextInt(9)+ 2), rand.nextInt(1 + AimFire.MAX_X - 60) + 30 + AimFire.MIN_X, rand.nextInt(1 + AimFire.MAX_Y - 60 - AimFire.MIN_Y) + 30 + AimFire.MIN_Y, rand.nextInt(3)-1, rand.nextInt(3)-1, rand.nextInt(5)+1, rand.nextInt(5)+1);
		if(xDirection == 0 && yDirection == 0){
			if(rand.nextInt(2)==0){
				if(rand.nextInt(2)==0){
					xDirection = 1;
				}else{
					xDirection = -1;
				}
			}else{
				if(rand.nextInt(2)==0){
					yDirection = 1;
				}else{
					yDirection = -1;
				}
			}
		}
	}
	
	public Target(AimFire driver, Graphics graphics, int r, int x, int y, int xDir, int yDir, int xSp, int ySp){
		a = driver;
		g = graphics;
		radius = r;
		xLocation = x;
		yLocation = y;
		xDirection = xDir;
		yDirection = yDir;
		xSpeed = xSp;
		ySpeed = ySp;
	}

	public void run() {
		if(g!=null){
			paint();
			int count = 1;
			while(xLocation >= AimFire.MIN_X && xLocation <= AimFire.MAX_X && yLocation >= AimFire.MIN_Y && yLocation <= AimFire.MAX_Y){
				unpaint();
				if(count % (AimFire.SPEED_CONSTANT/xSpeed) == 0){
					xLocation += xDirection;
				}
				if(count % (AimFire.SPEED_CONSTANT/ySpeed) == 0){
					yLocation += yDirection;
				}
				paint();
				count++;
			}
			unpaint();
			a.notifyOfDeath(this);
		}
	}
	
	public int hit(int x, int y){
		if(Math.abs(x - xLocation) < radius/3 && Math.abs(y - yLocation) < radius/3){
			xLocation = -99;
			return 3;
		}else if(Math.abs(x - xLocation) < 2*radius/3 && Math.abs(y - yLocation) < 2*radius/3){
			xLocation = -99;
			return 2;
		}else if(Math.abs(x - xLocation) < radius && Math.abs(y - yLocation) < radius){
			xLocation = -99;
			return 1;
		}else{
			return 0;
		}
	}
	
	private void paint(){
		g.setColor(Color.red);
		g.fillOval(xLocation-radius, yLocation-radius, 2*radius, 2*radius);
		g.setColor(Color.white);
		g.fillOval(xLocation-2*radius/3, yLocation-2*radius/3, 4*radius/3, 4*radius/3);
		g.setColor(Color.red);
		g.fillOval(xLocation-radius/3, yLocation-radius/3, 2*radius/3, 2*radius/3);
	}
	
	private void unpaint(){
		g.setColor(Color.black);
		g.fillOval(xLocation-radius, yLocation-radius, 2*radius, 2*radius);
	}
	
}