// Abstract superclass for all physical objects. Encapsulates // appearance, position, mass. package SpaceWar; import java.awt.*; import java.awt.geom.*; abstract class SpaceObject implements Cloneable { ////////////////////////////////////////////////////////////////////// // // Constants protected static final int NonexistentState = -1; protected static final int NormalState = 0; protected static final int FirstExplosionState = 1; protected static final int NumberOfExplosionStates = 8; protected static final int ExplosionStatesPerColor = 4; ////////////////////////////////////////////////////////////////////// // // Parameters protected static int ExplosionGrowthRate = 1; ////////////////////////////////////////////////////////////////////// // // Instance variables // Identifying characteristics. double mass; // Mass of object. double radius; // Non-exploding radius of object. Color bodyColor; // Body color. Color trimColor; // "Trim" (e.g., engine) color. Color speedBlurColor; // Color of speed-blur display. Color engineFlameBlurColor = Color.orange.darker().darker(); // Color of engine-flame speed-blur display int explosionState; // -1 = nonexistent. 0 = not exploding. // Otherwise, holds the number of timesteps that // the explosion has been ongoing. // Motion-related. double angle = 0.; // Angle in radians (-pi:pi) double oldAngle = 0.; double angularVelocity = 0.; // Angular velocity in radians per unit time. Point2D.Double position; Point2D.Double velocity; Point2D.Double oldVelocity; Point2D.Double acceleration; Point2D.Double oldPosition; double thrust = 0.; boolean discontinuousMotion = true; Universe universe; // Display-related. SpacePoint displayPosition; SpacePoint oldDisplayPosition; SpacePoint currentPoint; ////////////////////////////////////////////////////////////////////// // // Constructors. public SpaceObject() { explosionState = NormalState; position = new Point2D.Double(0., 0.); velocity = new Point2D.Double(0., 0.); oldVelocity = new Point2D.Double(0., 0.); acceleration = new Point2D.Double(0., 0.); oldPosition = new Point2D.Double(0., 0.); displayPosition = new SpacePoint(0, 0); oldDisplayPosition = new SpacePoint(0, 0); currentPoint = new SpacePoint(0, 0); } // Copy state from this SpaceObject to the specified one. // This is used to keep the mementos needed to track old display // state. public void copyTo(SpaceObject so) { SpacePoint sp; copyToLaunch(so); so.setMass(mass); so.setRadius(radius); so.setThrust(thrust); so.setDiscontinuousMotion(discontinuousMotion); sp = so.getDisplayPosition(); sp.x = displayPosition.x; sp.y = displayPosition.y; } // This copies the launch-pertinent information to the specified // object from the receiver. public void copyToLaunch(SpaceObject so) { Point2D.Double p; SpacePoint sp; so.setBodyColor(bodyColor); so.setTrimColor(trimColor); so.setExplosionState(explosionState); so.setAngle(angle); so.setAngularVelocity(angularVelocity); p = so.getPosition(); p.setLocation(position.getX(), position.getY()); p = so.getVelocity(); p.setLocation(velocity.getX(), velocity.getY()); sp = so.getOldDisplayPosition(); sp.x = displayPosition.x; sp.y = displayPosition.y; so.setDiscontinuousMotion(true); } // Create a clone of this object. This is a deep copy. abstract public SpaceObject cloneMe(); ////////////////////////////////////////////////////////////////////// // // Get-set methods. // Get the mass. public double getMass() { return (mass); } // Set the mass. public void setMass(double newMass) { mass = newMass; } // Get the current radius, allowing for expansion due to explosions. public double getEffectiveRadius() { return (radius + ExplosionGrowthRate * explosionState); } // Get the current raw radius. public double getRadius() { return (radius); } // Set the underlying radius (pre-explosion). public void setRadius(double newRadius) { radius = newRadius; } // Get the current body color. public Color getBodyColor() { return (bodyColor); } // Set the body color. public void setBodyColor(Color newColor) { bodyColor = newColor; if (bodyColor != null) { setSpeedBlurColor(newColor.darker().darker()); } } // Get the current trim color. public Color getTrimColor() { return (trimColor); } // Set the trim color. public void setTrimColor(Color newColor) { trimColor = newColor; } // Get the current speed-blur color. public Color getSpeedBlurColor() { return (speedBlurColor); } // Set the speed-blur color. public void setSpeedBlurColor(Color newColor) { speedBlurColor = newColor; } // Get current angle. public double getAngle() { return (angle); } // Set current angle. public void setAngle(double newAngle) { angle = newAngle; } // Get current angular velocity. public double getAngularVelocity() { return (angularVelocity); } // Set current angular velocity. public void setAngularVelocity(double newAngularVelocity) { angularVelocity = newAngularVelocity; } // Get current explosion state. public int getExplosionState() { return (explosionState); } // Set current explosion state. public void setExplosionState(int newExplosionState) { explosionState = newExplosionState; } // Get current position. public Point2D.Double getPosition() { return (position); } // Set current position to the specified Point2D.Double. public void setPosition(Point2D.Double newPosition) { position.setLocation(newPosition.getX(), newPosition.getY()); } // Set current position to the specified (x, y) coordinates. public void setPosition(double x, double y) { position.setLocation(x, y); } // Get current velocity. public Point2D.Double getVelocity() { return (velocity); } // Set current velocity. public void setVelocity(double x, double y) { velocity.setLocation(x, y); } // Get current acceleration. public Point2D.Double getAcceleration() { return (acceleration); } // Get old Position value. public Point2D.Double getOldPosition() { return (oldPosition); } // Get current thrust. public double getThrust() { return (thrust); } // Set current thrust. public void setThrust(double newThrust) { thrust = newThrust; } // Get discontinuous-motion state. public boolean getDiscontinuousMotion() { return (discontinuousMotion); } // Get current universe that this SpaceObject inhabits public Universe getUniverse() { return (universe); } // Set current universe that this SpaceObject inhabits. // This is currently used only when creating new SpaceObjects, // but one could imagine implementing a wormhole between a pair // of alternate Universes. ;-) public void setUniverse(Universe newUniverse) { universe = newUniverse; } // Set discontinuous motion flag. public void setDiscontinuousMotion(boolean newDM) { discontinuousMotion = newDM; } // Get display position (position rounded to pixels). public SpacePoint getDisplayPosition() { return (displayPosition); } // Get old display position (old position rounded to pixels). public SpacePoint getOldDisplayPosition() { return (oldDisplayPosition); } ////////////////////////////////////////////////////////////////////// // // State-control methods. // Start this object exploding. If otherObject is null, then this // is a user-initiated explosion. Otherwise, the subclass object // may override this method to check to see if exploding is really // warrented. For example, a sun should not explode just because a // ship ran into it. However, if the subclass object decides that // exploding is really the right thing to do, it should invoke this // method to properly initiate the explosion. public void startExploding(SpaceObject otherObject) { if (amOutOfControl()) { return; } setBodyColor(Color.yellow); explosionState = FirstExplosionState; thrust = 0.; } // Marks an object nonexistent. The universe object will react // by discarding it. public void stopExisting() { explosionState = NonexistentState; } ////////////////////////////////////////////////////////////////////// // // State-inquiry methods. // Return true if this object no longer exists. public boolean amDead() { return (explosionState == NonexistentState); } // Returns true if this object is currently exploding. public boolean amExploding() { return (explosionState > NormalState); } // Returns true if this object is not in its normal, controllable // state. public boolean amOutOfControl() { return (explosionState != NormalState); } // Returns true if this objects "eats" eaten objects. public boolean amEatingObject() { return (false); } // Returns true if this objects "is eaten" by eating objects. public boolean amEatenObject() { return (false); } // Returns true if this object is moving fast enough to warrant // painting speed blur for it, in other words, if it moves more // than its radius. public boolean amInNeedOfSpeedBlur(SpaceGraphics g) { double x = velocity.getX(); double y = velocity.getY(); if (discontinuousMotion) { return (false); } return (x * x + y * y > radius * radius); } ////////////////////////////////////////////////////////////////////// // // Update methods. // Initialize acceleration from thrust and current angle. public void initializeAcceleration() { double a; a = thrust / mass; acceleration.setLocation(a * Math.cos(angle), a * Math.sin(angle)); } // Capture old state in memento object, and update state. // Return false if object no longer exists. public boolean updateState() { updateExplosion(); updateAngle(); return (!amDead()); } // Update angle based on current angular velocity. public void updateAngle() { angle += angularVelocity; } // Update explosion state; protected void updateExplosion() { if (explosionState >= NumberOfExplosionStates) { stopExisting(); } else if (explosionState >= FirstExplosionState) { explosionState++; if ((explosionState % ExplosionStatesPerColor) == 0) { bodyColor = bodyColor.darker(); } } } ////////////////////////////////////////////////////////////////////// // // Paint methods. // Main paint method. public void paint(SpaceGraphics g) { int diameter; double effectiveRadius = getEffectiveRadius(); // Update display position. displayPosition.setLocation((int)position.getX(), (int)position.getY()); // Draw speed blur. if (amInNeedOfSpeedBlur(g)) { g.setColor(speedBlurColor); g.circleBlur(displayPosition, effectiveRadius, oldDisplayPosition, effectiveRadius); g.fillOval(oldDisplayPosition.x, oldDisplayPosition.y, 2 * (int)effectiveRadius, 2 * (int)effectiveRadius); } // Invoke method to paint this object. if (explosionState == NormalState) { // Draw speed blur for engine flame. if (amInNeedOfSpeedBlur(g)) { paintEngineFlameBlur(g); } // Draw body of object. diameter = 2 * (int)radius; g.setColor(bodyColor); g.fillOval(displayPosition.x, displayPosition.y, diameter, diameter); // Paint engine flame (if any). If you want your // SpaceObject to have non-zero thrust, but no engine // flame, you need to override paintEngineFlame()... paintEngineFlame(g); // Draw trim (if any). paintTrim(g); } else { paintExplosion(g); } // Track old position, convert new position to int. // Tracking the old position allows us to simulate // speed blur. oldAngle = angle; oldVelocity.setLocation(velocity.getX(), velocity.getY()); oldDisplayPosition.setLocation(displayPosition.getX(), displayPosition.getY()); } // Paint an explosion. public void paintExplosion(SpaceGraphics g) { int explosionDiameter = 2 * (int)getEffectiveRadius(); g.setColor(bodyColor); g.fillOval(displayPosition.x, displayPosition.y, explosionDiameter, explosionDiameter); } // Paint an engine flame. One size fits all. ;-) public void paintEngineFlame(SpaceGraphics g) { if (thrust == 0.) { return; } double flameAngle = angle + Math.PI; g.setColor(Color.red); g.fillOval(displayPosition, radius + 9., flameAngle, 8, 8); g.setColor(Color.orange); g.fillOval(displayPosition, radius + 6., flameAngle, 6, 6); g.setColor(Color.yellow); g.fillOval(displayPosition, radius + 4., flameAngle, 4, 4); g.fillOval(displayPosition, radius + 2., flameAngle, 2, 2); } // Paint an engine-flame speed blur. public void paintEngineFlameBlur(SpaceGraphics g) { if (thrust == 0.) { return; } g.setColor(engineFlameBlurColor); g.polarBlur(displayPosition, radius + 1, radius + 13, angle + Math.PI, oldDisplayPosition, radius + 1, radius + 13, oldAngle + Math.PI); } // Delegate painting trim to subclass. public abstract void paintTrim(SpaceGraphics g); }