/****************************************************************************** Tail Gunner, Version 1.2 Copyright 1998-2001 by Mike Hall. Please see http://www.brainjar.com for terms of use. Revision History: 1.01, 12/18/1999: Added progress bar for loading of sound clips. 1.2, 01/25/2001: Updated to JDK 1.1.8. Usage: Keyboard Controls: Mouse Controls: S - Start Game Move - Aim Crosshairs P - Toggle Pause Button - Start Game/Fire M - Toggle Sound ******************************************************************************/ import java.awt.*; import java.awt.event.*; import java.net.*; import java.util.*; import java.applet.AudioClip; import java.applet.Applet; /****************************************************************************** The TGPoint class defines a point in three-space. ******************************************************************************/ class TGPoint { // Fields: double xo, yo, zo; // Original point in three space. double xt, yt, zt; // Point after transformations. double xp, yp, zp; // Two-dimensional point and depth after projection. // Constructors: public TGPoint(double x, double y, double z) { this.xo = x; this.yo = y; this.zo = z; this.xt = x; this.yt = y; this.zt = z; this.xp = x; this.yp = y; this.zp = z; } // Methods: // Rotate, scale and translate the point. public void transform(double ax, double ay, double az, double m, double dx, double dy, double dz) { double x, y, z, c, s; // Rotate about the x-azis. x = this.xo; y = this.yo; z = this.zo; c = Math.cos(ax); s = Math.sin(ax); this.xt = x; this.yt = y * c - z * s; this.zt = y * s + z * c; // Rotate about the y-azis. x = this.xt; y = this.yt; z = this.zt; c = Math.cos(ay); s = Math.sin(ay); this.xt = x * c - z * s; this.yt = y; this.zt = x * s + z * c; // Rotate about the z-azis. x = this.xt; y = this.yt; z = this.zt; c = Math.cos(az); s = Math.sin(az); this.xt = x * c - y * s; this.yt = x * s + y * c; this.zt = z; // Scale. this.xt *= m; this.yt *= m; this.zt *= m; // Translate. this.xt += dx; this.yt += dy; this.zt += dz; } // Project the point onto a two-dimensional plane. public void project(TGPoint v) { double d; // Project the point. d = v.zt - this.zt; if (d != 0) { this.xp = (v.zt * this.xt - v.xt * this.zt) / d; this.yp = (v.zt * this.yt - v.yt * this.zt) / d; this.zp = (v.zt * this.zt) / d; } else { this.xp = 0.0; this.yp = 0.0; this.zp = 0.0; } } } /****************************************************************************** The TGFace class defines a set of points that make up one face of a three-dimensional object. ******************************************************************************/ class TGFace { // Fields: Vector indices; // Constructors: public TGFace() { this.indices = new Vector(); } // Methods: // Add an index to a vertice list to the face. public void addIndice(int i) { this.indices.addElement(new Integer(i)); } } /****************************************************************************** The TGObject class defines a set of points and faces for a three-dimensional object. The right-hand rule applies to the order of the points in each face so that one side will be considered the outer (and visible) side. When a TGObject is rendered, a set of two-dimensional polygons is created for each visible face. These should be used for drawing the object. There is also a master list of all TGObjects that have been rendered. The objects should be drawn in the order given by this list so that more distant objects are drawn before closer ones. ******************************************************************************/ class TGObject { // Fields: Vector vertices; // Object shape data. Vector faces; double ax, ay, az; // Rotation, scaling and translation values for object. double m; double dx, dy, dz; Vector polygons; // Two-dimensional representation of object. Vector angles; // Angle with respect to viewpoint. Vector depths; // Minimum depths of each face. // A list of all rendered three-dimensional objects. static Vector list = new Vector(); // Constructors: public TGObject() { this.vertices = new Vector(); this.ax = this.ay = this.az = 0.0; this.m = 1.0; this.dx = this.dy = this.dz = 0.0; this.faces = new Vector(); this.polygons = new Vector(); this.angles = new Vector(); this.depths = new Vector(); } // Create by copying an existing three-dimensional object. public TGObject(TGObject o) { int i; this.vertices = new Vector(); for (i = 0; i < o.vertices.size(); i++) this.vertices.addElement(o.vertices.elementAt(i)); this.ax = o.ax; this.ay = o.ay; this.az = o.az; this.m = o.m; this.dx = o.dx; this.dy = o.dy; this.dz = o.dz; this.faces = new Vector(); for (i = 0; i < o.faces.size(); i++) this.faces.addElement(o.faces.elementAt(i)); this.polygons = new Vector(); this.angles = new Vector(); this.depths = new Vector(); } // Methods: // Add a vertice. public void addVertice(TGPoint v) { this.vertices.addElement(v); } // Add a face. public void addFace(TGFace f) { this.faces.addElement(f); } // Render each face by projecting its points and creating a two-dimensional // polygon. public void render(TGPoint v, double mag, int xoffset, int yoffset) { int i, j, k; Integer index; Polygon polygon; TGPoint point; TGPoint[] p; TGFace face; double angle; double d1; Double d2; boolean inserted; // Project each point. for (i = 0; i < this.vertices.size(); i++) { point = (TGPoint) this.vertices.elementAt(i); point.transform(this.ax, this.ay, this.az, this.m, this.dx, this.dy, this.dz); point.project(v); } // Build the two-dimensional polygons using face data. this.polygons.removeAllElements(); this.angles.removeAllElements(); this.depths.removeAllElements(); d1 = 0; for (i = 0; i < this.faces.size(); i++) { // Build the polygon. polygon = new Polygon(); face = (TGFace) this.faces.elementAt(i); for (j = 0; j < face.indices.size(); j++) { index = (Integer) face.indices.elementAt(j); k = index.intValue(); point = (TGPoint) this.vertices.elementAt(k); polygon.addPoint((int) Math.round((mag * point.xp) + xoffset), (2 * yoffset) - Math.round((int) (mag * point.yp) + yoffset)); // Get the minimum depth of all points in face. if (j == 0 || point.zp > d1) d1 = point.zp; } // Find the angle of the face with respect to the viewpoint. Only add the // polygon if the outside of the face is visible (right-hand rule). p = new TGPoint[3]; index = (Integer) face.indices.elementAt(0); p[0] = (TGPoint) this.vertices.elementAt(index.intValue()); index = (Integer) face.indices.elementAt(1); p[1] = (TGPoint) this.vertices.elementAt(index.intValue()); index = (Integer) face.indices.elementAt(2); p[2] = (TGPoint) this.vertices.elementAt(index.intValue()); angle = calcAngle(p, v); // Insert the face according to depth. if (angle < Math.PI / 2) { k = 0; inserted = false; do { if (k < this.polygons.size()) { d2 = (Double) this.depths.elementAt(k); if (d1 < d2.doubleValue()) { this.polygons.insertElementAt(polygon, k); this.angles.insertElementAt(new Double(angle), k); this.depths.insertElementAt(new Double(d1), k); inserted = true; } } k++; } while (k < this.polygons.size() && !inserted); if (!inserted) { this.polygons.addElement(polygon); this.angles.addElement(new Double(angle)); this.depths.addElement(new Double(d1)); } } } // Add object to the master list. this.insert(); } // Find the angle of a face with respect to the viewpoint. private double calcAngle(TGPoint p[], TGPoint v) { TGPoint a, b, m, n; double s, t, u; double dot, div; double angle; // Find a normal vector to the plane of the face. a = new TGPoint(p[1].xt - p[0].xt, p[1].yt - p[0].yt, p[1].zt - p[0].zt); b = new TGPoint(p[2].xt - p[1].xt, p[2].yt - p[1].yt, p[2].zt - p[1].zt); s = a.yt * b.zt - b.yo * a.zt; t = -(a.xt * b.zt - b.xo * a.zt); u = a.xt * b.yt - b.xo * a.yt; n = new TGPoint(s, t, u); // Find the view vector. m = new TGPoint(v.xt - p[1].xt, v.yt - p[1].yt, v.zt - p[1].zt); // Find the angle between these two vectors. dot = m.xo * n.xt + m.yt * n.yt + m.zt * n.zt; div = Math.sqrt(m.xt * m.xt + m.yt * m.yt + m.zt * m.zt) * Math.sqrt(n.xt * n.xt + n.yt * n.yt + n.zt * n.zt); if (div == 0) return 0; angle = Math.acos(dot / div); return angle; } // Reset the list. public static void reset() { list.removeAllElements(); } // Insert a three-dimensional object into the master list according to depth. private void insert() { int i; boolean inserted; TGObject o; // Insert the object according to depth. i = 0; inserted = false; do { if (i < list.size()) { o = (TGObject) list.elementAt(i); if (this.dz < o.dz) { list.insertElementAt(this, i); inserted = true; } } i++; } while (i < list.size() && !inserted); if (!inserted) list.addElement(this); } } /****************************************************************************** The TGShip class extends the TGObject class and add values for color, movement, etc. ******************************************************************************/ class TGShip extends TGObject { // Fields: int type; // Type of ship. int color; // Color index. double speed; // Speed. double a1, a2; // Angles for movement along x- and y-axises. double c1, c2; // Change values for above angles. double dist1; // Distance from player. double dist2; // Last distance from player. boolean sounded; // Sound effect played flag. boolean exploding; // Explosion flag. int counter; // Counter for explosions. // Constructors: public TGShip() { // Initialize TGObject fields. this.vertices = new Vector(); this.ax = this.ay = this.az = 0.0; this.m = 1.0; this.dx = this.dy = this.dz = 0.0; this.faces = new Vector(); this.polygons = new Vector(); this.angles = new Vector(); this.depths = new Vector(); // Initialize new fields. this.type = 0; this.color = 0; this.speed = 0; this.a1 = 0; this.a2 = 0; this.c1 = 0; this.c2 = 0; this.dist1 = 0; this.dist2 = 0; this.sounded = false; this.exploding = false; this.counter = 0; } public TGShip(TGObject o) { int i; // Copy source ship's TGObject fields. this.vertices = new Vector(); for (i = 0; i < o.vertices.size(); i++) this.vertices.addElement(o.vertices.elementAt(i)); this.ax = o.ax; this.ay = o.ay; this.az = o.az; this.m = o.m; this.dx = o.dx; this.dy = o.dy; this.dz = o.dz; this.faces = new Vector(); for (i = 0; i < o.faces.size(); i++) this.faces.addElement(o.faces.elementAt(i)); this.polygons = new Vector(); this.angles = new Vector(); this.depths = new Vector(); // Initialize new fields. this.type = 0; this.color = 0; this.speed = 0; this.a1 = 0; this.a2 = 0; this.c1 = 0; this.c2 = 0; this.dist1 = 0; this.dist2 = 0; this.sounded = false; this.exploding = false; this.counter = 0; } } /****************************************************************************** The TGDebris class extends the TGShip class and adds values for rotation and movement, etc. ******************************************************************************/ class TGDebris extends TGShip { // Fields: double rx, ry, rz; // Change values for rotation. double mx, my, mz; // Change values position. boolean active; // Active flag. public TGDebris() { // Initialize TGObject fields. this.vertices = new Vector(); this.ax = this.ay = this.az = 0.0; this.m = 1.0; this.dx = this.dy = this.dz = 0.0; this.faces = new Vector(); this.polygons = new Vector(); this.angles = new Vector(); this.depths = new Vector(); // Initialize new fields. this.rx = 0; this.ry = 0; this.rz = 0; this.mx = 0; this.my = 0; this.mz = 0; this.active = false; } public TGDebris(TGShip s, int i) { int j; TGPoint point; TGFace face; Integer index; // Initialize TGObject fields using the specified face of the given ship. this.vertices = new Vector(); face = (TGFace) s.faces.elementAt(i); for (j = 0; j < face.indices.size(); j++) { index = (Integer) face.indices.elementAt(j); point = (TGPoint) s.vertices.elementAt(index.intValue()); this.addVertice(point); } face = new TGFace(); for (j = 0; j < this.vertices.size(); j++) face.addIndice(j); this.addFace(face); face = new TGFace(); for (j = this.vertices.size() - 1; j >= 0; j--) face.addIndice(j); this.addFace(face); this.polygons = new Vector(); this.angles = new Vector(); this.depths = new Vector(); // Copy TGship fields. this.ax = s.ax; this.ay = s.ay; this.az = s.az; this.m = s.m; this.dx = s.dx; this.dy = s.dy; this.dz = s.dz; this.color = s.color; } } /****************************************************************************** Main applet code. ******************************************************************************/ public class TailGunner extends Applet implements Runnable, KeyListener, MouseListener, MouseMotionListener { // Copyright information. String copyName = "Tail Gunner"; String copyVers = "Version 1.2"; String copyInfo = "Copyright 1998-2001 by Mike Hall"; String copyLink = "http://www.brainjar.com"; String copyText = copyName + '\n' + copyVers + '\n' + copyInfo + '\n' + copyLink; // Thread control variables. Thread loadThread; Thread loopThread; // Constants. static final int DELAY = 50; // Milliseconds between screen updates. static final int MAG = 100; // Magnification for 3D objects. static final int DEMO = 1; // Game states. static final int PLAY = 2; static final int END = 3; // Ship constants. static final int NUM_TYPES = 4; static final int NUM_COLORS = 6; static final int NUM_SHIPS = 6; static final int START_DISTANCE = -1000; static final int SOUND_DISTANCE = -150; static final int END_DISTANCE = 0; static final int MAX_HORIZONTAL = 100; static final int MAX_VERTICAL = 30; static final int MIN_SPEED = 2; static final int MAX_SPEED = 8; static final double MAX_TURN = Math.PI / 100; static final int EXPLODE_COUNT = 100; // Debris constants. static final int MAX_DEBRIS = 20; static final int DEBRIS_COUNT = 50; static final double MAX_SPIN = Math.PI / 20; // Demo mode constants. static final int DEMO_DISTANCE = -40; static final int DEMO_COUNT = 400; // Miscellaneous game constants. static final int BLAST_COUNT = 5; static final int END_COUNT = 200; static final int MAX_SHAKE = 5; static final int NUM_STARS = 20; static final int SHIELD_START = 100; static final int TARGET_WIDTH = 60; static final int TARGET_HEIGHT = 40; // Game data. boolean loaded = false; boolean firing; boolean paused; boolean sound; int gameState; int blastCounter; int endCounter; int mx, my; int score; int highScore; int shields; // Enemy ships. TGObject[] types = new TGObject[NUM_TYPES]; double[] typefrq = { .30, .30, .20, .20 }; int[] typepts = { 125, 150, 100, 250 }; double[] typespd = { 1.15, 1.35, 1.00, 1.75 }; double[] typetrn = { 1.15, 1.15, 1.00, 1.25 }; int[] typedmg = { 2, 4, 5, 3 }; String[] typenme = { "F15 Fighter", "F17A Advanced Tactical Fighter", "F21 Fighter/Bomber", "SF-111 Interceptor" }; TGShip[] ships = new TGShip[NUM_SHIPS]; // Explosion debris. TGDebris[] debris = new TGDebris[MAX_DEBRIS]; int debrisIndex = 0; // Viewpoint. TGPoint viewpoint; // Screen size. int scrnWidth; int scrnHeight; // Stars. int starsX[] = new int[NUM_STARS]; int starsY[] = new int[NUM_STARS]; double starsAngle[] = new double[NUM_STARS]; double starsRadius[] = new double[NUM_STARS]; double starsMinRadius[] = new double[NUM_STARS]; // Demo data. int demoIndex; int demoCounter; // Off screen image. Dimension offDimension; Image offImage; Graphics offGraphics; // Font data. Font font = new Font("Helvetica", Font.BOLD, 12); FontMetrics fm = getFontMetrics(font); int fontWidth = fm.getMaxAdvance(); int fontHeight = fm.getHeight(); Font smallFont = new Font("Dialog", font.PLAIN, 10); FontMetrics smallFm = getFontMetrics(smallFont); int smallFontWidth = fm.getMaxAdvance(); int smallFontHeight = fm.getHeight(); // Sound clips. AudioClip blastSound; AudioClip debrisSound; AudioClip explodeSound; AudioClip fireSound; AudioClip passingSound; AudioClip targetedSound; // For tracking the loading of sound clips. int clipTotal = 0; int clipsLoaded = 0; // Applet information. public String getAppletInfo() { // Return copyright information. return(copyText); } public void init() { Dimension d = getSize(); TGFace face; int i; // Display copyright information. System.out.println(copyText); // Set up mouse and key event handling and set focus to the applet window. addKeyListener(this); addMouseListener(this); addMouseMotionListener(this); requestFocus(); // Set cursor to crosshairs. setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR)); // Initialize shape data for each ship type. // Define type 1 ship. types[0] = new TGObject(); types[0].addVertice(new TGPoint(-2, 1, 5)); // Fuselage. types[0].addVertice(new TGPoint( 2, 1, 5)); types[0].addVertice(new TGPoint( 0, 0, -10)); types[0].addVertice(new TGPoint(-2, -1, 5)); types[0].addVertice(new TGPoint( 2, -1, 5)); types[0].addVertice(new TGPoint(-1, -1, 7)); types[0].addVertice(new TGPoint( 1, -1, 7)); face = new TGFace(); face.addIndice(0); face.addIndice(1); face.addIndice(2); types[0].addFace(face); face = new TGFace(); face.addIndice(6); face.addIndice(5); face.addIndice(3); face.addIndice(2); face.addIndice(4); types[0].addFace(face); face = new TGFace(); face.addIndice(4); face.addIndice(2); face.addIndice(1); types[0].addFace(face); face = new TGFace(); face.addIndice(0); face.addIndice(2); face.addIndice(3); types[0].addFace(face); face = new TGFace(); face.addIndice(0); face.addIndice(5); face.addIndice(6); face.addIndice(1); types[0].addFace(face); face = new TGFace(); face.addIndice(1); face.addIndice(6); face.addIndice(4); types[0].addFace(face); face = new TGFace(); face.addIndice(0); face.addIndice(3); face.addIndice(5); types[0].addFace(face); types[0].addVertice(new TGPoint(5, 0, -2)); // Port wing. types[0].addVertice(new TGPoint(5, -1, -2)); types[0].addVertice(new TGPoint(5, 0, -12)); face = new TGFace(); face.addIndice(1); face.addIndice(7); face.addIndice(9); face.addIndice(2); types[0].addFace(face); face = new TGFace(); face.addIndice(2); face.addIndice(9); face.addIndice(8); face.addIndice(4); types[0].addFace(face); face = new TGFace(); face.addIndice(7); face.addIndice(1); face.addIndice(4); face.addIndice(8); types[0].addFace(face); face = new TGFace(); face.addIndice(7); face.addIndice(8); face.addIndice(9); types[0].addFace(face); types[0].addVertice(new TGPoint(10, 3, -7)); face = new TGFace(); face.addIndice(7); face.addIndice(10); face.addIndice(9); types[0].addFace(face); face = new TGFace(); face.addIndice(9); face.addIndice(10); face.addIndice(8); types[0].addFace(face); face = new TGFace(); face.addIndice(7); face.addIndice(8); face.addIndice(10); types[0].addFace(face); types[0].addVertice(new TGPoint(-5, 0, -2)); // Starboard wing. types[0].addVertice(new TGPoint(-5, -1, -2)); types[0].addVertice(new TGPoint(-5, 0, -12)); face = new TGFace(); face.addIndice(0); face.addIndice(2); face.addIndice(13); face.addIndice(11); types[0].addFace(face); face = new TGFace(); face.addIndice(2); face.addIndice(3); face.addIndice(12); face.addIndice(13); types[0].addFace(face); face = new TGFace(); face.addIndice(0); face.addIndice(11); face.addIndice(12); face.addIndice(3); types[0].addFace(face); face = new TGFace(); face.addIndice(11); face.addIndice(13); face.addIndice(12); types[0].addFace(face); types[0].addVertice(new TGPoint(-10, 3, -7)); face = new TGFace(); face.addIndice(11); face.addIndice(13); face.addIndice(14); types[0].addFace(face); face = new TGFace(); face.addIndice(14); face.addIndice(13); face.addIndice(12); types[0].addFace(face); face = new TGFace(); face.addIndice(14); face.addIndice(12); face.addIndice(11); types[0].addFace(face); // Define type 2 ship. types[1] = new TGObject(); types[1].addVertice(new TGPoint(-1, -1, 7)); // Fuselage. types[1].addVertice(new TGPoint( 1, -1, 7)); types[1].addVertice(new TGPoint( 2, 1, 3)); types[1].addVertice(new TGPoint(-2, 1, 3)); types[1].addVertice(new TGPoint( 3, -1, 3)); types[1].addVertice(new TGPoint( 0, 0, -7)); types[1].addVertice(new TGPoint(-3, -1, 3)); face = new TGFace(); face.addIndice(0); face.addIndice(1); face.addIndice(2); face.addIndice(3); types[1].addFace(face); face = new TGFace(); face.addIndice(5); face.addIndice(3); face.addIndice(2); types[1].addFace(face); face = new TGFace(); face.addIndice(4); face.addIndice(2); face.addIndice(1); types[1].addFace(face); face = new TGFace(); face.addIndice(6); face.addIndice(0); face.addIndice(3); types[1].addFace(face); face = new TGFace(); face.addIndice(6); face.addIndice(5); face.addIndice(4); face.addIndice(1); face.addIndice(0); types[1].addFace(face); types[1].addVertice(new TGPoint( 9, 3, -1)); // Port wing. types[1].addVertice(new TGPoint( 9, 2.5, -1)); types[1].addVertice(new TGPoint( 9, 3, -10)); types[1].addVertice(new TGPoint(12, 1, -3)); types[1].addVertice(new TGPoint(12, 1, -10)); face = new TGFace(); face.addIndice(2); face.addIndice(7); face.addIndice(9); face.addIndice(5); types[1].addFace(face); face = new TGFace(); face.addIndice(8); face.addIndice(4); face.addIndice(5); face.addIndice(9); types[1].addFace(face); face = new TGFace(); face.addIndice(7); face.addIndice(10); face.addIndice(11); face.addIndice(9); types[1].addFace(face); face = new TGFace(); face.addIndice(11); face.addIndice(10); face.addIndice(8); face.addIndice(9); types[1].addFace(face); face = new TGFace(); face.addIndice(2); face.addIndice(4); face.addIndice(8); face.addIndice(7); types[1].addFace(face); face = new TGFace(); face.addIndice(7); face.addIndice(8); face.addIndice(10); types[1].addFace(face); types[1].addVertice(new TGPoint( -9, 3, -1)); // Starboard wing. types[1].addVertice(new TGPoint( -9, 2.5, -1)); types[1].addVertice(new TGPoint( -9, 3, -10)); types[1].addVertice(new TGPoint(-12, 1, -3)); types[1].addVertice(new TGPoint(-12, 1, -10)); types[1].addFace(face); face = new TGFace(); face.addIndice(3); face.addIndice(5); face.addIndice(14); face.addIndice(12); types[1].addFace(face); face = new TGFace(); face.addIndice(5); face.addIndice(6); face.addIndice(13); face.addIndice(14); types[1].addFace(face); face = new TGFace(); face.addIndice(12); face.addIndice(14); face.addIndice(16); face.addIndice(15); types[1].addFace(face); face = new TGFace(); face.addIndice(13); face.addIndice(15); face.addIndice(16); face.addIndice(14); types[1].addFace(face); face = new TGFace(); face.addIndice(3); face.addIndice(12); face.addIndice(13); face.addIndice(6); types[1].addFace(face); face = new TGFace(); face.addIndice(12); face.addIndice(15); face.addIndice(13); types[1].addFace(face); // Define type 3 ship. types[2] = new TGObject(); types[2].addVertice(new TGPoint( 1, -.5, 7)); // Fuselage. types[2].addVertice(new TGPoint(-1, -.5, 7)); types[2].addVertice(new TGPoint( 3, 0, 2)); types[2].addVertice(new TGPoint(-3, 0, 2)); types[2].addVertice(new TGPoint( 1, 1.5, 5)); types[2].addVertice(new TGPoint(-1, 1.5, 5)); types[2].addVertice(new TGPoint( 2, 1, 1)); types[2].addVertice(new TGPoint(-2, 1, 1)); types[2].addVertice(new TGPoint( 0, 0, -9)); face = new TGFace(); face.addIndice(0); face.addIndice(4); face.addIndice(5); face.addIndice(1); types[2].addFace(face); face = new TGFace(); face.addIndice(4); face.addIndice(6); face.addIndice(7); face.addIndice(5); types[2].addFace(face); face = new TGFace(); face.addIndice(0); face.addIndice(6); face.addIndice(4); types[2].addFace(face); face = new TGFace(); face.addIndice(0); face.addIndice(2); face.addIndice(6); types[2].addFace(face); face = new TGFace(); face.addIndice(1); face.addIndice(5); face.addIndice(3); types[2].addFace(face); face = new TGFace(); face.addIndice(3); face.addIndice(5); face.addIndice(7); types[2].addFace(face); face = new TGFace(); face.addIndice(0); face.addIndice(1); face.addIndice(3); face.addIndice(2); types[2].addFace(face); face = new TGFace(); face.addIndice(6); face.addIndice(8); face.addIndice(7); types[2].addFace(face); face = new TGFace(); face.addIndice(2); face.addIndice(8); face.addIndice(6); types[2].addFace(face); face = new TGFace(); face.addIndice(3); face.addIndice(7); face.addIndice(8); types[2].addFace(face); face = new TGFace(); face.addIndice(2); face.addIndice(3); face.addIndice(8); types[2].addFace(face); types[2].addVertice(new TGPoint(12, 1, -10)); // Port wing. face = new TGFace(); face.addIndice(6); face.addIndice(9); face.addIndice(8); types[2].addFace(face); face = new TGFace(); face.addIndice(2); face.addIndice(8); face.addIndice(9); types[2].addFace(face); face = new TGFace(); face.addIndice(2); face.addIndice(9); face.addIndice(6); types[2].addFace(face); types[2].addVertice(new TGPoint(-12, 1, -10)); // Starboard wing. face = new TGFace(); face.addIndice(7); face.addIndice(8); face.addIndice(10); types[2].addFace(face); face = new TGFace(); face.addIndice(3); face.addIndice(10); face.addIndice(8); types[2].addFace(face); face = new TGFace(); face.addIndice(3); face.addIndice(7); face.addIndice(10); types[2].addFace(face); // Define type 4 ship. types[3] = new TGObject(); types[3].addVertice(new TGPoint(-1, 0, 4)); // Fuselage. types[3].addVertice(new TGPoint(-2, .5, 5)); types[3].addVertice(new TGPoint(-1, -1, 7)); types[3].addVertice(new TGPoint( 1, -1, 7)); types[3].addVertice(new TGPoint( 2, .5, 5)); types[3].addVertice(new TGPoint( 1, 0, 4)); types[3].addVertice(new TGPoint( 2, -1, 5)); types[3].addVertice(new TGPoint( 1, -1, 4)); types[3].addVertice(new TGPoint(-1, -1, 4)); types[3].addVertice(new TGPoint(-2, -1, 5)); types[3].addVertice(new TGPoint( 0, 1, -5)); types[3].addVertice(new TGPoint( 0, -1, -5)); face = new TGFace(); face.addIndice(0); face.addIndice(1); face.addIndice(4); face.addIndice(5); types[3].addFace(face); face = new TGFace(); face.addIndice(1); face.addIndice(2); face.addIndice(3); face.addIndice(4); types[3].addFace(face); face = new TGFace(); face.addIndice(0); face.addIndice(1); face.addIndice(4); face.addIndice(5); types[3].addFace(face); face = new TGFace(); face.addIndice(0); face.addIndice(5); face.addIndice(10); types[3].addFace(face); face = new TGFace(); face.addIndice(2); face.addIndice(9); face.addIndice(8); face.addIndice(11); face.addIndice(7); face.addIndice(6); face.addIndice(3); types[3].addFace(face); face = new TGFace(); face.addIndice(3); face.addIndice(6); face.addIndice(4); types[3].addFace(face); face = new TGFace(); face.addIndice(6); face.addIndice(7); face.addIndice(5); face.addIndice(4); types[3].addFace(face); face = new TGFace(); face.addIndice(2); face.addIndice(1); face.addIndice(9); types[3].addFace(face); face = new TGFace(); face.addIndice(1); face.addIndice(0); face.addIndice(8); face.addIndice(9); types[3].addFace(face); types[3].addVertice(new TGPoint(9, 0, -12)); // Port wing. face = new TGFace(); face.addIndice(5); face.addIndice(12); face.addIndice(10); types[3].addFace(face); face = new TGFace(); face.addIndice(7); face.addIndice(11); face.addIndice(12); types[3].addFace(face); face = new TGFace(); face.addIndice(5); face.addIndice(7); face.addIndice(12); types[3].addFace(face); face = new TGFace(); face.addIndice(12); face.addIndice(11); face.addIndice(10); types[3].addFace(face); types[3].addVertice(new TGPoint(-9, 0, -12)); // Starboard wing. face = new TGFace(); face.addIndice(0); face.addIndice(10); face.addIndice(13); types[3].addFace(face); face = new TGFace(); face.addIndice(13); face.addIndice(11); face.addIndice(8); types[3].addFace(face); face = new TGFace(); face.addIndice(13); face.addIndice(8); face.addIndice(0); types[3].addFace(face); face = new TGFace(); face.addIndice(13); face.addIndice(10); face.addIndice(11); types[3].addFace(face); // Set viewpoint. viewpoint = new TGPoint(0, 0, 10); // Save the screen size. scrnWidth = d.width; scrnHeight = d.height; // Initialize stars. for (i = 0; i < NUM_STARS; i++) initStar(i); highScore = 0; sound = true; initGame(); initDemo(); } public void initGame() { int i; // Initialize game data. gameState = PLAY; paused = false; firing = false; blastCounter = 0; score = 0; shields = SHIELD_START; // Initialize ships. for (i = 0; i < NUM_SHIPS; i++) initShip(i); // Initialize debris. for (i = 0; i < MAX_DEBRIS; i++) debris[i] = new TGDebris(); } public void endGame() { gameState = END; endCounter = END_COUNT; } public void start() { if (loopThread == null) { loopThread = new Thread(this); loopThread.start(); } if (!loaded && loadThread == null) { loadThread = new Thread(this); loadThread.start(); } } public void stop() { if (loopThread != null) { loopThread.stop(); loopThread = null; } if (loadThread != null) { loadThread.stop(); loadThread = null; } } public void run() { long startTime; // Lower this thread's priority and get the current time. Thread.currentThread().setPriority(Thread.MIN_PRIORITY); startTime = System.currentTimeMillis(); // Run thread for loading sounds. if (!loaded && Thread.currentThread() == loadThread) { loadSounds(); loaded = true; loadThread.stop(); } // This is the main loop. while (Thread.currentThread() == loopThread) { // In playing or ending mode, update moving objects and advance blast // counter if active. if (!paused && (gameState == PLAY || gameState == END)) { TGObject.reset(); updateStars(); updateShips(); updateDebris(); if (blastCounter > 0) blastCounter--; } // In game ending mode, advance ending counter and start demo when it // hits zero. if (!paused && gameState == END && --endCounter <= 0) initDemo(); // In demo mode. if (!paused && gameState == DEMO) updateDemo(); // Update the screen and set the timer for the next loop. repaint(); try { startTime += DELAY; Thread.sleep(Math.max(0, startTime - System.currentTimeMillis())); } catch (InterruptedException e) { break; } } } public void loadSounds() { // Load all sound clips by playing and immediately stopping them. try { blastSound = getAudioClip(new URL(getCodeBase(), "blast.au")); clipTotal++; debrisSound = getAudioClip(new URL(getCodeBase(), "debris.au")); clipTotal++; explodeSound = getAudioClip(new URL(getCodeBase(), "explode.au")); clipTotal++; fireSound = getAudioClip(new URL(getCodeBase(), "fire.au")); clipTotal++; passingSound = getAudioClip(new URL(getCodeBase(), "passing.au")); clipTotal++; targetedSound = getAudioClip(new URL(getCodeBase(), "targeted.au")); clipTotal++; } catch (MalformedURLException e) {} try { blastSound.play(); blastSound.stop(); clipsLoaded++; repaint(); Thread.currentThread().sleep(DELAY); debrisSound.play(); debrisSound.stop(); clipsLoaded++; repaint(); Thread.currentThread().sleep(DELAY); explodeSound.play(); explodeSound.stop(); clipsLoaded++; repaint(); Thread.currentThread().sleep(DELAY); fireSound.play(); fireSound.stop(); clipsLoaded++; repaint(); Thread.currentThread().sleep(DELAY); passingSound.play(); passingSound.stop(); clipsLoaded++; repaint(); Thread.currentThread().sleep(DELAY); targetedSound.play(); targetedSound.stop(); clipsLoaded++; repaint(); Thread.currentThread().sleep(DELAY); } catch (InterruptedException e) {} } public void initStar(int i) { starsAngle[i] = Math.random() * 2 * Math.PI; starsRadius[i] = Math.max(scrnWidth, scrnHeight) / 2 + Math.random() * Math.min(scrnWidth, scrnHeight) / 4; starsMinRadius[i] = starsRadius[i] / (Math.random() * 9 + 1); } public void updateStars() { int i; // Move stars inward. for (i = 0; i < NUM_STARS; i++) { if((starsRadius[i] *= .95) < starsMinRadius[i]) initStar(i); starsX[i] = (int) (scrnWidth / 2 + starsRadius[i] * Math.sin(starsAngle[i])); starsY[i] = (int) (scrnHeight / 2 + starsRadius[i] * Math.cos(starsAngle[i])); } } public void initShip(int i) { int j; double r, f; r = Math.random(); f = 0; j = -1; do { j++; f += typefrq[j]; } while (f < r && j < NUM_TYPES - 1); ships[i] = new TGShip(types[j]); ships[i].type = j; ships[i].dz = START_DISTANCE; ships[i].ax = 0; ships[i].ay = 0; ships[i].az = 0; ships[i].color = (int) (Math.random() * NUM_COLORS); ships[i].speed = MIN_SPEED + Math.random() * (MAX_SPEED - MIN_SPEED); ships[i].speed *= typespd[ships[i].type]; ships[i].a1 = Math.random() * 2 * Math.PI; ships[i].a2 = Math.random() * 2 * Math.PI; ships[i].c1 = Math.random() * MAX_TURN - 2 * MAX_TURN; ships[i].c1 *= typetrn[ships[i].type]; ships[i].c2 = Math.random() * MAX_TURN - 2 * MAX_TURN; ships[i].c2 *= typetrn[ships[i].type]; ships[i].dist1 = ships[i].dz; ships[i].dist2 = ships[i].dz; ships[i].exploding = false; } public void updateShips() { int i; double sx, sy; for (i = 0; i < NUM_SHIPS; i++) { // If exploding, decrement explosion counter. Generate new ship at end. if (ships[i].exploding) { if (ships[i].counter == EXPLODE_COUNT) { if (sound) explodeSound.play(); explodeShip(i); score += typepts[ships[i].type]; if (score > highScore) highScore = score; } if (--ships[i].counter <= 0) initShip(i); } // Otherwise, move the ship. else { ships[i].a1 += ships[i].c1; if (ships[i].a1 > 2 * Math.PI) ships[i].a1 -= 2 * Math.PI; ships[i].a2 += ships[i].c2; if (ships[i].a2 > 2 * Math.PI) ships[i].a2 -= 2 * Math.PI; sx = Math.sin(ships[i].a1); sy = Math.sin(ships[i].a2); ships[i].dx = MAX_HORIZONTAL * sx; ships[i].dy = MAX_VERTICAL * sy; ships[i].ax = -sy * Math.PI / 6; ships[i].az = sx * Math.PI / 6; ships[i].dz += ships[i].speed; ships[i].dist2 = ships[i].dist1; ships[i].dist1 = Math.sqrt(Math.pow(ships[i].dx, 2) + Math.pow(ships[i].dy, 2) + Math.pow(ships[i].dz, 2)); // When close, play sound effect. if (ships[i].dz > SOUND_DISTANCE && !ships[i].sounded) { ships[i].sounded = true; if (sound) passingSound.play(); } // If too close, let it take a shot then remove and generate a new // ship. if (ships[i].dz > END_DISTANCE) { if (gameState != END && Math.random() < .5) { if (sound) blastSound.play(); shields -= typedmg[ships[i].type]; if (shields < 0) { shields = 0; endGame(); } blastCounter = BLAST_COUNT; } if (gameState == PLAY) initShip(i); else ships[i] = new TGShip(); } // Render the ship and add it to the master list. ships[i].render(viewpoint, MAG, scrnWidth / 2, scrnHeight / 2); } } } public void explodeShip(int i) { int j; // Create debris from the ship. for (j = 0; j < ships[i].faces.size(); j += 2) { // Copy ship face. debris[debrisIndex] = new TGDebris(ships[i], j); // Set random rotation and motion. debris[debrisIndex].rx = Math.random() * MAX_SPIN - 2 * MAX_SPIN; debris[debrisIndex].ry = Math.random() * MAX_SPIN - 2 * MAX_SPIN; debris[debrisIndex].rz = Math.random() * MAX_SPIN - 2 * MAX_SPIN; debris[debrisIndex].mx = Math.random() * ships[i].speed / 2 - ships[i].speed / 4; debris[debrisIndex].my = Math.random() * ships[i].speed / 2 - ships[i].speed / 4; debris[debrisIndex].mz = ships[i].speed + Math.random() * ships[i].speed / 2 - ships[i].speed / 4; // Set counter. debris[debrisIndex].counter = DEBRIS_COUNT; debris[debrisIndex].active = true; // Advance array index for next piece. if (++debrisIndex >= MAX_DEBRIS) debrisIndex = 0; } } public void updateDebris() { int i; // Move any active debris. for (i = 0; i < MAX_DEBRIS; i++) if (debris[i].active) { debris[i].ax += debris[i].rx; if (debris[i].ax > 2 * Math.PI) debris[i].ax -= 2 * Math.PI; debris[i].ay += debris[i].ry; if (debris[i].ay > 2 * Math.PI) debris[i].ay -= 2 * Math.PI; debris[i].az += debris[i].rz; if (debris[i].az > 2 * Math.PI) debris[i].az -= 2 * Math.PI; debris[i].dx += debris[i].mx; debris[i].dy += debris[i].my; debris[i].dz += debris[i].mz; // If too close, remove it. Otherwise render it. if (debris[i].dz > END_DISTANCE) { if (sound && Math.abs(debris[i].dx) < MAG / 2 && Math.abs(debris[i].dy) < MAG / 2) debrisSound.play(); debris[i].active = false; } else debris[i].render(viewpoint, MAG, scrnWidth / 2, scrnHeight / 2); // Advance the counter. if (--debris[i].counter <= 0) debris[i].active = false; } } public void initDemo() { int i; gameState = DEMO; demoIndex = 0; for (i = 1; i < NUM_SHIPS; i++) ships[i] = new TGShip(); setDemo(); } public void setDemo() { ships[0] = new TGShip(types[demoIndex]); ships[0].ax = 0; ships[0].ay = 0; ships[0].a1 = Math.PI / 200; ships[0].a2 = Math.PI / 50; ships[0].dz = START_DISTANCE; ships[0].color = demoIndex; ships[0].speed = MAX_SPEED; demoCounter = DEMO_COUNT; } public void updateDemo() { if (--demoCounter <= 0) { if (++demoIndex >= NUM_TYPES) demoIndex = 0; setDemo(); } ships[0].ax += ships[0].a1; if (Math.abs(ships[0].ax) > Math.PI / 6) ships[0].a1 = -ships[0].a1; ships[0].ay += ships[0].a2; if (ships[0].ay > 2 * Math.PI) ships[0].ay -= 2 * Math.PI; if (ships[0].dz < DEMO_DISTANCE) ships[0].dz += ships[0].speed; else ships[0].speed = 0; ships[0].dist2 = ships[0].dist1; ships[0].dist1 = Math.abs(ships[0].dz); TGObject.reset(); ships[0].render(viewpoint, MAG, scrnWidth / 2, scrnHeight / 2); } public void keyPressed(KeyEvent e) { char c; // Allow upper or lower case characters for keys. c = Character.toLowerCase(e.getKeyChar()); // 'M' key: toggle sound. if (c == 'm') { if (sound) { blastSound.stop(); debrisSound.stop(); explodeSound.stop(); fireSound.stop(); passingSound.stop(); targetedSound.stop(); } sound = !sound; } // 'P' key: toggle pause mode. if (c == 'p') { if (!paused && sound) { blastSound.stop(); debrisSound.stop(); explodeSound.stop(); fireSound.stop(); passingSound.stop(); targetedSound.stop(); } paused = !paused; firing = false; } // 'S' key: start game if not already in progress. if (loaded && c == 's' && gameState != PLAY) initGame(); // 'HOME' key: jump to web site (undocumented). if (e.getKeyCode() == KeyEvent.VK_HOME) try { getAppletContext().showDocument(new URL(copyLink)); } catch (Exception excp) {} } public void keyReleased(KeyEvent e) {} public void keyTyped(KeyEvent e) {} public void mouseMoved(MouseEvent e) { // Get screen coordinates of mouse pointer. mx = e.getX(); my = e.getY(); } public void mousePressed(MouseEvent e) { // Set firing flag. if (!paused && gameState == PLAY) { mx = e.getX(); my = e.getY(); firing = true; } // Start game if not already in progress. if (loaded && gameState == DEMO) initGame(); } public void mouseClicked(MouseEvent e) {} public void mouseDragged(MouseEvent e) {} public void mouseEntered(MouseEvent e) {} public void mouseExited(MouseEvent e) {} public void mouseReleased(MouseEvent e) {} public Color getColor(int index, int c) { if (blastCounter > 0) { c = 255 - c; return new Color(c, c, c); } if (index == 0) return new Color(c, c / 2, c / 2); if (index == 1) return new Color(c / 2, c, c / 2); if (index == 2) return new Color(c / 2, c / 2, c); if (index == 3) return new Color(0, c, c); if (index == 4) return new Color(c, 0, c); if (index == 5) return new Color(c, c, 0); return Color.black; } public void update(Graphics g) { paint(g); } public void paint(Graphics g) { int i, j; int c; int xtrans = 0, ytrans = 0; int range = 0, speed = 0; int x, y; int w, h; String s; TGShip shape, victim; Double angle; Polygon polygon; Dimension d; boolean targeted; d = getSize(); scrnWidth = d.width; scrnHeight = d.height; // Create the off screen graphics context, if no good one exists. if (offGraphics == null || d.width != offDimension.width || d.height != offDimension.height) { offDimension = d; offImage = createImage(d.width, d.height); offGraphics = offImage.getGraphics(); } // Fill in background. offGraphics.setColor(Color.black); if (blastCounter > 0) offGraphics.setColor(Color.white); offGraphics.fillRect(0, 0, d.width, d.height); // Shake up view if being blasted. if (blastCounter > 0 && !paused) { xtrans = (int) (Math.random() * 2 * MAX_SHAKE - MAX_SHAKE); ytrans = (int) (Math.random() * 2 * MAX_SHAKE - MAX_SHAKE); } offGraphics.translate(xtrans, ytrans); // Draw stars. offGraphics.setColor(Color.white); if (blastCounter > 0) offGraphics.setColor(Color.black); for (i = 0; i < NUM_STARS; i++) offGraphics.drawLine(starsX[i], starsY[i], starsX[i], starsY[i]); // Draw all active ships and debris. targeted = false; victim = null; for (i = 0; i < TGObject.list.size(); i++) { shape = (TGShip) TGObject.list.elementAt(i); for (j = 0; j < shape.polygons.size(); j++) { polygon = (Polygon) shape.polygons.elementAt(j); angle = (Double) shape.angles.elementAt(j); c = 255 - (int) (255 * (angle.doubleValue() / (Math.PI / 2))); offGraphics.setColor(getColor(shape.color, c)); offGraphics.fillPolygon(polygon); // If this is a ship, is it targeted and are we firing? (If ships // overlap, only the last, or nearest, should be hit so save the // index.) if (!(shape instanceof TGDebris) && polygon.contains(mx, my)) { targeted = true; range = (int) shape.dist1; speed = (int) ((shape.dist2 - shape.dist1) * 1000 / DELAY); if (firing) victim = shape; } } } // If a ship was targeted while firing, set it up for explosion. if (victim != null) { victim.exploding = true; victim.counter = EXPLODE_COUNT; } // Draw beams if firing. if (firing && !paused) { if (sound) fireSound.play(); firing = false; for (i = 0; i < 3; i++) { c = 255 - i * 64; offGraphics.setColor(new Color(c / 2, c, c / 2)); if (blastCounter > 0) offGraphics.setColor(Color.gray); offGraphics.drawLine(0, d.height - 3 + i, mx, my); offGraphics.drawLine(0, d.height - 3 - i, mx, my); offGraphics.drawLine(d.width, d.height - 3 + i, mx, my); offGraphics.drawLine(d.width, d.height - 3 - i, mx, my); } } // Highlight target if in sights. if (targeted && gameState != END) { if (loaded && !paused && sound) targetedSound.play(); offGraphics.setColor(Color.yellow); if (blastCounter > 0) offGraphics.setColor(Color.darkGray); x = TARGET_WIDTH / 2; y = TARGET_HEIGHT / 2; w = TARGET_WIDTH / 4; h = TARGET_HEIGHT / 4; offGraphics.drawLine(mx - x, my - y, mx - x + w, my - y); offGraphics.drawLine(mx + x, my - y, mx + x - w, my - y); offGraphics.drawLine(mx - x, my - y, mx - x, my - y + h); offGraphics.drawLine(mx + x, my - y, mx + x, my - y + h); offGraphics.drawLine(mx - x, my + y, mx - x, my + y - h); offGraphics.drawLine(mx + x, my + y, mx + x, my + y - h); offGraphics.drawLine(mx - x, my + y, mx - x + w, my + y); offGraphics.drawLine(mx + x, my + y, mx + x - w, my + y); offGraphics.setFont(smallFont); s = range + "m"; offGraphics.drawString(s, mx + x + smallFontWidth, my); s = speed + "mps"; offGraphics.drawString(s, mx - x - (smallFm.stringWidth(s) + smallFontWidth), my); } // Display score, messages. offGraphics.setFont(font); offGraphics.setColor(Color.green); if (blastCounter > 0) offGraphics.setColor(Color.darkGray); s = "Score: " + score; offGraphics.drawString(s, fontWidth, fontHeight); s = "High: " + highScore; offGraphics.drawString(s, d.width - (fontWidth + fm.stringWidth(s)), fontHeight); s = "Shields: "; offGraphics.drawString(s, fontWidth, d.height - fontHeight); x = fm.stringWidth(s) + fontWidth; y = d.height - 3 * fontHeight / 2; offGraphics.drawRect(x, y, 2 * SHIELD_START, fontHeight / 2); offGraphics.fillRect(x, y, 2 * shields, fontHeight / 2); if (!sound && !paused && gameState != DEMO) { s = "Muted"; offGraphics.drawString(s, d.width - (fontWidth + fm.stringWidth(s)), d.height - fontHeight); } if (paused && gameState != DEMO) { s = "Paused"; offGraphics.drawString(s, d.width - (fontWidth + fm.stringWidth(s)), d.height - fontHeight); } offGraphics.setColor(Color.white); if (gameState == END) { s = "Game Over"; offGraphics.drawString(s, (d.width - fm.stringWidth(s)) / 2, (d.height - fontHeight) / 2); } if (gameState == DEMO) { s = copyName; offGraphics.drawString(s, (d.width - fm.stringWidth(s)) / 2, fontHeight); s = copyVers; offGraphics.drawString(s, (d.width - fm.stringWidth(s)) / 2, 2 * fontHeight); s = copyInfo; offGraphics.drawString(s, (d.width - fm.stringWidth(s)) / 2, 4 * fontHeight); s = copyLink; offGraphics.drawString(s, (d.width - fm.stringWidth(s)) / 2, 5 * fontHeight); // Display loading message. if (!loaded) { s = "Loading sounds..."; w = 4 * fontWidth + fm.stringWidth(s); h = fontHeight; x = (d.width - w) / 2; y = (d.height - h) / 2 - fm.getMaxAscent(); offGraphics.setColor(Color.black); offGraphics.fillRect(x, y, w, h); offGraphics.setColor(Color.blue); if (clipTotal > 0) offGraphics.fillRect(x, y, (int) (w * clipsLoaded / clipTotal), h); offGraphics.setColor(Color.white); offGraphics.drawRect(x, y, w, h); offGraphics.drawString(s, x + 2 * fontWidth, y + fm.getMaxAscent()); } else { offGraphics.setColor(Color.green); s = "'S' - Start 'P' - Pause/Resume 'M' - Mute/Sound"; offGraphics.drawString(s, d.width - (fontWidth + fm.stringWidth(s)), d.height - fontHeight); } offGraphics.setColor(Color.green); offGraphics.setFont(smallFont); if (targeted) { offGraphics.setColor(Color.yellow); s = "Target box displays craft's closing speed and range."; offGraphics.drawString(s, (d.width - smallFm.stringWidth(s)) / 2, 6 * fontHeight); } offGraphics.setColor(Color.red); s = typenme[demoIndex]; offGraphics.drawString(s, d.width / 2 - (smallFm.stringWidth(s) + fontWidth), d.height - 4 * fontHeight); s = "Maximum speed: " + (MAX_SPEED * typespd[demoIndex] * 1000 / DELAY) + "mps"; offGraphics.drawString(s, d.width / 2 + smallFontWidth, d.height - 4 * fontHeight); s = "Weapon strength: " + typedmg[demoIndex]; offGraphics.drawString(s, d.width / 2 - (smallFm.stringWidth(s) + fontWidth), d.height - 3 * fontHeight); s = "Point value: " + typepts[demoIndex]; offGraphics.drawString(s, d.width / 2 + smallFontWidth, d.height - 3 * fontHeight); } // Copy the off screen buffer to the screen. offGraphics.translate(-xtrans, -ytrans); g.drawImage(offImage, 0, 0, this); } }