Development Class Java

/*
DEVELOPING GAME IN JAVA 
Caracteristiques
Editeur : NEW RIDERS 
Auteur : BRACKEEN 
Parution : 09 2003 
Pages : 972 
Isbn : 1-59273-005-1 
Reliure : Paperback 
Disponibilite : Disponible a la librairie 
*/
         
import java.awt.AWTException;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.DisplayMode;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Point;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
/**
 * The Filter3dTest class demonstrates the Filter3d functionality. A fly buzzes
 * around the listener, and the closer the fly is, the louder it's heard.
 * 
 * @see Filter3d
 * @see SimpleSoundPlayer
 */
public class Filter3dTest extends GameCore {
  public static void main(String[] args) {
    new Filter3dTest().run();
  }
  private Sprite fly;
  private Sprite listener;
  private InputManager inputManager;
  private GameAction exit;
  private SimpleSoundPlayer bzzSound;
  private InputStream bzzSoundStream;
  public void init() {
    super.init();
    // set up input manager
    exit = new GameAction("exit", GameAction.DETECT_INITAL_PRESS_ONLY);
    inputManager = new InputManager(screen.getFullScreenWindow());
    inputManager.mapToKey(exit, KeyEvent.VK_ESCAPE);
    inputManager.setCursor(InputManager.INVISIBLE_CURSOR);
    createSprites();
    // load the sound
    bzzSound = new SimpleSoundPlayer("../sounds/fly-bzz.wav");
    // create the 3d filter
    Filter3d filter = new Filter3d(fly, listener, screen.getHeight());
    // create the filtered sound stream
    bzzSoundStream = new FilteredSoundStream(new LoopingByteInputStream(
        bzzSound.getSamples()), filter);
    // play the sound in a separate thread
    new Thread() {
      public void run() {
        bzzSound.play(bzzSoundStream);
      }
    }.start();
  }
  /**
   * Loads images and creates sprites.
   */
  private void createSprites() {
    // load images
    Image fly1 = loadImage("../images/fly1.png");
    Image fly2 = loadImage("../images/fly2.png");
    Image fly3 = loadImage("../images/fly3.png");
    Image ear = loadImage("../images/ear.png");
    // create "fly" sprite
    Animation anim = new Animation();
    anim.addFrame(fly1, 50);
    anim.addFrame(fly2, 50);
    anim.addFrame(fly3, 50);
    anim.addFrame(fly2, 50);
    fly = new Sprite(anim);
    // create the listener sprite
    anim = new Animation();
    anim.addFrame(ear, 0);
    listener = new Sprite(anim);
    listener.setX((screen.getWidth() - listener.getWidth()) / 2);
    listener.setY((screen.getHeight() - listener.getHeight()) / 2);
  }
  public void update(long elapsedTime) {
    if (exit.isPressed()) {
      stop();
    } else {
      listener.update(elapsedTime);
      fly.update(elapsedTime);
      fly.setX(inputManager.getMouseX());
      fly.setY(inputManager.getMouseY());
    }
  }
  public void stop() {
    super.stop();
    // stop the bzz sound
    try {
      bzzSoundStream.close();
    } catch (IOException ex) {
    }
  }
  public void draw(Graphics2D g) {
    // draw background
    g.setColor(new Color(0x33cc33));
    g.fillRect(0, 0, screen.getWidth(), screen.getHeight());
    // draw listener
    g.drawImage(listener.getImage(), Math.round(listener.getX()), Math
        .round(listener.getY()), null);
    // draw fly
    g.drawImage(fly.getImage(), Math.round(fly.getX()), Math.round(fly
        .getY()), null);
  }
}
/**
 * The SimpleSoundPlayer encapsulates a sound that can be opened from the file
 * system and later played.
 */
class SimpleSoundPlayer {
  public static void main(String[] args) {
    // load a sound
    SimpleSoundPlayer sound = new SimpleSoundPlayer("../sounds/voice.wav");
    // create the stream to play
    InputStream stream = new ByteArrayInputStream(sound.getSamples());
    // play the sound
    sound.play(stream);
    // exit
    System.exit(0);
  }
  private AudioFormat format;
  private byte[] samples;
  /**
   * Opens a sound from a file.
   */
  public SimpleSoundPlayer(String filename) {
    try {
      // open the audio input stream
      AudioInputStream stream = AudioSystem.getAudioInputStream(new File(
          filename));
      format = stream.getFormat();
      // get the audio samples
      samples = getSamples(stream);
    } catch (UnsupportedAudioFileException ex) {
      ex.printStackTrace();
    } catch (IOException ex) {
      ex.printStackTrace();
    }
  }
  /**
   * Gets the samples of this sound as a byte array.
   */
  public byte[] getSamples() {
    return samples;
  }
  /**
   * Gets the samples from an AudioInputStream as an array of bytes.
   */
  private byte[] getSamples(AudioInputStream audioStream) {
    // get the number of bytes to read
    int length = (int) (audioStream.getFrameLength() * format
        .getFrameSize());
    // read the entire stream
    byte[] samples = new byte[length];
    DataInputStream is = new DataInputStream(audioStream);
    try {
      is.readFully(samples);
    } catch (IOException ex) {
      ex.printStackTrace();
    }
    // return the samples
    return samples;
  }
  /**
   * Plays a stream. This method blocks (doesn't return) until the sound is
   * finished playing.
   */
  public void play(InputStream source) {
    // use a short, 100ms (1/10th sec) buffer for real-time
    // change to the sound stream
    int bufferSize = format.getFrameSize()
        * Math.round(format.getSampleRate() / 10);
    byte[] buffer = new byte[bufferSize];
    // create a line to play to
    SourceDataLine line;
    try {
      DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
      line = (SourceDataLine) AudioSystem.getLine(info);
      line.open(format, bufferSize);
    } catch (LineUnavailableException ex) {
      ex.printStackTrace();
      return;
    }
    // start the line
    line.start();
    // copy data to the line
    try {
      int numBytesRead = 0;
      while (numBytesRead != -1) {
        numBytesRead = source.read(buffer, 0, buffer.length);
        if (numBytesRead != -1) {
          line.write(buffer, 0, numBytesRead);
        }
      }
    } catch (IOException ex) {
      ex.printStackTrace();
    }
    // wait until all data is played, then close the line
    line.drain();
    line.close();
  }
}
/**
 * The InputManager manages input of key and mouse events. Events are mapped to
 * GameActions.
 */
class InputManager implements KeyListener, MouseListener, MouseMotionListener,
    MouseWheelListener {
  /**
   * An invisible cursor.
   */
  public static final Cursor INVISIBLE_CURSOR = Toolkit.getDefaultToolkit()
      .createCustomCursor(Toolkit.getDefaultToolkit().getImage(""),
          new Point(0, 0), "invisible");
  // mouse codes
  public static final int MOUSE_MOVE_LEFT = 0;
  public static final int MOUSE_MOVE_RIGHT = 1;
  public static final int MOUSE_MOVE_UP = 2;
  public static final int MOUSE_MOVE_DOWN = 3;
  public static final int MOUSE_WHEEL_UP = 4;
  public static final int MOUSE_WHEEL_DOWN = 5;
  public static final int MOUSE_BUTTON_1 = 6;
  public static final int MOUSE_BUTTON_2 = 7;
  public static final int MOUSE_BUTTON_3 = 8;
  private static final int NUM_MOUSE_CODES = 9;
  // key codes are defined in java.awt.KeyEvent.
  // most of the codes (except for some rare ones like
  // "alt graph") are less than 600.
  private static final int NUM_KEY_CODES = 600;
  private GameAction[] keyActions = new GameAction[NUM_KEY_CODES];
  private GameAction[] mouseActions = new GameAction[NUM_MOUSE_CODES];
  private Point mouseLocation;
  private Point centerLocation;
  private Component comp;
  private Robot robot;
  private boolean isRecentering;
  /**
   * Creates a new InputManager that listens to input from the specified
   * component.
   */
  public InputManager(Component comp) {
    this.comp = comp;
    mouseLocation = new Point();
    centerLocation = new Point();
    // register key and mouse listeners
    comp.addKeyListener(this);
    comp.addMouseListener(this);
    comp.addMouseMotionListener(this);
    comp.addMouseWheelListener(this);
    // allow input of the TAB key and other keys normally
    // used for focus traversal
    comp.setFocusTraversalKeysEnabled(false);
  }
  /**
   * Sets the cursor on this InputManager's input component.
   */
  public void setCursor(Cursor cursor) {
    comp.setCursor(cursor);
  }
  /**
   * Sets whether realtive mouse mode is on or not. For relative mouse mode,
   * the mouse is "locked" in the center of the screen, and only the changed
   * in mouse movement is measured. In normal mode, the mouse is free to move
   * about the screen.
   */
  public void setRelativeMouseMode(boolean mode) {
    if (mode == isRelativeMouseMode()) {
      return;
    }
    if (mode) {
      try {
        robot = new Robot();
        recenterMouse();
      } catch (AWTException ex) {
        // couldn't create robot!
        robot = null;
      }
    } else {
      robot = null;
    }
  }
  /**
   * Returns whether or not relative mouse mode is on.
   */
  public boolean isRelativeMouseMode() {
    return (robot != null);
  }
  /**
   * Maps a GameAction to a specific key. The key codes are defined in
   * java.awt.KeyEvent. If the key already has a GameAction mapped to it, the
   * new GameAction overwrites it.
   */
  public void mapToKey(GameAction gameAction, int keyCode) {
    keyActions[keyCode] = gameAction;
  }
  /**
   * Maps a GameAction to a specific mouse action. The mouse codes are defined
   * herer in InputManager (MOUSE_MOVE_LEFT, MOUSE_BUTTON_1, etc). If the
   * mouse action already has a GameAction mapped to it, the new GameAction
   * overwrites it.
   */
  public void mapToMouse(GameAction gameAction, int mouseCode) {
    mouseActions[mouseCode] = gameAction;
  }
  /**
   * Clears all mapped keys and mouse actions to this GameAction.
   */
  public void clearMap(GameAction gameAction) {
    for (int i = 0; i < keyActions.length; i++) {
      if (keyActions[i] == gameAction) {
        keyActions[i] = null;
      }
    }
    for (int i = 0; i < mouseActions.length; i++) {
      if (mouseActions[i] == gameAction) {
        mouseActions[i] = null;
      }
    }
    gameAction.reset();
  }
  /**
   * Gets a List of names of the keys and mouse actions mapped to this
   * GameAction. Each entry in the List is a String.
   */
  public List getMaps(GameAction gameCode) {
    ArrayList list = new ArrayList();
    for (int i = 0; i < keyActions.length; i++) {
      if (keyActions[i] == gameCode) {
        list.add(getKeyName(i));
      }
    }
    for (int i = 0; i < mouseActions.length; i++) {
      if (mouseActions[i] == gameCode) {
        list.add(getMouseName(i));
      }
    }
    return list;
  }
  /**
   * Resets all GameActions so they appear like they haven't been pressed.
   */
  public void resetAllGameActions() {
    for (int i = 0; i < keyActions.length; i++) {
      if (keyActions[i] != null) {
        keyActions[i].reset();
      }
    }
    for (int i = 0; i < mouseActions.length; i++) {
      if (mouseActions[i] != null) {
        mouseActions[i].reset();
      }
    }
  }
  /**
   * Gets the name of a key code.
   */
  public static String getKeyName(int keyCode) {
    return KeyEvent.getKeyText(keyCode);
  }
  /**
   * Gets the name of a mouse code.
   */
  public static String getMouseName(int mouseCode) {
    switch (mouseCode) {
    case MOUSE_MOVE_LEFT:
      return "Mouse Left";
    case MOUSE_MOVE_RIGHT:
      return "Mouse Right";
    case MOUSE_MOVE_UP:
      return "Mouse Up";
    case MOUSE_MOVE_DOWN:
      return "Mouse Down";
    case MOUSE_WHEEL_UP:
      return "Mouse Wheel Up";
    case MOUSE_WHEEL_DOWN:
      return "Mouse Wheel Down";
    case MOUSE_BUTTON_1:
      return "Mouse Button 1";
    case MOUSE_BUTTON_2:
      return "Mouse Button 2";
    case MOUSE_BUTTON_3:
      return "Mouse Button 3";
    default:
      return "Unknown mouse code " + mouseCode;
    }
  }
  /**
   * Gets the x position of the mouse.
   */
  public int getMouseX() {
    return mouseLocation.x;
  }
  /**
   * Gets the y position of the mouse.
   */
  public int getMouseY() {
    return mouseLocation.y;
  }
  /**
   * Uses the Robot class to try to postion the mouse in the center of the
   * screen.
   * 


   * Note that use of the Robot class may not be available on all platforms.
   */
  private synchronized void recenterMouse() {
    if (robot != null && comp.isShowing()) {
      centerLocation.x = comp.getWidth() / 2;
      centerLocation.y = comp.getHeight() / 2;
      SwingUtilities.convertPointToScreen(centerLocation, comp);
      isRecentering = true;
      robot.mouseMove(centerLocation.x, centerLocation.y);
    }
  }
  private GameAction getKeyAction(KeyEvent e) {
    int keyCode = e.getKeyCode();
    if (keyCode < keyActions.length) {
      return keyActions[keyCode];
    } else {
      return null;
    }
  }
  /**
   * Gets the mouse code for the button specified in this MouseEvent.
   */
  public static int getMouseButtonCode(MouseEvent e) {
    switch (e.getButton()) {
    case MouseEvent.BUTTON1:
      return MOUSE_BUTTON_1;
    case MouseEvent.BUTTON2:
      return MOUSE_BUTTON_2;
    case MouseEvent.BUTTON3:
      return MOUSE_BUTTON_3;
    default:
      return -1;
    }
  }
  private GameAction getMouseButtonAction(MouseEvent e) {
    int mouseCode = getMouseButtonCode(e);
    if (mouseCode != -1) {
      return mouseActions[mouseCode];
    } else {
      return null;
    }
  }
  // from the KeyListener interface
  public void keyPressed(KeyEvent e) {
    GameAction gameAction = getKeyAction(e);
    if (gameAction != null) {
      gameAction.press();
    }
    // make sure the key isn't processed for anything else
    e.consume();
  }
  // from the KeyListener interface
  public void keyReleased(KeyEvent e) {
    GameAction gameAction = getKeyAction(e);
    if (gameAction != null) {
      gameAction.release();
    }
    // make sure the key isn't processed for anything else
    e.consume();
  }
  // from the KeyListener interface
  public void keyTyped(KeyEvent e) {
    // make sure the key isn't processed for anything else
    e.consume();
  }
  // from the MouseListener interface
  public void mousePressed(MouseEvent e) {
    GameAction gameAction = getMouseButtonAction(e);
    if (gameAction != null) {
      gameAction.press();
    }
  }
  // from the MouseListener interface
  public void mouseReleased(MouseEvent e) {
    GameAction gameAction = getMouseButtonAction(e);
    if (gameAction != null) {
      gameAction.release();
    }
  }
  // from the MouseListener interface
  public void mouseClicked(MouseEvent e) {
    // do nothing
  }
  // from the MouseListener interface
  public void mouseEntered(MouseEvent e) {
    mouseMoved(e);
  }
  // from the MouseListener interface
  public void mouseExited(MouseEvent e) {
    mouseMoved(e);
  }
  // from the MouseMotionListener interface
  public void mouseDragged(MouseEvent e) {
    mouseMoved(e);
  }
  // from the MouseMotionListener interface
  public synchronized void mouseMoved(MouseEvent e) {
    // this event is from re-centering the mouse - ignore it
    if (isRecentering && centerLocation.x == e.getX()
        && centerLocation.y == e.getY()) {
      isRecentering = false;
    } else {
      int dx = e.getX() - mouseLocation.x;
      int dy = e.getY() - mouseLocation.y;
      mouseHelper(MOUSE_MOVE_LEFT, MOUSE_MOVE_RIGHT, dx);
      mouseHelper(MOUSE_MOVE_UP, MOUSE_MOVE_DOWN, dy);
      if (isRelativeMouseMode()) {
        recenterMouse();
      }
    }
    mouseLocation.x = e.getX();
    mouseLocation.y = e.getY();
  }
  // from the MouseWheelListener interface
  public void mouseWheelMoved(MouseWheelEvent e) {
    mouseHelper(MOUSE_WHEEL_UP, MOUSE_WHEEL_DOWN, e.getWheelRotation());
  }
  private void mouseHelper(int codeNeg, int codePos, int amount) {
    GameAction gameAction;
    if (amount < 0) {
      gameAction = mouseActions[codeNeg];
    } else {
      gameAction = mouseActions[codePos];
    }
    if (gameAction != null) {
      gameAction.press(Math.abs(amount));
      gameAction.release();
    }
  }
}
/**
 * Simple abstract class used for testing. Subclasses should implement the
 * draw() method.
 */
abstract class GameCore {
  protected static final int FONT_SIZE = 24;
  private static final DisplayMode POSSIBLE_MODES[] = {
      new DisplayMode(800, 600, 32, 0), new DisplayMode(800, 600, 24, 0),
      new DisplayMode(800, 600, 16, 0), new DisplayMode(640, 480, 32, 0),
      new DisplayMode(640, 480, 24, 0), new DisplayMode(640, 480, 16, 0) };
  private boolean isRunning;
  protected ScreenManager screen;
  /**
   * Signals the game loop that it's time to quit
   */
  public void stop() {
    isRunning = false;
  }
  /**
   * Calls init() and gameLoop()
   */
  public void run() {
    try {
      init();
      gameLoop();
    } finally {
      screen.restoreScreen();
      lazilyExit();
    }
  }
  /**
   * Exits the VM from a daemon thread. The daemon thread waits 2 seconds then
   * calls System.exit(0). Since the VM should exit when only daemon threads
   * are running, this makes sure System.exit(0) is only called if neccesary.
   * It's neccesary if the Java Sound system is running.
   */
  public void lazilyExit() {
    Thread thread = new Thread() {
      public void run() {
        // first, wait for the VM exit on its own.
        try {
          Thread.sleep(2000);
        } catch (InterruptedException ex) {
        }
        // system is still running, so force an exit
        System.exit(0);
      }
    };
    thread.setDaemon(true);
    thread.start();
  }
  /**
   * Sets full screen mode and initiates and objects.
   */
  public void init() {
    screen = new ScreenManager();
    DisplayMode displayMode = screen
        .findFirstCompatibleMode(POSSIBLE_MODES);
    screen.setFullScreen(displayMode);
    Window window = screen.getFullScreenWindow();
    window.setFont(new Font("Dialog", Font.PLAIN, FONT_SIZE));
    window.setBackground(Color.blue);
    window.setForeground(Color.white);
    isRunning = true;
  }
  public Image loadImage(String fileName) {
    return new ImageIcon(fileName).getImage();
  }
  /**
   * Runs through the game loop until stop() is called.
   */
  public void gameLoop() {
    long startTime = System.currentTimeMillis();
    long currTime = startTime;
    while (isRunning) {
      long elapsedTime = System.currentTimeMillis() - currTime;
      currTime += elapsedTime;
      // update
      update(elapsedTime);
      // draw the screen
      Graphics2D g = screen.getGraphics();
      draw(g);
      g.dispose();
      screen.update();
      // take a nap
      try {
        Thread.sleep(20);
      } catch (InterruptedException ex) {
      }
    }
  }
  /**
   * Updates the state of the game/animation based on the amount of elapsed
   * time that has passed.
   */
  public void update(long elapsedTime) {
    // do nothing
  }
  /**
   * Draws to the screen. Subclasses must override this method.
   */
  public abstract void draw(Graphics2D g);
}
/**
 * The GameAction class is an abstract to a user-initiated action, like jumping
 * or moving. GameActions can be mapped to keys or the mouse with the
 * InputManager.
 */
class GameAction {
  /**
   * Normal behavior. The isPressed() method returns true as long as the key
   * is held down.
   */
  public static final int NORMAL = 0;
  /**
   * Initial press behavior. The isPressed() method returns true only after
   * the key is first pressed, and not again until the key is released and
   * pressed again.
   */
  public static final int DETECT_INITAL_PRESS_ONLY = 1;
  private static final int STATE_RELEASED = 0;
  private static final int STATE_PRESSED = 1;
  private static final int STATE_WAITING_FOR_RELEASE = 2;
  private String name;
  private int behavior;
  private int amount;
  private int state;
  /**
   * Create a new GameAction with the NORMAL behavior.
   */
  public GameAction(String name) {
    this(name, NORMAL);
  }
  /**
   * Create a new GameAction with the specified behavior.
   */
  public GameAction(String name, int behavior) {
    this.name = name;
    this.behavior = behavior;
    reset();
  }
  /**
   * Gets the name of this GameAction.
   */
  public String getName() {
    return name;
  }
  /**
   * Resets this GameAction so that it appears like it hasn't been pressed.
   */
  public void reset() {
    state = STATE_RELEASED;
    amount = 0;
  }
  /**
   * Taps this GameAction. Same as calling press() followed by release().
   */
  public synchronized void tap() {
    press();
    release();
  }
  /**
   * Signals that the key was pressed.
   */
  public synchronized void press() {
    press(1);
  }
  /**
   * Signals that the key was pressed a specified number of times, or that the
   * mouse move a spcified distance.
   */
  public synchronized void press(int amount) {
    if (state != STATE_WAITING_FOR_RELEASE) {
      this.amount += amount;
      state = STATE_PRESSED;
    }
  }
  /**
   * Signals that the key was released
   */
  public synchronized void release() {
    state = STATE_RELEASED;
  }
  /**
   * Returns whether the key was pressed or not since last checked.
   */
  public synchronized boolean isPressed() {
    return (getAmount() != 0);
  }
  /**
   * For keys, this is the number of times the key was pressed since it was
   * last checked. For mouse movement, this is the distance moved.
   */
  public synchronized int getAmount() {
    int retVal = amount;
    if (retVal != 0) {
      if (state == STATE_RELEASED) {
        amount = 0;
      } else if (behavior == DETECT_INITAL_PRESS_ONLY) {
        state = STATE_WAITING_FOR_RELEASE;
        amount = 0;
      }
    }
    return retVal;
  }
}
class Sprite {
  private Animation anim;
  // position (pixels)
  private float x;
  private float y;
  // velocity (pixels per millisecond)
  private float dx;
  private float dy;
  /**
   * Creates a new Sprite object with the specified Animation.
   */
  public Sprite(Animation anim) {
    this.anim = anim;
  }
  /**
   * Updates this Sprite's Animation and its position based on the velocity.
   */
  public void update(long elapsedTime) {
    x += dx * elapsedTime;
    y += dy * elapsedTime;
    anim.update(elapsedTime);
  }
  /**
   * Gets this Sprite's current x position.
   */
  public float getX() {
    return x;
  }
  /**
   * Gets this Sprite's current y position.
   */
  public float getY() {
    return y;
  }
  /**
   * Sets this Sprite's current x position.
   */
  public void setX(float x) {
    this.x = x;
  }
  /**
   * Sets this Sprite's current y position.
   */
  public void setY(float y) {
    this.y = y;
  }
  /**
   * Gets this Sprite's width, based on the size of the current image.
   */
  public int getWidth() {
    return anim.getImage().getWidth(null);
  }
  /**
   * Gets this Sprite's height, based on the size of the current image.
   */
  public int getHeight() {
    return anim.getImage().getHeight(null);
  }
  /**
   * Gets the horizontal velocity of this Sprite in pixels per millisecond.
   */
  public float getVelocityX() {
    return dx;
  }
  /**
   * Gets the vertical velocity of this Sprite in pixels per millisecond.
   */
  public float getVelocityY() {
    return dy;
  }
  /**
   * Sets the horizontal velocity of this Sprite in pixels per millisecond.
   */
  public void setVelocityX(float dx) {
    this.dx = dx;
  }
  /**
   * Sets the vertical velocity of this Sprite in pixels per millisecond.
   */
  public void setVelocityY(float dy) {
    this.dy = dy;
  }
  /**
   * Gets this Sprite's current image.
   */
  public Image getImage() {
    return anim.getImage();
  }
}
/**
 * The Filter3d class is a SoundFilter that creates a 3d sound effect. The sound
 * is filtered so that it is quiter the farther away the sound source is from
 * the listener.
 * 


 * Possible ideas to extend this class:
 * 


     * 
  • pan the sound to the left and right speakers
     * 

 * 
 * @see FilteredSoundStream
 */
class Filter3d extends SoundFilter {
  // number of samples to shift when changing the volume.
  private static final int NUM_SHIFTING_SAMPLES = 500;
  private Sprite source;
  private Sprite listener;
  private int maxDistance;
  private float lastVolume;
  /**
   * Creates a new Filter3d object with the specified source and listener
   * Sprites. The Sprite's position can be changed while this filter is
   * running.
   * 


   * The maxDistance parameter is the maximum distance that the sound can be
   * heard.
   */
  public Filter3d(Sprite source, Sprite listener, int maxDistance) {
    this.source = source;
    this.listener = listener;
    this.maxDistance = maxDistance;
    this.lastVolume = 0.0f;
  }
  /**
   * Filters the sound so that it gets more quiet with distance.
   */
  public void filter(byte[] samples, int offset, int length) {
    if (source == null || listener == null) {
      // nothing to filter - return
      return;
    }
    // calculate the listener's distance from the sound source
    float dx = (source.getX() - listener.getX());
    float dy = (source.getY() - listener.getY());
    float distance = (float) Math.sqrt(dx * dx + dy * dy);
    // set volume from 0 (no sound) to 1
    float newVolume = (maxDistance - distance) / maxDistance;
    if (newVolume <= 0) {
      newVolume = 0;
    }
    // set the volume of the sample
    int shift = 0;
    for (int i = offset; i < offset + length; i += 2) {
      float volume = newVolume;
      // shift from the last volume to the new volume
      if (shift < NUM_SHIFTING_SAMPLES) {
        volume = lastVolume + (newVolume - lastVolume) * shift
            / NUM_SHIFTING_SAMPLES;
        shift++;
      }
      // change the volume of the sample
      short oldSample = getSample(samples, i);
      short newSample = (short) (oldSample * volume);
      setSample(samples, i, newSample);
    }
    lastVolume = newVolume;
  }
}
/**
 * The FilteredSoundStream class is a FilterInputStream that applies a
 * SoundFilter to the underlying input stream.
 * 
 * @see SoundFilter
 */
class FilteredSoundStream extends FilterInputStream {
  private static final int REMAINING_SIZE_UNKNOWN = -1;
  private SoundFilter soundFilter;
  private int remainingSize;
  /**
   * Creates a new FilteredSoundStream object with the specified InputStream
   * and SoundFilter.
   */
  public FilteredSoundStream(InputStream in, SoundFilter soundFilter) {
    super(in);
    this.soundFilter = soundFilter;
    remainingSize = REMAINING_SIZE_UNKNOWN;
  }
  /**
   * Overrides the FilterInputStream method to apply this filter whenever
   * bytes are read
   */
  public int read(byte[] samples, int offset, int length) throws IOException {
    // read and filter the sound samples in the stream
    int bytesRead = super.read(samples, offset, length);
    if (bytesRead > 0) {
      soundFilter.filter(samples, offset, bytesRead);
      return bytesRead;
    }
    // if there are no remaining bytes in the sound stream,
    // check if the filter has any remaining bytes ("echoes").
    if (remainingSize == REMAINING_SIZE_UNKNOWN) {
      remainingSize = soundFilter.getRemainingSize();
      // round down to nearest multiple of 4
      // (typical frame size)
      remainingSize = remainingSize / 4 * 4;
    }
    if (remainingSize > 0) {
      length = Math.min(length, remainingSize);
      // clear the buffer
      for (int i = offset; i < offset + length; i++) {
        samples[i] = 0;
      }
      // filter the remaining bytes
      soundFilter.filter(samples, offset, length);
      remainingSize -= length;
      // return
      return length;
    } else {
      // end of stream
      return -1;
    }
  }
}
/**
 * The Animation class manages a series of images (frames) and the amount of
 * time to display each frame.
 */
class Animation {
  private ArrayList frames;
  private int currFrameIndex;
  private long animTime;
  private long totalDuration;
  /**
   * Creates a new, empty Animation.
   */
  public Animation() {
    frames = new ArrayList();
    totalDuration = 0;
    start();
  }
  /**
   * Adds an image to the animation with the specified duration (time to
   * display the image).
   */
  public synchronized void addFrame(Image image, long duration) {
    totalDuration += duration;
    frames.add(new AnimFrame(image, totalDuration));
  }
  /**
   * Starts this animation over from the beginning.
   */
  public synchronized void start() {
    animTime = 0;
    currFrameIndex = 0;
  }
  /**
   * Updates this animation's current image (frame), if neccesary.
   */
  public synchronized void update(long elapsedTime) {
    if (frames.size() > 1) {
      animTime += elapsedTime;
      if (animTime >= totalDuration) {
        animTime = animTime % totalDuration;
        currFrameIndex = 0;
      }
      while (animTime > getFrame(currFrameIndex).endTime) {
        currFrameIndex++;
      }
    }
  }
  /**
   * Gets this Animation's current image. Returns null if this animation has
   * no images.
   */
  public synchronized Image getImage() {
    if (frames.size() == 0) {
      return null;
    } else {
      return getFrame(currFrameIndex).image;
    }
  }
  private AnimFrame getFrame(int i) {
    return (AnimFrame) frames.get(i);
  }
  private class AnimFrame {
    Image image;
    long endTime;
    public AnimFrame(Image image, long endTime) {
      this.image = image;
      this.endTime = endTime;
    }
  }
}
/**
 * The LoopingByteInputStream is a ByteArrayInputStream that loops indefinitly.
 * The looping stops when the close() method is called.
 * 


 * Possible ideas to extend this class:
 * 


     * 
  • Add an option to only loop a certain number of times.
     * 

 */
class LoopingByteInputStream extends ByteArrayInputStream {
  private boolean closed;
  /**
   * Creates a new LoopingByteInputStream with the specified byte array. The
   * array is not copied.
   */
  public LoopingByteInputStream(byte[] buffer) {
    super(buffer);
    closed = false;
  }
  /**
   * Reads length bytes from the array. If the end of the array
   * is reached, the reading starts over from the beginning of the array.
   * Returns -1 if the array has been closed.
   */
  public int read(byte[] buffer, int offset, int length) {
    if (closed) {
      return -1;
    }
    int totalBytesRead = 0;
    while (totalBytesRead < length) {
      int numBytesRead = super.read(buffer, offset + totalBytesRead,
          length - totalBytesRead);
      if (numBytesRead > 0) {
        totalBytesRead += numBytesRead;
      } else {
        reset();
      }
    }
    return totalBytesRead;
  }
  /**
   * Closes the stream. Future calls to the read() methods will return 1.
   */
  public void close() throws IOException {
    super.close();
    closed = true;
  }
}
/**
 * A abstract class designed to filter sound samples. Since SoundFilters may use
 * internal buffering of samples, a new SoundFilter object should be created for
 * every sound played. However, SoundFilters can be reused after they are
 * finished by called the reset() method.
 * 


 * Assumes all samples are 16-bit, signed, little-endian format.
 * 
 * @see FilteredSoundStream
 */
abstract class SoundFilter {
  /**
   * Resets this SoundFilter. Does nothing by default.
   */
  public void reset() {
    // do nothing
  }
  /**
   * Gets the remaining size, in bytes, that this filter plays after the sound
   * is finished. An example would be an echo that plays longer than it's
   * original sound. This method returns 0 by default.
   */
  public int getRemainingSize() {
    return 0;
  }
  /**
   * Filters an array of samples. Samples should be in 16-bit, signed,
   * little-endian format.
   */
  public void filter(byte[] samples) {
    filter(samples, 0, samples.length);
  }
  /**
   * Filters an array of samples. Samples should be in 16-bit, signed,
   * little-endian format. This method should be implemented by subclasses.
   */
  public abstract void filter(byte[] samples, int offset, int length);
  /**
   * Convenience method for getting a 16-bit sample from a byte array. Samples
   * should be in 16-bit, signed, little-endian format.
   */
  public static short getSample(byte[] buffer, int position) {
    return (short) (((buffer[position + 1] & 0xff) << 8) | (buffer[position] & 0xff));
  }
  /**
   * Convenience method for setting a 16-bit sample in a byte array. Samples
   * should be in 16-bit, signed, little-endian format.
   */
  public static void setSample(byte[] buffer, int position, short sample) {
    buffer[position] = (byte) (sample & 0xff);
    buffer[position + 1] = (byte) ((sample >> 8) & 0xff);
  }
}
/**
 * The ScreenManager class manages initializing and displaying full screen
 * graphics modes.
 */
class ScreenManager {
  private GraphicsDevice device;
  /**
   * Creates a new ScreenManager object.
   */
  public ScreenManager() {
    GraphicsEnvironment environment = GraphicsEnvironment
        .getLocalGraphicsEnvironment();
    device = environment.getDefaultScreenDevice();
  }
  /**
   * Returns a list of compatible display modes for the default device on the
   * system.
   */
  public DisplayMode[] getCompatibleDisplayModes() {
    return device.getDisplayModes();
  }
  /**
   * Returns the first compatible mode in a list of modes. Returns null if no
   * modes are compatible.
   */
  public DisplayMode findFirstCompatibleMode(DisplayMode modes[]) {
    DisplayMode goodModes[] = device.getDisplayModes();
    for (int i = 0; i < modes.length; i++) {
      for (int j = 0; j < goodModes.length; j++) {
        if (displayModesMatch(modes[i], goodModes[j])) {
          return modes[i];
        }
      }
    }
    return null;
  }
  /**
   * Returns the current display mode.
   */
  public DisplayMode getCurrentDisplayMode() {
    return device.getDisplayMode();
  }
  /**
   * Determines if two display modes "match". Two display modes match if they
   * have the same resolution, bit depth, and refresh rate. The bit depth is
   * ignored if one of the modes has a bit depth of
   * DisplayMode.BIT_DEPTH_MULTI. Likewise, the refresh rate is ignored if one
   * of the modes has a refresh rate of DisplayMode.REFRESH_RATE_UNKNOWN.
   */
  public boolean displayModesMatch(DisplayMode mode1, DisplayMode mode2)
  {
    if (mode1.getWidth() != mode2.getWidth()
        || mode1.getHeight() != mode2.getHeight()) {
      return false;
    }
    if (mode1.getBitDepth() != DisplayMode.BIT_DEPTH_MULTI
        && mode2.getBitDepth() != DisplayMode.BIT_DEPTH_MULTI
        && mode1.getBitDepth() != mode2.getBitDepth()) {
      return false;
    }
    if (mode1.getRefreshRate() != DisplayMode.REFRESH_RATE_UNKNOWN
        && mode2.getRefreshRate() != DisplayMode.REFRESH_RATE_UNKNOWN
        && mode1.getRefreshRate() != mode2.getRefreshRate()) {
      return false;
    }
    return true;
  }
  /**
   * Enters full screen mode and changes the display mode. If the specified
   * display mode is null or not compatible with this device, or if the
   * display mode cannot be changed on this system, the current display mode
   * is used.
   * 


   * The display uses a BufferStrategy with 2 buffers.
   */
  public void setFullScreen(DisplayMode displayMode) {
    final JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setUndecorated(true);
    frame.setIgnoreRepaint(true);
    frame.setResizable(false);
    device.setFullScreenWindow(frame);
    if (displayMode != null && device.isDisplayChangeSupported()) {
      try {
        device.setDisplayMode(displayMode);
      } catch (IllegalArgumentException ex) {
      }
      // fix for mac os x
      frame.setSize(displayMode.getWidth(), displayMode.getHeight());
    }
    // avoid potential deadlock in 1.4.1_02
    try {
      EventQueue.invokeAndWait(new Runnable() {
        public void run() {
          frame.createBufferStrategy(2);
        }
      });
    } catch (InterruptedException ex) {
      // ignore
    } catch (InvocationTargetException ex) {
      // ignore
    }
  }
  /**
   * Gets the graphics context for the display. The ScreenManager uses double
   * buffering, so applications must call update() to show any graphics drawn.
   * 


   * The application must dispose of the graphics object.
   */
  public Graphics2D getGraphics() {
    Window window = device.getFullScreenWindow();
    if (window != null) {
      BufferStrategy strategy = window.getBufferStrategy();
      return (Graphics2D) strategy.getDrawGraphics();
    } else {
      return null;
    }
  }
  /**
   * Updates the display.
   */
  public void update() {
    Window window = device.getFullScreenWindow();
    if (window != null) {
      BufferStrategy strategy = window.getBufferStrategy();
      if (!strategy.contentsLost()) {
        strategy.show();
      }
    }
    // Sync the display on some systems.
    // (on Linux, this fixes event queue problems)
    Toolkit.getDefaultToolkit().sync();
  }
  /**
   * Returns the window currently used in full screen mode. Returns null if
   * the device is not in full screen mode.
   */
  public JFrame getFullScreenWindow() {
    return (JFrame) device.getFullScreenWindow();
  }
  /**
   * Returns the width of the window currently used in full screen mode.
   * Returns 0 if the device is not in full screen mode.
   */
  public int getWidth() {
    Window window = device.getFullScreenWindow();
    if (window != null) {
      return window.getWidth();
    } else {
      return 0;
    }
  }
  /**
   * Returns the height of the window currently used in full screen mode.
   * Returns 0 if the device is not in full screen mode.
   */
  public int getHeight() {
    Window window = device.getFullScreenWindow();
    if (window != null) {
      return window.getHeight();
    } else {
      return 0;
    }
  }
  /**
   * Restores the screen's display mode.
   */
  public void restoreScreen() {
    Window window = device.getFullScreenWindow();
    if (window != null) {
      window.dispose();
    }
    device.setFullScreenWindow(null);
  }
  /**
   * Creates an image compatible with the current display.
   */
  public BufferedImage createCompatibleImage(int w, int h, int transparancy) {
    Window window = device.getFullScreenWindow();
    if (window != null) {
      GraphicsConfiguration gc = window.getGraphicsConfiguration();
      return gc.createCompatibleImage(w, h, transparancy);
    }
    return null;
  }
}