Java/Game/Game Demo

Материал из Java эксперт
Перейти к: навигация, поиск

Tetris Game

   <source lang="java">
  

/**

* @(#)Main.java
*
* This work is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This work is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* Copyright (c) 2003 Per Cederberg. All rights reserved.
*/

import java.awt.BorderLayout; import java.awt.Button; import java.awt.Color; import java.awt.ruponent; import java.awt.Container; import java.awt.Dimension; import java.awt.Font; import java.awt.Frame; import java.awt.Graphics; import java.awt.GridLayout; import java.awt.Image; import java.awt.Insets; import java.awt.Rectangle; import java.awt.TextArea; import java.awt.TextField; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.util.Hashtable; import javax.swing.JComponent; /**

* The main class of the Tetris game. This class contains the
* necessary methods to run the game either as a stand-alone
* application or as an applet inside a web page.
*
* @version  1.2
* @author   Per Cederberg, per@percederberg.net
*/

public class Tetris { // /** // * The applet parameter information structure. // */ // private static final String PARAMETER[][] = { // { "tetris.color.background", "color", // "The overall background color." }, // { "tetris.color.label", "color", // "The text color of the labels." }, // { "tetris.color.button", "color", // "The start and pause button bolor." }, // { "tetris.color.board.background", "color", // "The background game board color." }, // { "tetris.color.board.message", "color", // "The game board message color." }, // { "tetris.color.figure.square", "color", // "The color of the square figure." }, // { "tetris.color.figure.line", "color", // "The color of the line figure." }, // { "tetris.color.figure.s", "color", // "The color of the "s" curved figure." }, // { "tetris.color.figure.z", "color", // "The color of the "z" curved figure." }, // { "tetris.color.figure.right", "color", // "The color of the right angle figure." }, // { "tetris.color.figure.left", "color", // "The color of the left angle figure." }, // { "tetris.color.figure.triangle", "color", // "The color of the triangle figure." } // };

   /**
    * The Tetris game being played (in applet mode).
    */
   private Game game = null;
   /**
    * The stand-alone main routine.
    * 
    * @param args      the command-line arguments
    */
   public static void main(String[] args) {
       System.out.println("starting");
       Frame  frame = new Frame("Tetris");
       final Game   game = new Game();
       
       game.addPropertyChangeListener(new PropertyChangeListener()
       {
           public void propertyChange(PropertyChangeEvent evt)
           {
              System.out.println("PCE "+evt.getPropertyName()+" "+evt.getNewValue());
           }
       });
       
       
       final TextArea taHiScores = new TextArea("",10,10,TextArea.SCROLLBARS_NONE);
      
       taHiScores.setBackground(Color.black);
       taHiScores.setForeground(Color.white);
       taHiScores.setFont(new Font("monospaced",0,11));
       taHiScores.setText(" High Scores                  \n"+
                          " -----------------------------\n\n"+
                          
                          " PLAYER     LEVEL    SCORE    \n\n"+
                          
                          " Lorenzo       12 1  50280     \n"+
                          " Lorenzo       12 1  50280     \n"
       );
       taHiScores.setEditable(false);
       
       final TextField txt = new TextField();
       txt.setEnabled(false);
       
       game.addPropertyChangeListener(new PropertyChangeListener()
       {
           public void propertyChange(PropertyChangeEvent evt)
           {
              if (evt.getPropertyName().equals("state"))
              {
                 int state = ((Integer) evt.getNewValue()).intValue();
                 if (state == Game.STATE_GAMEOVER)
                 {
                    txt.setEnabled(true);
                    txt.requestFocus();
                    txt.addActionListener(new ActionListener()
                    {
                       public void actionPerformed(ActionEvent e)
                       {
                          txt.setEnabled(false);
                          game.init();
                       }
                    });
                    // show score...
                 }
              }
           }
       });
       
       Button btnStart = new Button("Start");
       btnStart.setFocusable(false);
       btnStart.addActionListener(new ActionListener()
       {
          public void actionPerformed(ActionEvent e)
          {
             game.start();
          }
       });
       
       final Container c = new Container();
       c.setLayout(new BorderLayout());
       c.add(txt, BorderLayout.NORTH);
       c.add(game.getSquareBoardComponent(), BorderLayout.CENTER);
       c.add(btnStart,BorderLayout.SOUTH);
       
       final Container c2 = new Container();
       c2.setLayout(new GridLayout(1,2));
       c2.add(c);
       c2.add(taHiScores);
       
       frame.add(c2);
       
       System.out.println("packing");        
       frame.pack();
       
       
    
       // Add frame window listener
       frame.addWindowListener(new WindowAdapter() {
           public void windowClosing(WindowEvent e) {
               System.exit(0);
           }
       });
       // Show frame (and start game)
       frame.show();
   }

// /** // * Returns information about the parameters that are understood by // * this applet. // * // * @return an array describing the parameters to this applet // */ // public String[][] getParameterInfo() { // return PARAMETER; // } // /** // * Initializes the game in applet mode. // */ // public void init() { // String value; // // // Set all configuration parameters // for (int i = 0; i < PARAMETER.length; i++) { // value = getParameter(PARAMETER[i][0]); // if (value != null) { // Configuration.setValue(PARAMETER[i][0], value); // } // } // // // Create game object // game = new Game(); // // // Initialize applet component // setLayout(new BorderLayout()); // add(game.getComponent(), "Center"); // } // // /** // * Stops the game in applet mode. // */ // public void stop() { // game.quit(); // }


} /*

* @(#)SquareBoard.java
*
* This work is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as 
* published by the Free Software Foundation; either version 2 of 
* the License, or (at your option) any later version.
*
* This work is distributed in the hope that it will be useful, 
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
* GNU General Public License for more details.
*
* Copyright (c) 2003 Per Cederberg. All rights reserved.
*/

/**

* A Tetris square board. The board is rectangular and contains a grid
* of colored squares. The board is considered to be constrained to
* both sides (left and right), and to the bottom. There is no 
* constraint to the top of the board, although colors assigned to 
* positions above the board are not saved.
*
* @version  1.2
* @author   Per Cederberg, per@percederberg.net
*/
class SquareBoard extends Object {
   /**
    * The board width (in squares)
    */
   private final int width;
   /**
    * The board height (in squares).
    */
   private final int height;
   /**
    * The square board color matrix. This matrix (or grid) contains
    * a color entry for each square in the board. The matrix is 
    * indexed by the vertical, and then the horizontal coordinate.
    */
   private Color[][]  matrix = null;
   /**
    * An optional board message. The board message can be set at any
    * time, printing it on top of the board.
    */
   private String  message = null;
   /**
    * The number of lines removed. This counter is increased each 
    * time a line is removed from the board.
    */
   private int  removedLines = 0;
   /**
    * The graphical sqare board component. This graphical 
    * representation is created upon the first call to 
    * getComponent().
    */
   private final SquareBoardComponent  component;
   /**
    * Creates a new square board with the specified size. The square
    * board will initially be empty.
    *
    * @param width     the width of the board (in squares)
    * @param height    the height of the board (in squares)
    */
   public SquareBoard(int width, int height) {
       this.width = width;
       this.height = height;
       this.matrix = new Color[height][width];
       this.ruponent = new SquareBoardComponent();
       clear();
   }
   /**
    * Checks if a specified square is empty, i.e. if it is not 
    * marked with a color. If the square is outside the board, 
    * false will be returned in all cases except when the square is 
    * directly above the board.
    *
    * @param x         the horizontal position (0 <= x < width)
    * @param y         the vertical position (0 <= y < height)
    * 
    * @return true if the square is emtpy, or
    *         false otherwise
    */
   public boolean isSquareEmpty(int x, int y) {
       if (x < 0 || x >= width || y < 0 || y >= height) {
           return x >= 0 && x < width && y < 0;
       } else {
           return matrix[y][x] == null;
       }
   }
   /**
    * Checks if a specified line is empty, i.e. only contains 
    * empty squares. If the line is outside the board, false will
    * always be returned.
    *
    * @param y         the vertical position (0 <= y < height)
    * 
    * @return true if the whole line is empty, or
    *         false otherwise
    */
   public boolean isLineEmpty(int y) {
       if (y < 0 || y >= height) {
           return false;
       }
       for (int x = 0; x < width; x++) {
           if (matrix[y][x] != null) {
               return false;
           }
       }
       return true;
   }
   /**
    * Checks if a specified line is full, i.e. only contains no empty
    * squares. If the line is outside the board, true will always be 
    * returned.
    *
    * @param y         the vertical position (0 <= y < height)
    * 
    * @return true if the whole line is full, or
    *         false otherwise
    */
   public boolean isLineFull(int y) {
       if (y < 0 || y >= height) {
           return true;
       }
       for (int x = 0; x < width; x++) {
           if (matrix[y][x] == null) {
               return false;
           }
       }
       return true;
   }
   /**
    * Checks if the board contains any full lines.
    *
    * @return true if there are full lines on the board, or
    *         false otherwise
    */
   public boolean hasFullLines() {
       for (int y = height - 1; y >= 0; y--) {
           if (isLineFull(y)) {
               return true;
           }
       }
       return false;
   }
   /**
    * Returns a graphical component to draw the board. The component 
    * returned will automatically be updated when changes are made to
    * this board. Multiple calls to this method will return the same
    * component, as a square board can only have a single graphical
    * representation.
    * 
    * @return a graphical component that draws this board
    */
   public Component getComponent() {
       return component;
   }
   /**
    * Returns the board height (in squares). This method returns, 
    * i.e, the number of vertical squares that fit on the board.
    * 
    * @return the board height in squares
    */
   public int getBoardHeight() {
       return height;
   }
   /**
    * Returns the board width (in squares). This method returns, i.e,
    * the number of horizontal squares that fit on the board.
    * 
    * @return the board width in squares
    */
   public int getBoardWidth() {
       return width;
   }
   /**
    * Returns the number of lines removed since the last clear().
    * 
    * @return the number of lines removed since the last clear call
    */
   public int getRemovedLines() {
       return removedLines;
   }
   /**
    * Returns the color of an individual square on the board. If the 
    * square is empty or outside the board, null will be returned.
    *
    * @param x         the horizontal position (0 <= x < width)
    * @param y         the vertical position (0 <= y < height)
    * 
    * @return the square color, or null for none
    */
   public Color getSquareColor(int x, int y) {
       if (x < 0 || x >= width || y < 0 || y >= height) {
           return null;
       } else {
           return matrix[y][x];
       }
   }
   /**
    * Changes the color of an individual square on the board. The 
    * square will be marked as in need of a repaint, but the 
    * graphical component will NOT be repainted until the update() 
    * method is called.
    *
    * @param x         the horizontal position (0 <= x < width)
    * @param y         the vertical position (0 <= y < height)
    * @param color     the new square color, or null for empty
    */
   public void setSquareColor(int x, int y, Color color) {
       if (x < 0 || x >= width || y < 0 || y >= height) {
           return;
       }
       matrix[y][x] = color;
       if (component != null) {
           component.invalidateSquare(x, y);
       }
   }
   /**
    * Sets a message to display on the square board. This is supposed 
    * to be used when the board is not being used for active drawing, 
    * as it slows down the drawing considerably.
    *
    * @param message  a message to display, or null to remove a
    *                 previous message
    */
   public void setMessage(String message) {
       this.message = message;
       if (component != null) {
           component.redrawAll();
       }
   }
   /**
    * Clears the board, i.e. removes all the colored squares. As 
    * side-effects, the number of removed lines will be reset to 
    * zero, and the component will be repainted immediately.
    */
   public void clear() {
       removedLines = 0;
       for (int y = 0; y < height; y++) {
           for (int x = 0; x < width; x++) {
               this.matrix[y][x] = null;
           }
       }
       if (component != null) {
           component.redrawAll();
       }
   }
   /**
    * Removes all full lines. All lines above a removed line will be 
    * moved downward one step, and a new empty line will be added at 
    * the top. After removing all full lines, the component will be 
    * repainted.
    * 
    * @see #hasFullLines
    */
   public void removeFullLines() {
       boolean repaint = false;
       // Remove full lines
       for (int y = height - 1; y >= 0; y--) {
           if (isLineFull(y)) {
               removeLine(y);
               removedLines++;
               repaint = true;
               y++;
           }
       }
       // Repaint if necessary
       if (repaint && component != null) {
           component.redrawAll();
       }
   }
   /**
    * Removes a single line. All lines above are moved down one step, 
    * and a new empty line is added at the top. No repainting will be 
    * done after removing the line.
    *
    * @param y         the vertical position (0 <= y < height)
    */
   private void removeLine(int y) {
       if (y < 0 || y >= height) {
           return;
       }
       for (; y > 0; y--) {
           for (int x = 0; x < width; x++) {
               matrix[y][x] = matrix[y - 1][x];
           }
       }
       for (int x = 0; x < width; x++) {
           matrix[0][x] = null;
       }
   }
   /**
    * Updates the graphical component. Any squares previously changed 
    * will be repainted by this method.
    */
   public void update() {
       component.redraw();
   }
   /**
    * The graphical component that paints the square board. This is
    * implemented as an inner class in order to better abstract the 
    * detailed information that must be sent between the square board
    * and its graphical representation.
    */
   private class SquareBoardComponent extends JComponent {
       /**
        * The component size. If the component has been resized, that 
        * will be detected when the paint method executes. If this 
        * value is set to null, the component dimensions are unknown.
        */
       private Dimension  size = null;
       /**
        * The component insets. The inset values are used to create a 
        * border around the board to compensate for a skewed aspect 
        * ratio. If the component has been resized, the insets values 
        * will be recalculated when the paint method executes.
        */
       private Insets  insets = new Insets(0, 0, 0, 0);
       /**
        * The square size in pixels. This value is updated when the 
        * component size is changed, i.e. when the size 
        * variable is modified.
        */
       private Dimension  squareSize = new Dimension(0, 0);
       /**
        * An image used for double buffering. The board is first
        * painted onto this image, and that image is then painted 
        * onto the real surface in order to avoid making the drawing
        * process visible to the user. This image is recreated each
        * time the component size changes.
        */
       private Image  bufferImage = null;
       /**
        * A clip boundary buffer rectangle. This rectangle is used 
        * when calculating the clip boundaries, in order to avoid 
        * allocating a new clip rectangle for each board square.
        */
       private Rectangle  bufferRect = new Rectangle();
       /**
        * The board message color.
        */
       private Color  messageColor = Color.white;
       /**
        * A lookup table containing lighter versions of the colors.
        * This table is used to avoid calculating the lighter 
        * versions of the colors for each and every square drawn.
        */
       private Hashtable  lighterColors = new Hashtable();
       /**
        * A lookup table containing darker versions of the colors.
        * This table is used to avoid calculating the darker
        * versions of the colors for each and every square drawn.
        */
       private Hashtable  darkerColors = new Hashtable();
       /**
        * A flag set when the component has been updated.
        */
       private boolean  updated = true;
       /**
        * A bounding box of the squares to update. The coordinates 
        * used in the rectangle refers to the square matrix.
        */
       private Rectangle  updateRect = new Rectangle();
       /**
        * Creates a new square board component.
        */
       public SquareBoardComponent() {
           setBackground(Configuration.getColor("board.background", 
                                                "#000000"));
           messageColor = Configuration.getColor("board.message", 
                                                 "#ffffff");
       }
       /**
        * Adds a square to the set of squares in need of redrawing.
        *
        * @param x     the horizontal position (0 <= x < width)
        * @param y     the vertical position (0 <= y < height)
        */
       public void invalidateSquare(int x, int y) {
           if (updated) {
               updated = false;
               updateRect.x = x;
               updateRect.y = y;
               updateRect.width = 0;
               updateRect.height = 0;
           } else {
               if (x < updateRect.x) {
                   updateRect.width += updateRect.x - x;
                   updateRect.x = x;
               } else if (x > updateRect.x + updateRect.width) {
                   updateRect.width = x - updateRect.x;
               }
               if (y < updateRect.y) {
                   updateRect.height += updateRect.y - y;
                   updateRect.y = y;
               } else if (y > updateRect.y + updateRect.height) {
                   updateRect.height = y - updateRect.y;
               }
           }
       }
       /**
        * Redraws all the invalidated squares. If no squares have 
        * been marked as in need of redrawing, no redrawing will 
        * occur.
        */
       public void redraw() {
           Graphics  g;
           if (!updated) {
               updated = true;
               g = getGraphics();
               if (g==null) return;
               g.setClip(insets.left + updateRect.x * squareSize.width,
                         insets.top + updateRect.y * squareSize.height,
                         (updateRect.width + 1) * squareSize.width,
                         (updateRect.height + 1) * squareSize.height);
               paint(g);
           }
       }
       /**
        * Redraws the whole component.
        */
       public void redrawAll() {
           Graphics  g;
           updated = true;
           g = getGraphics();
           if (g==null) return;
           g.setClip(insets.left, 
                     insets.top, 
                     width * squareSize.width, 
                     height * squareSize.height);
           paint(g);
       }
       /**
        * Returns true as this component is double buffered.
        * 
        * @return true as this component is double buffered
        */
       public boolean isDoubleBuffered() {
           return true;
       }
       
       /**
        * Returns the preferred size of this component.
        * 
        * @return the preferred component size
        */
       public Dimension getPreferredSize() {
           return new Dimension(width * 20, height * 20);
       }
       /**
        * Returns the minimum size of this component.
        * 
        * @return the minimum component size
        */
       public Dimension getMinimumSize() {
           return getPreferredSize();
       }
       /**
        * Returns the maximum size of this component.
        * 
        * @return the maximum component size
        */
       public Dimension getMaximumSize() {
           return getPreferredSize();
       }
       /**
        * Returns a lighter version of the specified color. The 
        * lighter color will looked up in a hashtable, making this
        * method fast. If the color is not found, the ligher color 
        * will be calculated and added to the lookup table for later
        * reference.
        * 
        * @param c     the base color
        * 
        * @return the lighter version of the color
        */
       private Color getLighterColor(Color c) {
           Color  lighter;
           
           lighter = (Color) lighterColors.get(c);
           if (lighter == null) {
               lighter = c.brighter().brighter();
               lighterColors.put(c, lighter);
           }
           return lighter;
       }
       /**
        * Returns a darker version of the specified color. The 
        * darker color will looked up in a hashtable, making this
        * method fast. If the color is not found, the darker color 
        * will be calculated and added to the lookup table for later
        * reference.
        * 
        * @param c     the base color
        * 
        * @return the darker version of the color
        */
       private Color getDarkerColor(Color c) {
           Color  darker;
           
           darker = (Color) darkerColors.get(c);
           if (darker == null) {
               darker = c.darker().darker();
               darkerColors.put(c, darker);
           }
           return darker;
       }
       /**
        * Paints this component indirectly. The painting is first 
        * done to a buffer image, that is then painted directly to 
        * the specified graphics context.
        * 
        * @param g     the graphics context to use
        */
       public synchronized void paint(Graphics g) {
           Graphics   bufferGraphics;
           Rectangle  rect;
           // Handle component size change
           if (size == null || !size.equals(getSize())) {
               size = getSize();
               squareSize.width = size.width / width;
               squareSize.height = size.height / height;
               
               //if (squareSize.width <= squareSize.height) {
               //    squareSize.height = squareSize.width;
               //} else {
               //    squareSize.width = squareSize.height;
               //}
               
               insets.left = (size.width - width * squareSize.width) / 2;
               insets.right = insets.left;
               insets.top = 0;
               insets.bottom = size.height - height * squareSize.height;
               bufferImage = createImage(width * squareSize.width, 
                                         height * squareSize.height);
           }
           // Paint component in buffer image
           rect = g.getClipBounds();
           bufferGraphics = bufferImage.getGraphics();
           bufferGraphics.setClip(rect.x - insets.left, 
                                  rect.y - insets.top, 
                                  rect.width, 
                                  rect.height);
           doPaintComponent(bufferGraphics);
           // Paint image buffer
           g.drawImage(bufferImage, 
                       insets.left,
                       insets.top, 
                       getBackground(), 
                       null);
       }
       /**
        * Paints this component directly. All the squares on the 
        * board will be painted directly to the specified graphics
        * context.
        * 
        * @param g     the graphics context to use
        */
       private void doPaintComponent(Graphics g) {
           // Paint background
           g.setColor(getBackground());
           g.fillRect(0, 
                      0, 
                      width * squareSize.width, 
                      height * squareSize.height);
           
           // Paint squares
           for (int y = 0; y < height; y++) {
               for (int x = 0; x < width; x++) {
                   if (matrix[y][x] != null) {
                       paintSquare(g, x, y);
                   }
               }
           }
           // Paint message
           if (message != null) {
               paintMessage(g, message);
           }
       }
       /**
        * Paints a single board square. The specified position must 
        * contain a color object.
        *
        * @param g     the graphics context to use
        * @param x     the horizontal position (0 <= x < width)
        * @param y     the vertical position (0 <= y < height)
        */
       private void paintSquare(Graphics g, int x, int y) {
           Color  color = matrix[y][x];
           int    xMin = x * squareSize.width;
           int    yMin = y * squareSize.height;
           int    xMax = xMin + squareSize.width - 1;
           int    yMax = yMin + squareSize.height - 1;
           int    i;
           // Skip drawing if not visible
           bufferRect.x = xMin;
           bufferRect.y = yMin;
           bufferRect.width = squareSize.width;
           bufferRect.height = squareSize.height;
           if (!bufferRect.intersects(g.getClipBounds())) {
               return;
           }
           // Fill with base color
           g.setColor(color);
           g.fillRect(xMin, yMin, squareSize.width, squareSize.height);
           // Draw brighter lines
           g.setColor(getLighterColor(color));
           for (i = 0; i < squareSize.width / 10; i++) {
               g.drawLine(xMin + i, yMin + i, xMax - i, yMin + i);
               g.drawLine(xMin + i, yMin + i, xMin + i, yMax - i);
           }
           // Draw darker lines
           g.setColor(getDarkerColor(color));
           for (i = 0; i < squareSize.width / 10; i++) {
               g.drawLine(xMax - i, yMin + i, xMax - i, yMax - i);
               g.drawLine(xMin + i, yMax - i, xMax - i, yMax - i);
           }
       }
       /**
        * Paints a board message. The message will be drawn at the
        * center of the component.
        *
        * @param g     the graphics context to use
        * @param msg   the string message
        */
       private void paintMessage(Graphics g, String msg) {
           int  fontWidth;
           int  offset;
           int  x;
           int  y;
           // Find string font width
           g.setFont(new Font("SansSerif", Font.BOLD, squareSize.width + 4));
           fontWidth = g.getFontMetrics().stringWidth(msg);
           // Find centered position
           x = (width * squareSize.width - fontWidth) / 2;
           y = height * squareSize.height / 2;
           // Draw black version of the string
           offset = squareSize.width / 10;
           g.setColor(Color.black);
           g.drawString(msg, x - offset, y - offset);
           g.drawString(msg, x - offset, y);
           g.drawString(msg, x - offset, y - offset);
           g.drawString(msg, x, y - offset);
           g.drawString(msg, x, y + offset);
           g.drawString(msg, x + offset, y - offset);
           g.drawString(msg, x + offset, y);
           g.drawString(msg, x + offset, y + offset);
           // Draw white version of the string
           g.setColor(messageColor);
           g.drawString(msg, x, y);
       }
   }

}

/*
 * @(#)Game.java
 *
 * This work is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This work is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * Copyright (c) 2003 Per Cederberg. All rights reserved.
 */
/**
 * The Tetris game. This class controls all events in the game and
 * handles all the game logics. The game is started through user
 * interaction with the graphical game component provided by this 
 * class.
 *
 * @version  1.2
 * @author   Per Cederberg, per@percederberg.net
 */
 class Game extends Object 
{
    public static final int STATE_GETREADY =1;
    public static final int STATE_PLAYING = 2;
    public static final int STATE_PAUSED =  3;
    public static final int STATE_GAMEOVER =4;
       
    
    /**
     * The PropertyChangeSupport Object able to register listener and dispatch events to them.
     */
    private final PropertyChangeSupport PCS = new PropertyChangeSupport(this);
    
    /**
     * The main square board. This board is used for the game itself.
     */
    private final SquareBoard board;
    /**
     * The preview square board. This board is used to display a 
     * preview of the figures.
     */
    private final SquareBoard previewBoard = new SquareBoard(5, 5);
    /**
     * The figures used on both boards. All figures are reutilized in 
     * order to avoid creating new objects while the game is running.
     * Special care has to be taken when the preview figure and the
     * current figure refers to the same object.
     */
    private Figure[] figures = {
        new Figure(Figure.SQUARE_FIGURE),
        new Figure(Figure.LINE_FIGURE),
        new Figure(Figure.S_FIGURE),
        new Figure(Figure.Z_FIGURE),
        new Figure(Figure.RIGHT_ANGLE_FIGURE),
        new Figure(Figure.LEFT_ANGLE_FIGURE),
        new Figure(Figure.TRIANGLE_FIGURE)
    };
    /**
     * The thread that runs the game. When this variable is set to 
     * null, the game thread will terminate.
     */
    private final GameThread thread;
    /**
     * The game level. The level will be increased for every 20 lines 
     * removed from the square board.
     */
    private int level = 1;
    /**
     * The current score. The score is increased for every figure that
     * is possible to place on the main board.
     */
    private int score = 0;
    /**
     * The current figure. The figure will be updated when 
     */
    private Figure figure = null;
    /**
     * The next figure.
     */
    private Figure nextFigure = null;
    
    /**
     * The rotation of the next figure.
     */
    private int nextRotation = 0;
    /**
     * The figure preview flag. If this flag is set, the figure
     * will be shown in the figure preview board.
     */
    private boolean preview = true;
    /**
     * The move lock flag. If this flag is set, the current figure
     * cannot be moved. This flag is set when a figure is moved all 
     * the way down, and reset when a new figure is displayed.
     */
    private boolean moveLock = false;
    
    /**
     * 
     */
    private int state;
    /**
     * Creates a new Tetris game. The square board will be given
     * the default size of 10x20.
     */
    public Game() {
        this(10, 20);
    }
    /**
     * Creates a new Tetris game. The square board will be given
     * the specified size.
     *
     * @param width     the width of the square board (in positions)
     * @param height    the height of the square board (in positions)
     */
    public Game(int width, int height) {
        board = new SquareBoard(width, height);
        thread = new GameThread();
        handleGetReady();
        board.getComponent().setFocusable(true);
        board.getComponent().addKeyListener(new KeyAdapter() {
          public void keyPressed(KeyEvent e) {
             handleKeyEvent(e);
          }
        });
    }
    /**
     * Adds a PropertyChangeListener to this Game.
     * 
     * This is the list the Events that can be fired: 
     * 
     * name: "state" 
     * value: new current state (int) one of those: STATE_OVER,STATE_PLAYING,STATE_PAUSED 
     * when: fired when the state changes.  
     * 
     * name: "level"
     * value: current level (int)
     * when: fired when the player moves to the next level.
     * 
     * name: "score"
     * value: current score (int)
     * when: fired when the player increases his/her score.
     * 
     * name: "lines"
     * value: number of "removed" lines (int)
     * when: fired when the player removes one or more lines.
     * 
     * @param l the property change listener which is going to be notified.
     */
    public void addPropertyChangeListener(PropertyChangeListener l)
    {
       PCS.addPropertyChangeListener(l);
    }
    /**
     * Removes this propertyChangeListener
     * @param l the PropertyChangeListener object to remove.
     */
    public void removePropertyChangeListener(PropertyChangeListener l)
    {
       PCS.removePropertyChangeListener(l);
    }
    /**
     * Gets the current "state". 
     * One of the following: 
     * STATE_GETREADY,STATE_PLAYING,STATE_PAUSED,STATE_GAMEOVER. 
     * @return the current state.
     */
    public int getState()
    {
       return state;
    }
     
    /**
     * Gets the current level.
     * @return the current level.
     */
    public int getLevel()
    {
       return level;
    }
    
    /**
     * Gets the current score. 
     * @return the current score.
    **/
    public int getScore()
    {
       return score;
    }
    
    /**
     * Gets the number of lines that have been removed since the game started.
     * @return the number of removed lines.
     */
    public int getRemovedLines()
    {
       return board.getRemovedLines();
    }
    
    /**
     * Gets the java.awt.ruponent for the board.
     * @return the gui component for the board.
     */
    public Component getSquareBoardComponent()
    {
       return board.getComponent();
    }
    
    /**
     * Gets the java.awt.ruponent for the preview board (5x5)
     * @return the gui component for the board.
     */
    public Component getPreviewBoardComponent()
    {
       return previewBoard.getComponent();
    }
    
    /**
     * Initializes the game ready if the state is on STATE_GAMEOVER
     * otherwise it does nothing.
    **/
    public void init()
    {
       if (state == STATE_GAMEOVER)
       {
          handleGetReady();
       }
    }
    
    /**
     * Starts the game. (No matter what the current state is)
    **/
    public void start()
    {
       handleStart();
    }
    
    /**
     * Pauses the game  if the state is on STATE_PLAYING
     * otherwise it does nothing.
    **/
    public void pause()
    {
       if (state == STATE_PLAYING)
       {
          handlePause();
       }
    }
    /**
     * Resumes the game  if the state is on STATE_PAUSED
     * otherwise it does nothing.
    **/
    public void resume()
    {
       if (state == STATE_PAUSED)
       {
          handleResume();
       }
    }
    
    /**
     * Terminates the game. (No matter what the current state is)
    **/
    public void terminate()
    {
       handleGameOver();
    }    
    /**
     * Handles a game start event. Both the main and preview square
     * boards will be reset, and all other game parameters will be
     * reset. Finally the game thread will be launched.
     */
    private void handleStart() {
        // Reset score and figures
        level = 1;
        score = 0;
        figure = null;
        nextFigure = randomFigure();
        nextFigure.rotateRandom();
        nextRotation = nextFigure.getRotation();  
        // Reset components
        state = STATE_PLAYING;
        board.setMessage(null);
        board.clear();
        previewBoard.clear();
        handleLevelModification();
        handleScoreModification();
        
        PCS.firePropertyChange("state",-1, STATE_PLAYING );
        
        // Start game thread
        thread.reset();
    }
    /**
     * Handles a game over event. This will stop the game thread,
     * reset all figures and print a game over message.
     */
    private void handleGameOver() {
        // Stop game thred
        thread.setPaused(true);
        // Reset figures
        if (figure != null) {
            figure.detach();
        }
        figure = null;
        if (nextFigure != null) {
            nextFigure.detach();
        }
        nextFigure = null;
        // Handle components
        state = STATE_GAMEOVER;
        board.setMessage("Game Over");
        PCS.firePropertyChange("state",-1, STATE_GAMEOVER );
    }
    
    /**
     * Handles a getReady event. 
     * This will print a "get ready" message on the game board.
     */
    private void handleGetReady()
    {
       board.setMessage("Get Ready");
       board.clear();
       previewBoard.clear();
       state = STATE_GETREADY;
       PCS.firePropertyChange("state",-1, STATE_GETREADY );
    }
    /**
     * Handles a game pause event. This will pause the game thread and
     * print a pause message on the game board.
     */
    private void handlePause() {
        thread.setPaused(true);
        state = STATE_PAUSED;
        board.setMessage("Paused");
        PCS.firePropertyChange("state",-1, STATE_PAUSED );
    }
    /**
     * Handles a game resume event. This will resume the game thread 
     * and remove any messages on the game board.
     */
    private void handleResume() {
        state = STATE_PLAYING;
        board.setMessage(null);
        thread.setPaused(false);
        PCS.firePropertyChange("state",-1,STATE_PLAYING);
    }
    /**
     * Handles a level modification event. This will modify the level
     * label and adjust the thread speed.
     */
    private void handleLevelModification() {
        PCS.firePropertyChange("level",-1,level);
        thread.adjustSpeed();
    }
    
    /**
     * Handle a score modification event. This will modify the score
     * label.
     */
    private void handleScoreModification() {
       PCS.firePropertyChange("score",-1,score);
    }
    /**
     * Handles a figure start event. This will move the next figure
     * to the current figure position, while also creating a new 
     * preview figure. If the figure cannot be introduced onto the
     * game board, a game over event will be launched.
     */
    private void handleFigureStart() {
        int  rotation;
        // Move next figure to current
        figure = nextFigure;
        moveLock = false;
        rotation = nextRotation;
        nextFigure = randomFigure();
        nextFigure.rotateRandom(); 
        nextRotation = nextFigure.getRotation(); 
        // Handle figure preview
        if (preview) {
            previewBoard.clear(); 
            nextFigure.attach(previewBoard, true);
            nextFigure.detach();
        }
        // Attach figure to game board
        figure.setRotation(rotation);
        if (!figure.attach(board, false)) {
            previewBoard.clear();
            figure.attach(previewBoard, true);
            figure.detach();
            handleGameOver();
        }
    }
    /**
     * Handles a figure landed event. This will check that the figure
     * is completely visible, or a game over event will be launched.
     * After this control, any full lines will be removed. If no full
     * lines could be removed, a figure start event is launched 
     * directly.
     */
    private void handleFigureLanded() {
        // Check and detach figure
        if (figure.isAllVisible()) {
            score += 10;
            handleScoreModification();
        } else {
            handleGameOver();
            return;
        }
        figure.detach();
        figure = null;
        // Check for full lines or create new figure
        if (board.hasFullLines()) {
            board.removeFullLines();
            PCS.firePropertyChange("lines", -1, board.getRemovedLines());
            if (level < 9 && board.getRemovedLines() / 20 > level) {
                level = board.getRemovedLines() / 20;
                handleLevelModification();
            }
        } else {
            handleFigureStart();
        }
    }
    /**
     * Handles a timer event. This will normally move the figure down
     * one step, but when a figure has landed or isn"t ready other 
     * events will be launched. This method is synchronized to avoid 
     * race conditions with other asynchronous events (keyboard and
     * mouse).
     */
    private synchronized void handleTimer() {
        if (figure == null) {
            handleFigureStart();
        } else if (figure.hasLanded()) {
            handleFigureLanded();
        } else {
            figure.moveDown();
        }
    }
    /**
     * Handles a button press event. This will launch different events
     * depending on the state of the game, as the button semantics
     * change as the game changes. This method is synchronized to 
     * avoid race conditions with other asynchronous events (timer and
     * keyboard).
     */
    private synchronized void handlePauseOnOff() {
        if (nextFigure == null) {
            handleStart();
        } else if (thread.isPaused()) {
            handleResume();
        } else {
            handlePause();
        }
    }
    
    /**
     * Handles a keyboard event. This will result in different actions
     * being taken, depending on the key pressed. In some cases, other
     * events will be launched. This method is synchronized to avoid 
     * race conditions with other asynchronous events (timer and 
     * mouse).
     * 
     * @param e         the key event
     */
    private synchronized void handleKeyEvent(KeyEvent e) 
    {
        // Handle start (any key to start !!!)
        if (state == STATE_GETREADY) 
        {
           handleStart();
           return;
        }
        
        // pause and resume
        if (e.getKeyCode() == KeyEvent.VK_P) {
            handlePauseOnOff();
            return;
        }
        // Don"t proceed if stopped or paused
        if (figure == null || moveLock || thread.isPaused()) {
            return;
        }
        // Handle remaining key events
        switch (e.getKeyCode()) {
        case KeyEvent.VK_LEFT:
            figure.moveLeft();
            break;
        case KeyEvent.VK_RIGHT:
            figure.moveRight();
            break;
        case KeyEvent.VK_DOWN:
            figure.moveAllWayDown();
            moveLock = true;
            break;
        case KeyEvent.VK_UP:
        case KeyEvent.VK_SPACE:
            if (e.isControlDown()) {
                figure.rotateRandom();  
            } else if (e.isShiftDown()) { 
                figure.rotateClockwise();
            } else {
                figure.rotateCounterClockwise();
            }
            break;
        case KeyEvent.VK_S:
            if (level < 9) {
                level++;
                handleLevelModification();
            }
            break;
        case KeyEvent.VK_N:
            preview = !preview;
            if (preview && figure != nextFigure) {
                nextFigure.attach(previewBoard, true);
                nextFigure.detach(); 
            } else {
                previewBoard.clear();
            }
            break;
        }
    }
    /**
     * Returns a random figure. The figures come from the figures
     * array, and will not be initialized.
     * 
     * @return a random figure
     */
    private Figure randomFigure() {
        return figures[(int) (Math.random() * figures.length)];
    }
    /**
     * The game time thread. This thread makes sure that the timer
     * events are launched appropriately, making the current figure 
     * fall. This thread can be reused across games, but should be set
     * to paused state when no game is running.
     */
    private class GameThread extends Thread {
        /**
         * The game pause flag. This flag is set to true while the 
         * game should pause.
         */
        private boolean paused = true;
        /**
         * The number of milliseconds to sleep before each automatic
         * move. This number will be lowered as the game progresses.
         */
        private int sleepTime = 500;
        /**
         * Creates a new game thread with default values.
         */
        public GameThread() {
        }
        /**
         * Resets the game thread. This will adjust the speed and 
         * start the game thread if not previously started.
         */
        public void reset() {
            adjustSpeed();
            setPaused(false);
            if (!isAlive()) {
                this.start();
            }
        }
        /**
         * Checks if the thread is paused.
         * 
         * @return true if the thread is paused, or
         *         false otherwise
         */
        public boolean isPaused() {
            return paused;
        }
        /**
         * Sets the thread pause flag.
         * 
         * @param paused     the new paused flag value
         */
        public void setPaused(boolean paused) {
            this.paused = paused;
        }
        /**
         * Adjusts the game speed according to the current level. The 
         * sleeping time is calculated with a function making larger 
         * steps initially an smaller as the level increases. A level 
         * above ten (10) doesn"t have any further effect.
         */
        public void adjustSpeed() {
            sleepTime = 4500 / (level + 5) - 250;
            if (sleepTime < 50) {
                sleepTime = 50;
            }
        }
        /**
         * Runs the game.
         */
        public void run() {
            while (thread == this) {
                // Make the time step
                handleTimer();
                // Sleep for some time
                try {
                    Thread.sleep(sleepTime);
                } catch (InterruptedException ignore) {
                    // Do nothing
                }
                // Sleep if paused
                while (paused && thread == this) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException ignore) {
                        // Do nothing
                    }
                }
            }
        }
    }
}
 /*
  * @(#)Configuration.java
  *
  * This work is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
  * published by the Free Software Foundation; either version 2 of
  * the License, or (at your option) any later version.
  *
  * This work is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  * GNU General Public License for more details.
  *
  * Copyright (c) 2003 Per Cederberg. All rights reserved.
  */
 /**
  * A program configuration. This class provides static methods for 
  * simplifying the reading of configuration parameters. It also 
  * provides some methods for transforming string values into more 
  * useful objects.
  * 
  * @author   Per Cederberg, per@percederberg.net
  * @version  1.2
  */
 class Configuration extends Object {
     /**
      * The internal configuration property values. This lookup table
      * is used to avoid setting configuration parameters in the system 
      * properties, as some programs (applets) do not have the security
      * permissions to set system properties.
      */
     private static Hashtable  config = new Hashtable();
     /**
      * Returns a configuration parameter value.
      * 
      * @param key       the configuration parameter key
      * 
      * @return the configuration parameter value, or
      *         null if not set
      */
     public static String getValue(String key) {
         if (config.containsKey(key)) {
             return config.get(key).toString();
         } else {
             try {
                 return System.getProperty(key);
             } catch (SecurityException ignore) {
                 return null;
             }
         }
     }
     
     /**
      * Returns a configuration parameter value. If the configuration
      * parameter is not set, a default value will be returned instead.
      * 
      * @param key       the configuration parameter key
      * @param def       the default value to use
      * 
      * @return the configuration parameter value, or
      *         the default value if not set
      */
     public static String getValue(String key, String def) {
         String  value = getValue(key);
         
         return (value == null) ? def : value;
     }
     /**
      * Sets a configuration parameter value.
      * 
      * @param key       the configuration parameter key
      * @param value     the configuration parameter value
      */
     public static void setValue(String key, String value) {
         config.put(key, value);
     }
     /**
      * Returns the color configured for the specified key. The key 
      * will be prepended with "tetris.color." and the value will be
      * read from the system properties. The color value must be 
      * specified in hexadecimal web format, i.e. in the "#RRGGBB" 
      * format. If the default color isn"t in a valid format, white 
      * will be returned.
      * 
      * @param key       the configuration parameter key
      * @param def       the default value
      * 
      * @return the color specified in the configuration, or 
      *         a default color value
      */
     public static Color getColor(String key, String def) {
         String  value = getValue("tetris.color." + key, def);
         Color   color;
         
         color = parseColor(value);
         if (color != null) {
             return color;
         }
         color = parseColor(def);
         if (color != null) {
             return color;
         } else {
             return Color.white;
         }
     }
     
     /**
      * Parses a web color string. If the color value couldn"t be 
      * parsed correctly, null will be returned.
      * 
      * @param value     the color value to parse
      * 
      * @return the color represented by the string, or
      *         null if the string was malformed
      */
     private static Color parseColor(String value) {
         if (!value.startsWith("#")) {
             return null;
         }
         try {
             return new Color(Integer.parseInt(value.substring(1), 16));
         } catch (NumberFormatException ignore) {
             return null;
         }
     }
 }
 /*
  * @(#)Figure.java
  *
  * This work is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
  * published by the Free Software Foundation; either version 2 of
  * the License, or (at your option) any later version.
  *
  * This work is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  * GNU General Public License for more details.
  *
  * Copyright (c) 2003 Per Cederberg. All rights reserved.
  */
 /**
  * A class representing a Tetris square figure. Each figure consists 
  * of four connected squares in one of seven possible constellations. 
  * The figures may be rotated in 90 degree steps and have sideways and 
* downwards movability.

* * Each figure instance can have two states, either attached to a * square board or not. When attached, all move and rotation * operations are checked so that collisions do not occur with other * squares on the board. When not attached, any rotation can be made * (and will be kept when attached to a new board). * * @version 1.2 * @author Per Cederberg, per@percederberg.net */ class Figure extends Object { /** * A figure constant used to create a figure forming a square. */ public static final int SQUARE_FIGURE = 1; /** * A figure constant used to create a figure forming a line. */ public static final int LINE_FIGURE = 2; /** * A figure constant used to create a figure forming an "S". */ public static final int S_FIGURE = 3; /** * A figure constant used to create a figure forming a "Z". */ public static final int Z_FIGURE = 4; /** * A figure constant used to create a figure forming a right angle. */ public static final int RIGHT_ANGLE_FIGURE = 5; /** * A figure constant used to create a figure forming a left angle. */ public static final int LEFT_ANGLE_FIGURE = 6; /** * A figure constant used to create a figure forming a triangle. */ public static final int TRIANGLE_FIGURE = 7; /** * The square board to which the figure is attached. If this * variable is set to null, the figure is not attached. */ private SquareBoard board = null; /** * The horizontal figure position on the board. This value has no * meaning when the figure is not attached to a square board. */ private int xPos = 0; /** * The vertical figure position on the board. This value has no * meaning when the figure is not attached to a square board. */ private int yPos = 0; /** * The figure orientation (or rotation). This value is normally * between 0 and 3, but must also be less than the maxOrientation * value. * * @see #maxOrientation */ private int orientation = 0; /** * The maximum allowed orientation number. This is used to reduce * the number of possible rotations for some figures, such as the * square figure. If this value is not used, the square figure * will be possible to rotate around one of its squares, which * gives an erroneous effect. * * @see #orientation */ private int maxOrientation = 4; /** * The horizontal coordinates of the figure shape. The coordinates * are relative to the current figure position and orientation. */ private int[] shapeX = new int[4]; /** * The vertical coordinates of the figure shape. The coordinates * are relative to the current figure position and orientation. */ private int[] shapeY = new int[4]; /** * The figure color. */ private Color color = Color.white; /** * Creates a new figure of one of the seven predefined types. The * figure will not be attached to any square board and default * colors and orientations will be assigned. * * @param type the figure type (one of the figure constants) * * @see #SQUARE_FIGURE * @see #LINE_FIGURE * @see #S_FIGURE * @see #Z_FIGURE * @see #RIGHT_ANGLE_FIGURE * @see #LEFT_ANGLE_FIGURE * @see #TRIANGLE_FIGURE * * @throws IllegalArgumentException if the figure type specified * is not recognized */ public Figure(int type) throws IllegalArgumentException { initialize(type); } /** * Initializes the instance variables for a specified figure type. * * @param type the figure type (one of the figure constants) * * @see #SQUARE_FIGURE * @see #LINE_FIGURE * @see #S_FIGURE * @see #Z_FIGURE * @see #RIGHT_ANGLE_FIGURE * @see #LEFT_ANGLE_FIGURE * @see #TRIANGLE_FIGURE * * @throws IllegalArgumentException if the figure type specified * is not recognized */ private void initialize(int type) throws IllegalArgumentException { // Initialize default variables board = null; xPos = 0; yPos = 0; orientation = 0; // Initialize figure type variables switch (type) { case SQUARE_FIGURE : maxOrientation = 1; color = Configuration.getColor("figure.square", "#ffd8b1"); shapeX[0] = -1; shapeY[0] = 0; shapeX[1] = 0; shapeY[1] = 0; shapeX[2] = -1; shapeY[2] = 1; shapeX[3] = 0; shapeY[3] = 1; break; case LINE_FIGURE : maxOrientation = 2; color = Configuration.getColor("figure.line", "#ffb4b4"); shapeX[0] = -2; shapeY[0] = 0; shapeX[1] = -1; shapeY[1] = 0; shapeX[2] = 0; shapeY[2] = 0; shapeX[3] = 1; shapeY[3] = 0; break; case S_FIGURE : maxOrientation = 2; color = Configuration.getColor("figure.s", "#a3d5ee"); shapeX[0] = 0; shapeY[0] = 0; shapeX[1] = 1; shapeY[1] = 0; shapeX[2] = -1; shapeY[2] = 1; shapeX[3] = 0; shapeY[3] = 1; break; case Z_FIGURE : maxOrientation = 2; color = Configuration.getColor("figure.z", "#f4adff"); shapeX[0] = -1; shapeY[0] = 0; shapeX[1] = 0; shapeY[1] = 0; shapeX[2] = 0; shapeY[2] = 1; shapeX[3] = 1; shapeY[3] = 1; break; case RIGHT_ANGLE_FIGURE : maxOrientation = 4; color = Configuration.getColor("figure.right", "#c0b6fa"); shapeX[0] = -1; shapeY[0] = 0; shapeX[1] = 0; shapeY[1] = 0; shapeX[2] = 1; shapeY[2] = 0; shapeX[3] = 1; shapeY[3] = 1; break; case LEFT_ANGLE_FIGURE : maxOrientation = 4; color = Configuration.getColor("figure.left", "#f5f4a7"); shapeX[0] = -1; shapeY[0] = 0; shapeX[1] = 0; shapeY[1] = 0; shapeX[2] = 1; shapeY[2] = 0; shapeX[3] = -1; shapeY[3] = 1; break; case TRIANGLE_FIGURE : maxOrientation = 4; color = Configuration.getColor("figure.triangle", "#a4d9b6"); shapeX[0] = -1; shapeY[0] = 0; shapeX[1] = 0; shapeY[1] = 0; shapeX[2] = 1; shapeY[2] = 0; shapeX[3] = 0; shapeY[3] = 1; break; default : throw new IllegalArgumentException("No figure constant: " + type); } } /** * Checks if this figure is attached to a square board. * * @return true if the figure is already attached, or * false otherwise */ public boolean isAttached() { return board != null; } /** * Attaches the figure to a specified square board. The figure * will be drawn either at the absolute top of the board, with * only the bottom line visible, or centered onto the board. In * both cases, the squares on the new board are checked for * collisions. If the squares are already occupied, this method * returns false and no attachment is made.<p> * * The horizontal and vertical coordinates will be reset for the * figure, when centering the figure on the new board. The figure * orientation (rotation) will be kept, however. If the figure was * previously attached to another board, it will be detached from * that board before attaching to the new board. * * @param board the square board to attach to * @param center the centered position flag * * @return true if the figure could be attached, or * false otherwise */ public boolean attach(SquareBoard board, boolean center) { int newX; int newY; int i; // Check for previous attachment if (isAttached()) { detach(); } // Reset position (for correct controls) xPos = 0; yPos = 0; // Calculate position newX = board.getBoardWidth() / 2; if (center) { newY = board.getBoardHeight() / 2; } else { newY = 0; for (i = 0; i < shapeX.length; i++) { if (getRelativeY(i, orientation) - newY > 0) { newY = -getRelativeY(i, orientation); } } } // Check position this.board = board; if (!canMoveTo(newX, newY, orientation)) { this.board = null; return false; } // Draw figure xPos = newX; yPos = newY; paint(color); board.update(); return true; } /** * Detaches this figure from its square board. The figure will not * be removed from the board by this operation, resulting in the * figure being left intact. */ public void detach() { board = null; } /** * Checks if the figure is fully visible on the square board. If * the figure isn"t attached to a board, false will be returned. * * @return true if the figure is fully visible, or * false otherwise */ public boolean isAllVisible() { if (!isAttached()) { return false; } for (int i = 0; i < shapeX.length; i++) { if (yPos + getRelativeY(i, orientation) < 0) { return false; } } return true; } /** * Checks if the figure has landed. If this method returns true, * the moveDown() or the moveAllWayDown() methods should have no * effect. If no square board is attached, this method will return * true. * * @return true if the figure has landed, or false otherwise */ public boolean hasLanded() { return !isAttached() || !canMoveTo(xPos, yPos + 1, orientation); } /** * Moves the figure one step to the left. If such a move is not * possible with respect to the square board, nothing is done. The * square board will be changed as the figure moves, clearing the * previous cells. If no square board is attached, nothing is * done. */ public void moveLeft() { if (isAttached() && canMoveTo(xPos - 1, yPos, orientation)) { paint(null); xPos--; paint(color); board.update(); } } /** * Moves the figure one step to the right. If such a move is not * possible with respect to the square board, nothing is done. The * square board will be changed as the figure moves, clearing the * previous cells. If no square board is attached, nothing is * done. */ public void moveRight() { if (isAttached() && canMoveTo(xPos + 1, yPos, orientation)) { paint(null); xPos++; paint(color); board.update(); } } /** * Moves the figure one step down. If such a move is not possible * with respect to the square board, nothing is done. The square * board will be changed as the figure moves, clearing the * previous cells. If no square board is attached, nothing is * done. */ public void moveDown() { if (isAttached() && canMoveTo(xPos, yPos + 1, orientation)) { paint(null); yPos++; paint(color); board.update(); } } /** * Moves the figure all the way down. The limits of the move are * either the square board bottom, or squares not being empty. If * no move is possible with respect to the square board, nothing * is done. The square board will be changed as the figure moves, * clearing the previous cells. If no square board is attached, * nothing is done. */ public void moveAllWayDown() { int y = yPos; // Check for board if (!isAttached()) { return; } // Find lowest position while (canMoveTo(xPos, y + 1, orientation)) { y++; } // Update if (y != yPos) { paint(null); yPos = y; paint(color); board.update(); } } /** * Returns the current figure rotation (orientation). * * @return the current figure rotation */ public int getRotation() { return orientation; } /** * Sets the figure rotation (orientation). If the desired rotation * is not possible with respect to the square board, nothing is * done. The square board will be changed as the figure moves, * clearing the previous cells. If no square board is attached, * the rotation is performed directly. * * @param rotation the new figure orientation */ public void setRotation(int rotation) { int newOrientation; // Set new orientation newOrientation = rotation % maxOrientation; // Check new position if (!isAttached()) { orientation = newOrientation; } else if (canMoveTo(xPos, yPos, newOrientation)) { paint(null); orientation = newOrientation; paint(color); board.update(); } } /** * Rotates the figure randomly. If such a rotation is not * possible with respect to the square board, nothing is done. * The square board will be changed as the figure moves, * clearing the previous cells. If no square board is attached, * the rotation is performed directly. */ public void rotateRandom() { setRotation((int) (Math.random() * 4.0) % maxOrientation); } /** * Rotates the figure clockwise. If such a rotation is not * possible with respect to the square board, nothing is done. * The square board will be changed as the figure moves, * clearing the previous cells. If no square board is attached, * the rotation is performed directly. */ public void rotateClockwise() { if (maxOrientation == 1) { return; } else { setRotation((orientation + 1) % maxOrientation); } } /** * Rotates the figure counter-clockwise. If such a rotation * is not possible with respect to the square board, nothing * is done. The square board will be changed as the figure * moves, clearing the previous cells. If no square board is * attached, the rotation is performed directly. */ public void rotateCounterClockwise() { if (maxOrientation == 1) { return; } else { setRotation((orientation + 3) % 4); } } /** * Checks if a specified pair of (square) coordinates are inside * the figure, or not. * * @param x the horizontal position * @param y the vertical position * * @return true if the coordinates are inside the figure, or * false otherwise */ private boolean isInside(int x, int y) { for (int i = 0; i < shapeX.length; i++) { if (x == xPos + getRelativeX(i, orientation) && y == yPos + getRelativeY(i, orientation)) { return true; } } return false; } /** * Checks if the figure can move to a new position. The current * figure position is taken into account when checking for * collisions. If a collision is detected, this method will return * false. * * @param newX the new horizontal position * @param newY the new vertical position * @param newOrientation the new orientation (rotation) * * @return true if the figure can be moved, or * false otherwise */ private boolean canMoveTo(int newX, int newY, int newOrientation) { int x; int y; for (int i = 0; i < 4; i++) { x = newX + getRelativeX(i, newOrientation); y = newY + getRelativeY(i, newOrientation); if (!isInside(x, y) && !board.isSquareEmpty(x, y)) { return false; } } return true; } /** * Returns the relative horizontal position of a specified square. * The square will be rotated according to the specified * orientation. * * @param square the square to rotate (0-3) * @param orientation the orientation to use (0-3) * * @return the rotated relative horizontal position */ private int getRelativeX(int square, int orientation) { switch (orientation % 4) { case 0 : return shapeX[square]; case 1 : return -shapeY[square]; case 2 : return -shapeX[square]; case 3 : return shapeY[square]; default: return 0; // Should never occur } } /** * Rotates the relative vertical position of a specified square. * The square will be rotated according to the specified * orientation. * * @param square the square to rotate (0-3) * @param orientation the orientation to use (0-3) * * @return the rotated relative vertical position */ private int getRelativeY(int square, int orientation) { switch (orientation % 4) { case 0 : return shapeY[square]; case 1 : return shapeX[square]; case 2 : return -shapeY[square]; case 3 : return -shapeX[square]; default: return 0; // Should never occur } } /** * Paints the figure on the board with the specified color. * * @param color the color to paint with, or null for clearing */ private void paint(Color color) { int x, y; for (int i = 0; i < shapeX.length; i++) { x = xPos + getRelativeX(i, orientation); y = yPos + getRelativeY(i, orientation); board.setSquareColor(x, y, color); } } } </source>

Title Game

   <source lang="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.ruponent; 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.Transparency; 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.geom.AffineTransform; import java.awt.image.BufferStrategy; import java.awt.image.BufferedImage; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileReader; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import javax.sound.midi.InvalidMidiDataException; import javax.sound.midi.MetaEventListener; import javax.sound.midi.MetaMessage; import javax.sound.midi.MidiSystem; import javax.sound.midi.MidiUnavailableException; import javax.sound.midi.Sequence; import javax.sound.midi.Sequencer; 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.Mixer; import javax.sound.sampled.SourceDataLine; import javax.sound.sampled.UnsupportedAudioFileException; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.SwingUtilities; /**

* GameManager manages all parts of the game.
*/

public class TitleGame extends GameCore {

 public static void main(String[] args) {
   new TitleGame().run();
 }
 // uncompressed, 44100Hz, 16-bit, mono, signed, little-endian
 private static final AudioFormat PLAYBACK_FORMAT = new AudioFormat(44100,
     16, 1, true, false);
 private static final int DRUM_TRACK = 1;
 public static final float GRAVITY = 0.002f;
 private Point pointCache = new Point();
 private TileMap map;
 private MidiPlayer midiPlayer;
 private SoundManager soundManager;
 private ResourceManager resourceManager;
 private Sound prizeSound;
 private Sound boopSound;
 private InputManager inputManager;
 private TileMapRenderer renderer;
 private GameAction moveLeft;
 private GameAction moveRight;
 private GameAction jump;
 private GameAction exit;
 public void init() {
   super.init();
   // set up input manager
   initInput();
   // start resource manager
   resourceManager = new ResourceManager(screen.getFullScreenWindow()
       .getGraphicsConfiguration());
   // load resources
   renderer = new TileMapRenderer();
   renderer.setBackground(resourceManager.loadImage("background.png"));
   // load first map
   map = resourceManager.loadNextMap();
   // load sounds
   soundManager = new SoundManager(PLAYBACK_FORMAT);
   prizeSound = soundManager.getSound("sounds/prize.wav");
   boopSound = soundManager.getSound("sounds/boop2.wav");
   // start music
   midiPlayer = new MidiPlayer();
   Sequence sequence = midiPlayer.getSequence("sounds/music.midi");
   midiPlayer.play(sequence, true);
   toggleDrumPlayback();
 }
 /**
  * Closes any resurces used by the GameManager.
  */
 public void stop() {
   super.stop();
   midiPlayer.close();
   soundManager.close();
 }
 private void initInput() {
   moveLeft = new GameAction("moveLeft");
   moveRight = new GameAction("moveRight");
   jump = new GameAction("jump", GameAction.DETECT_INITAL_PRESS_ONLY);
   exit = new GameAction("exit", GameAction.DETECT_INITAL_PRESS_ONLY);
   inputManager = new InputManager(screen.getFullScreenWindow());
   inputManager.setCursor(InputManager.INVISIBLE_CURSOR);
   inputManager.mapToKey(moveLeft, KeyEvent.VK_LEFT);
   inputManager.mapToKey(moveRight, KeyEvent.VK_RIGHT);
   inputManager.mapToKey(jump, KeyEvent.VK_SPACE);
   inputManager.mapToKey(exit, KeyEvent.VK_ESCAPE);
 }
 private void checkInput(long elapsedTime) {
   if (exit.isPressed()) {
     stop();
   }
   Player player = (Player) map.getPlayer();
   if (player.isAlive()) {
     float velocityX = 0;
     if (moveLeft.isPressed()) {
       velocityX -= player.getMaxSpeed();
     }
     if (moveRight.isPressed()) {
       velocityX += player.getMaxSpeed();
     }
     if (jump.isPressed()) {
       player.jump(false);
     }
     player.setVelocityX(velocityX);
   }
 }
 public void draw(Graphics2D g) {
   renderer.draw(g, map, screen.getWidth(), screen.getHeight());
 }
 /**
  * Gets the current map.
  */
 public TileMap getMap() {
   return map;
 }
 /**
  * Turns on/off drum playback in the midi music (track 1).
  */
 public void toggleDrumPlayback() {
   Sequencer sequencer = midiPlayer.getSequencer();
   if (sequencer != null) {
     sequencer.setTrackMute(DRUM_TRACK, !sequencer
         .getTrackMute(DRUM_TRACK));
   }
 }
 /**
  * Gets the tile that a Sprites collides with. Only the Sprite"s X or Y
  * should be changed, not both. Returns null if no collision is detected.
  */
 public Point getTileCollision(Sprite sprite, float newX, float newY) {
   float fromX = Math.min(sprite.getX(), newX);
   float fromY = Math.min(sprite.getY(), newY);
   float toX = Math.max(sprite.getX(), newX);
   float toY = Math.max(sprite.getY(), newY);
   // get the tile locations
   int fromTileX = TileMapRenderer.pixelsToTiles(fromX);
   int fromTileY = TileMapRenderer.pixelsToTiles(fromY);
   int toTileX = TileMapRenderer
       .pixelsToTiles(toX + sprite.getWidth() - 1);
   int toTileY = TileMapRenderer.pixelsToTiles(toY + sprite.getHeight()
       - 1);
   // check each tile for a collision
   for (int x = fromTileX; x <= toTileX; x++) {
     for (int y = fromTileY; y <= toTileY; y++) {
       if (x < 0 || x >= map.getWidth() || map.getTile(x, y) != null) {
         // collision found, return the tile
         pointCache.setLocation(x, y);
         return pointCache;
       }
     }
   }
   // no collision found
   return null;
 }
 /**
  * Checks if two Sprites collide with one another. Returns false if the two
  * Sprites are the same. Returns false if one of the Sprites is a Creature
  * that is not alive.
  */
 public boolean isCollision(Sprite s1, Sprite s2) {
   // if the Sprites are the same, return false
   if (s1 == s2) {
     return false;
   }
   // if one of the Sprites is a dead Creature, return false
   if (s1 instanceof Creature && !((Creature) s1).isAlive()) {
     return false;
   }
   if (s2 instanceof Creature && !((Creature) s2).isAlive()) {
     return false;
   }
   // get the pixel location of the Sprites
   int s1x = Math.round(s1.getX());
   int s1y = Math.round(s1.getY());
   int s2x = Math.round(s2.getX());
   int s2y = Math.round(s2.getY());
   // check if the two sprites" boundaries intersect
   return (s1x < s2x + s2.getWidth() && s2x < s1x + s1.getWidth()
       && s1y < s2y + s2.getHeight() && s2y < s1y + s1.getHeight());
 }
 /**
  * Gets the Sprite that collides with the specified Sprite, or null if no
  * Sprite collides with the specified Sprite.
  */
 public Sprite getSpriteCollision(Sprite sprite) {
   // run through the list of Sprites
   Iterator i = map.getSprites();
   while (i.hasNext()) {
     Sprite otherSprite = (Sprite) i.next();
     if (isCollision(sprite, otherSprite)) {
       // collision found, return the Sprite
       return otherSprite;
     }
   }
   // no collision found
   return null;
 }
 /**
  * Updates Animation, position, and velocity of all Sprites in the current
  * map.
  */
 public void update(long elapsedTime) {
   Creature player = (Creature) map.getPlayer();
   // player is dead! start map over
   if (player.getState() == Creature.STATE_DEAD) {
     map = resourceManager.reloadMap();
     return;
   }
   // get keyboard/mouse input
   checkInput(elapsedTime);
   // update player
   updateCreature(player, elapsedTime);
   player.update(elapsedTime);
   // update other sprites
   Iterator i = map.getSprites();
   while (i.hasNext()) {
     Sprite sprite = (Sprite) i.next();
     if (sprite instanceof Creature) {
       Creature creature = (Creature) sprite;
       if (creature.getState() == Creature.STATE_DEAD) {
         i.remove();
       } else {
         updateCreature(creature, elapsedTime);
       }
     }
     // normal update
     sprite.update(elapsedTime);
   }
 }
 /**
  * Updates the creature, applying gravity for creatures that aren"t flying,
  * and checks collisions.
  */
 private void updateCreature(Creature creature, long elapsedTime) {
   // apply gravity
   if (!creature.isFlying()) {
     creature.setVelocityY(creature.getVelocityY() + GRAVITY
         * elapsedTime);
   }
   // change x
   float dx = creature.getVelocityX();
   float oldX = creature.getX();
   float newX = oldX + dx * elapsedTime;
   Point tile = getTileCollision(creature, newX, creature.getY());
   if (tile == null) {
     creature.setX(newX);
   } else {
     // line up with the tile boundary
     if (dx > 0) {
       creature.setX(TileMapRenderer.tilesToPixels(tile.x)
           - creature.getWidth());
     } else if (dx < 0) {
       creature.setX(TileMapRenderer.tilesToPixels(tile.x + 1));
     }
     creature.collideHorizontal();
   }
   if (creature instanceof Player) {
     checkPlayerCollision((Player) creature, false);
   }
   // change y
   float dy = creature.getVelocityY();
   float oldY = creature.getY();
   float newY = oldY + dy * elapsedTime;
   tile = getTileCollision(creature, creature.getX(), newY);
   if (tile == null) {
     creature.setY(newY);
   } else {
     // line up with the tile boundary
     if (dy > 0) {
       creature.setY(TileMapRenderer.tilesToPixels(tile.y)
           - creature.getHeight());
     } else if (dy < 0) {
       creature.setY(TileMapRenderer.tilesToPixels(tile.y + 1));
     }
     creature.collideVertical();
   }
   if (creature instanceof Player) {
     boolean canKill = (oldY < creature.getY());
     checkPlayerCollision((Player) creature, canKill);
   }
 }
 /**
  * Checks for Player collision with other Sprites. If canKill is true,
  * collisions with Creatures will kill them.
  */
 public void checkPlayerCollision(Player player, boolean canKill) {
   if (!player.isAlive()) {
     return;
   }
   // check for player collision with other sprites
   Sprite collisionSprite = getSpriteCollision(player);
   if (collisionSprite instanceof PowerUp) {
     acquirePowerUp((PowerUp) collisionSprite);
   } else if (collisionSprite instanceof Creature) {
     Creature badguy = (Creature) collisionSprite;
     if (canKill) {
       // kill the badguy and make player bounce
       soundManager.play(boopSound);
       badguy.setState(Creature.STATE_DYING);
       player.setY(badguy.getY() - player.getHeight());
       player.jump(true);
     } else {
       // player dies!
       player.setState(Creature.STATE_DYING);
     }
   }
 }
 /**
  * Gives the player the speicifed power up and removes it from the map.
  */
 public void acquirePowerUp(PowerUp powerUp) {
   // remove it from the map
   map.removeSprite(powerUp);
   if (powerUp instanceof PowerUp.Star) {
     // do something here, like give the player points
     soundManager.play(prizeSound);
   } else if (powerUp instanceof PowerUp.Music) {
     // change the music
     soundManager.play(prizeSound);
     toggleDrumPlayback();
   } else if (powerUp instanceof PowerUp.Goal) {
     // advance to next map
     soundManager.play(prizeSound, new EchoFilter(2000, .7f), false);
     map = resourceManager.loadNextMap();
   }
 }

} /**

* The EchoFilter class is a SoundFilter that emulates an echo.
* 
* @see FilteredSoundStream
*/

class EchoFilter extends SoundFilter {

 private short[] delayBuffer;
 private int delayBufferPos;
 private float decay;
 /**
  * Creates an EchoFilter with the specified number of delay samples and the
  * specified decay rate.
  * <p>
  * The number of delay samples specifies how long before the echo is
  * initially heard. For a 1 second echo with mono, 44100Hz sound, use 44100
  * delay samples.
  * <p>
  * The decay value is how much the echo has decayed from the source. A decay
  * value of .5 means the echo heard is half as loud as the source.
  */
 public EchoFilter(int numDelaySamples, float decay) {
   delayBuffer = new short[numDelaySamples];
   this.decay = decay;
 }
 /**
  * Gets the remaining size, in bytes, of samples that this filter can echo
  * after the sound is done playing. Ensures that the sound will have decayed
  * to below 1% of maximum volume (amplitude).
  */
 public int getRemainingSize() {
   float finalDecay = 0.01f;
   // derived from Math.pow(decay,x) <= finalDecay
   int numRemainingBuffers = (int) Math.ceil(Math.log(finalDecay)
       / Math.log(decay));
   int bufferSize = delayBuffer.length * 2;
   return bufferSize * numRemainingBuffers;
 }
 /**
  * Clears this EchoFilter"s internal delay buffer.
  */
 public void reset() {
   for (int i = 0; i < delayBuffer.length; i++) {
     delayBuffer[i] = 0;
   }
   delayBufferPos = 0;
 }
 /**
  * Filters the sound samples to add an echo. The samples played are added to
  * the sound in the delay buffer multipied by the decay rate. The result is
  * then stored in the delay buffer, so multiple echoes are heard.
  */
 public void filter(byte[] samples, int offset, int length) {
   for (int i = offset; i < offset + length; i += 2) {
     // update the sample
     short oldSample = getSample(samples, i);
     short newSample = (short) (oldSample + decay
         * delayBuffer[delayBufferPos]);
     setSample(samples, i, newSample);
     // update the delay buffer
     delayBuffer[delayBufferPos] = newSample;
     delayBufferPos++;
     if (delayBufferPos == delayBuffer.length) {
       delayBufferPos = 0;
     }
   }
 }

} /**

* 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.
* <p>
* 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);
 }

} /**

* A Creature is a Sprite that is affected by gravity and can die. It has four
* Animations: moving left, moving right, dying on the left, and dying on the
* right.
*/

abstract class Creature extends Sprite {

 /**
  * Amount of time to go from STATE_DYING to STATE_DEAD.
  */
 private static final int DIE_TIME = 1000;
 public static final int STATE_NORMAL = 0;
 public static final int STATE_DYING = 1;
 public static final int STATE_DEAD = 2;
 private Animation left;
 private Animation right;
 private Animation deadLeft;
 private Animation deadRight;
 private int state;
 private long stateTime;
 /**
  * Creates a new Creature with the specified Animations.
  */
 public Creature(Animation left, Animation right, Animation deadLeft,
     Animation deadRight) {
   super(right);
   this.left = left;
   this.right = right;
   this.deadLeft = deadLeft;
   this.deadRight = deadRight;
   state = STATE_NORMAL;
 }
 public Object clone() {
   // use reflection to create the correct subclass
   Constructor constructor = getClass().getConstructors()[0];
   try {
     return constructor
         .newInstance(new Object[] { (Animation) left.clone(),
             (Animation) right.clone(),
             (Animation) deadLeft.clone(),
             (Animation) deadRight.clone() });
   } catch (Exception ex) {
     // should never happen
     ex.printStackTrace();
     return null;
   }
 }
 /**
  * Gets the maximum speed of this Creature.
  */
 public float getMaxSpeed() {
   return 0;
 }
 /**
  * Wakes up the creature when the Creature first appears on screen.
  * Normally, the creature starts moving left.
  */
 public void wakeUp() {
   if (getState() == STATE_NORMAL && getVelocityX() == 0) {
     setVelocityX(-getMaxSpeed());
   }
 }
 /**
  * Gets the state of this Creature. The state is either STATE_NORMAL,
  * STATE_DYING, or STATE_DEAD.
  */
 public int getState() {
   return state;
 }
 /**
  * Sets the state of this Creature to STATE_NORMAL, STATE_DYING, or
  * STATE_DEAD.
  */
 public void setState(int state) {
   if (this.state != state) {
     this.state = state;
     stateTime = 0;
     if (state == STATE_DYING) {
       setVelocityX(0);
       setVelocityY(0);
     }
   }
 }
 /**
  * Checks if this creature is alive.
  */
 public boolean isAlive() {
   return (state == STATE_NORMAL);
 }
 /**
  * Checks if this creature is flying.
  */
 public boolean isFlying() {
   return false;
 }
 /**
  * Called before update() if the creature collided with a tile horizontally.
  */
 public void collideHorizontal() {
   setVelocityX(-getVelocityX());
 }
 /**
  * Called before update() if the creature collided with a tile vertically.
  */
 public void collideVertical() {
   setVelocityY(0);
 }
 /**
  * Updates the animaton for this creature.
  */
 public void update(long elapsedTime) {
   // select the correct Animation
   Animation newAnim = anim;
   if (getVelocityX() < 0) {
     newAnim = left;
   } else if (getVelocityX() > 0) {
     newAnim = right;
   }
   if (state == STATE_DYING && newAnim == left) {
     newAnim = deadLeft;
   } else if (state == STATE_DYING && newAnim == right) {
     newAnim = deadRight;
   }
   // update the Animation
   if (anim != newAnim) {
     anim = newAnim;
     anim.start();
   } else {
     anim.update(elapsedTime);
   }
   // update to "dead" state
   stateTime += elapsedTime;
   if (state == STATE_DYING && stateTime >= DIE_TIME) {
     setState(STATE_DEAD);
   }
 }

} /**

* 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() {
   this(new ArrayList(), 0);
 }
 private Animation(ArrayList frames, long totalDuration) {
   this.frames = frames;
   this.totalDuration = totalDuration;
   start();
 }
 /**
  * Creates a duplicate of this animation. The list of frames are shared
  * between the two Animations, but each Animation can be animated
  * independently.
  */
 public Object clone() {
   return new Animation(frames, totalDuration);
 }
 /**
  * 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;
   }
 }

} /**

* A thread pool is a group of a limited number of threads that are used to
* execute tasks.
*/

class ThreadPool extends ThreadGroup {

 private boolean isAlive;
 private LinkedList taskQueue;
 private int threadID;
 private static int threadPoolID;
 /**
  * Creates a new ThreadPool.
  * 
  * @param numThreads
  *            The number of threads in the pool.
  */
 public ThreadPool(int numThreads) {
   super("ThreadPool-" + (threadPoolID++));
   setDaemon(true);
   isAlive = true;
   taskQueue = new LinkedList();
   for (int i = 0; i < numThreads; i++) {
     new PooledThread().start();
   }
 }
 /**
  * Requests a new task to run. This method returns immediately, and the task
  * executes on the next available idle thread in this ThreadPool.
  * <p>
  * Tasks start execution in the order they are received.
  * 
  * @param task
  *            The task to run. If null, no action is taken.
  * @throws IllegalStateException
  *             if this ThreadPool is already closed.
  */
 public synchronized void runTask(Runnable task) {
   if (!isAlive) {
     throw new IllegalStateException();
   }
   if (task != null) {
     taskQueue.add(task);
     notify();
   }
 }
 protected synchronized Runnable getTask() throws InterruptedException {
   while (taskQueue.size() == 0) {
     if (!isAlive) {
       return null;
     }
     wait();
   }
   return (Runnable) taskQueue.removeFirst();
 }
 /**
  * Closes this ThreadPool and returns immediately. All threads are stopped,
  * and any waiting tasks are not executed. Once a ThreadPool is closed, no
  * more tasks can be run on this ThreadPool.
  */
 public synchronized void close() {
   if (isAlive) {
     isAlive = false;
     taskQueue.clear();
     interrupt();
   }
 }
 /**
  * Closes this ThreadPool and waits for all running threads to finish. Any
  * waiting tasks are executed.
  */
 public void join() {
   // notify all waiting threads that this ThreadPool is no
   // longer alive
   synchronized (this) {
     isAlive = false;
     notifyAll();
   }
   // wait for all threads to finish
   Thread[] threads = new Thread[activeCount()];
   int count = enumerate(threads);
   for (int i = 0; i < count; i++) {
     try {
       threads[i].join();
     } catch (InterruptedException ex) {
     }
   }
 }
 /**
  * Signals that a PooledThread has started. This method does nothing by
  * default; subclasses should override to do any thread-specific startup
  * tasks.
  */
 protected void threadStarted() {
   // do nothing
 }
 /**
  * Signals that a PooledThread has stopped. This method does nothing by
  * default; subclasses should override to do any thread-specific cleanup
  * tasks.
  */
 protected void threadStopped() {
   // do nothing
 }
 /**
  * A PooledThread is a Thread in a ThreadPool group, designed to run tasks
  * (Runnables).
  */
 private class PooledThread extends Thread {
   public PooledThread() {
     super(ThreadPool.this, "PooledThread-" + (threadID++));
   }
   public void run() {
     // signal that this thread has started
     threadStarted();
     while (!isInterrupted()) {
       // get a task to run
       Runnable task = null;
       try {
         task = getTask();
       } catch (InterruptedException ex) {
       }
       // if getTask() returned null or was interrupted,
       // close this thread.
       if (task == null) {
         break;
       }
       // run the task, and eat any exceptions it throws
       try {
         task.run();
       } catch (Throwable t) {
         uncaughtException(this, t);
       }
     }
     // signal that this thread has stopped
     threadStopped();
   }
 }

} /**

* The Player.
*/

class Player extends Creature {

 private static final float JUMP_SPEED = -.95f;
 private boolean onGround;
 public Player(Animation left, Animation right, Animation deadLeft,
     Animation deadRight) {
   super(left, right, deadLeft, deadRight);
 }
 public void collideHorizontal() {
   setVelocityX(0);
 }
 public void collideVertical() {
   // check if collided with ground
   if (getVelocityY() > 0) {
     onGround = true;
   }
   setVelocityY(0);
 }
 public void setY(float y) {
   // check if falling
   if (Math.round(y) > Math.round(getY())) {
     onGround = false;
   }
   super.setY(y);
 }
 public void wakeUp() {
   // do nothing
 }
 /**
  * Makes the player jump if the player is on the ground or if forceJump is
  * true.
  */
 public void jump(boolean forceJump) {
   if (onGround || forceJump) {
     onGround = false;
     setVelocityY(JUMP_SPEED);
   }
 }
 public float getMaxSpeed() {
   return 0.5f;
 }

} /**

* 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, 16, 0), new DisplayMode(800, 600, 32, 0),
     new DisplayMode(800, 600, 24, 0), new DisplayMode(640, 480, 16, 0),
     new DisplayMode(640, 480, 32, 0), new DisplayMode(640, 480, 24, 0),
     new DisplayMode(1024, 768, 16, 0),
     new DisplayMode(1024, 768, 32, 0),
     new DisplayMode(1024, 768, 24, 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();
     // don"t take a nap! run as fast as possible
     /*
      * 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 ResourceManager class loads and manages tile Images and "host" Sprites
* used in the game. Game Sprites are cloned from "host" Sprites.
*/

class ResourceManager {

 private ArrayList tiles;
 private int currentMap;
 private GraphicsConfiguration gc;
 // host sprites used for cloning
 private Sprite playerSprite;
 private Sprite musicSprite;
 private Sprite coinSprite;
 private Sprite goalSprite;
 private Sprite grubSprite;
 private Sprite flySprite;
 /**
  * Creates a new ResourceManager with the specified GraphicsConfiguration.
  */
 public ResourceManager(GraphicsConfiguration gc) {
   this.gc = gc;
   loadTileImages();
   loadCreatureSprites();
   loadPowerUpSprites();
 }
 /**
  * Gets an image from the images/ directory.
  */
 public Image loadImage(String name) {
   String filename = "images/" + name;
   return new ImageIcon(filename).getImage();
 }
 public Image getMirrorImage(Image image) {
   return getScaledImage(image, -1, 1);
 }
 public Image getFlippedImage(Image image) {
   return getScaledImage(image, 1, -1);
 }
 private Image getScaledImage(Image image, float x, float y) {
   // set up the transform
   AffineTransform transform = new AffineTransform();
   transform.scale(x, y);
   transform.translate((x - 1) * image.getWidth(null) / 2, (y - 1)
       * image.getHeight(null) / 2);
   // create a transparent (not translucent) image
   Image newImage = gc.createCompatibleImage(image.getWidth(null), image
       .getHeight(null), Transparency.BITMASK);
   // draw the transformed image
   Graphics2D g = (Graphics2D) newImage.getGraphics();
   g.drawImage(image, transform, null);
   g.dispose();
   return newImage;
 }
 public TileMap loadNextMap() {
   TileMap map = null;
   while (map == null) {
     currentMap++;
     try {
       map = loadMap("maps/map" + currentMap + ".txt");
     } catch (IOException ex) {
       if (currentMap == 1) {
         // no maps to load!
         return null;
       }
       currentMap = 0;
       map = null;
     }
   }
   return map;
 }
 public TileMap reloadMap() {
   try {
     return loadMap("maps/map" + currentMap + ".txt");
   } catch (IOException ex) {
     ex.printStackTrace();
     return null;
   }
 }
 private TileMap loadMap(String filename) throws IOException {
   ArrayList lines = new ArrayList();
   int width = 0;
   int height = 0;
   // read every line in the text file into the list
   BufferedReader reader = new BufferedReader(new FileReader(filename));
   while (true) {
     String line = reader.readLine();
     // no more lines to read
     if (line == null) {
       reader.close();
       break;
     }
     // add every line except for comments
     if (!line.startsWith("#")) {
       lines.add(line);
       width = Math.max(width, line.length());
     }
   }
   // parse the lines to create a TileEngine
   height = lines.size();
   TileMap newMap = new TileMap(width, height);
   for (int y = 0; y < height; y++) {
     String line = (String) lines.get(y);
     for (int x = 0; x < line.length(); x++) {
       char ch = line.charAt(x);
       // check if the char represents tile A, B, C etc.
       int tile = ch - "A";
       if (tile >= 0 && tile < tiles.size()) {
         newMap.setTile(x, y, (Image) tiles.get(tile));
       }
       // check if the char represents a sprite
       else if (ch == "o") {
         addSprite(newMap, coinSprite, x, y);
       } else if (ch == "!") {
         addSprite(newMap, musicSprite, x, y);
       } else if (ch == "*") {
         addSprite(newMap, goalSprite, x, y);
       } else if (ch == "1") {
         addSprite(newMap, grubSprite, x, y);
       } else if (ch == "2") {
         addSprite(newMap, flySprite, x, y);
       }
     }
   }
   // add the player to the map
   Sprite player = (Sprite) playerSprite.clone();
   player.setX(TileMapRenderer.tilesToPixels(3));
   player.setY(0);
   newMap.setPlayer(player);
   return newMap;
 }
 private void addSprite(TileMap map, Sprite hostSprite, int tileX, int tileY) {
   if (hostSprite != null) {
     // clone the sprite from the "host"
     Sprite sprite = (Sprite) hostSprite.clone();
     // center the sprite
     sprite.setX(TileMapRenderer.tilesToPixels(tileX)
         + (TileMapRenderer.tilesToPixels(1) - sprite.getWidth())
         / 2);
     // bottom-justify the sprite
     sprite.setY(TileMapRenderer.tilesToPixels(tileY + 1)
         - sprite.getHeight());
     // add it to the map
     map.addSprite(sprite);
   }
 }
 // -----------------------------------------------------------
 // code for loading sprites and images
 // -----------------------------------------------------------
 public void loadTileImages() {
   // keep looking for tile A,B,C, etc. this makes it
   // easy to drop new tiles in the images/ directory
   tiles = new ArrayList();
   char ch = "A";
   while (true) {
     String name = "tile_" + ch + ".png";
     File file = new File("images/" + name);
     if (!file.exists()) {
       break;
     }
     tiles.add(loadImage(name));
     ch++;
   }
 }
 public void loadCreatureSprites() {
   Image[][] images = new Image[4][];
   // load left-facing images
   images[0] = new Image[] { loadImage("player1.png"),
       loadImage("player2.png"), loadImage("player3.png"),
       loadImage("fly1.png"), loadImage("fly2.png"),
       loadImage("fly3.png"), loadImage("grub1.png"),
       loadImage("grub2.png"), };
   images[1] = new Image[images[0].length];
   images[2] = new Image[images[0].length];
   images[3] = new Image[images[0].length];
   for (int i = 0; i < images[0].length; i++) {
     // right-facing images
     images[1][i] = getMirrorImage(images[0][i]);
     // left-facing "dead" images
     images[2][i] = getFlippedImage(images[0][i]);
     // right-facing "dead" images
     images[3][i] = getFlippedImage(images[1][i]);
   }
   // create creature animations
   Animation[] playerAnim = new Animation[4];
   Animation[] flyAnim = new Animation[4];
   Animation[] grubAnim = new Animation[4];
   for (int i = 0; i < 4; i++) {
     playerAnim[i] = createPlayerAnim(images[i][0], images[i][1],
         images[i][2]);
     flyAnim[i] = createFlyAnim(images[i][3], images[i][4], images[i][5]);
     grubAnim[i] = createGrubAnim(images[i][6], images[i][7]);
   }
   // create creature sprites
   playerSprite = new Player(playerAnim[0], playerAnim[1], playerAnim[2],
       playerAnim[3]);
   flySprite = new Fly(flyAnim[0], flyAnim[1], flyAnim[2], flyAnim[3]);
   grubSprite = new Grub(grubAnim[0], grubAnim[1], grubAnim[2],
       grubAnim[3]);
 }
 private Animation createPlayerAnim(Image player1, Image player2,
     Image player3) {
   Animation anim = new Animation();
   anim.addFrame(player1, 250);
   anim.addFrame(player2, 150);
   anim.addFrame(player1, 150);
   anim.addFrame(player2, 150);
   anim.addFrame(player3, 200);
   anim.addFrame(player2, 150);
   return anim;
 }
 private Animation createFlyAnim(Image img1, Image img2, Image img3) {
   Animation anim = new Animation();
   anim.addFrame(img1, 50);
   anim.addFrame(img2, 50);
   anim.addFrame(img3, 50);
   anim.addFrame(img2, 50);
   return anim;
 }
 private Animation createGrubAnim(Image img1, Image img2) {
   Animation anim = new Animation();
   anim.addFrame(img1, 250);
   anim.addFrame(img2, 250);
   return anim;
 }
 private void loadPowerUpSprites() {
   // create "goal" sprite
   Animation anim = new Animation();
   anim.addFrame(loadImage("heart1.png"), 150);
   anim.addFrame(loadImage("heart2.png"), 150);
   anim.addFrame(loadImage("heart3.png"), 150);
   anim.addFrame(loadImage("heart2.png"), 150);
   goalSprite = new PowerUp.Goal(anim);
   // create "star" sprite
   anim = new Animation();
   anim.addFrame(loadImage("star1.png"), 100);
   anim.addFrame(loadImage("star2.png"), 100);
   anim.addFrame(loadImage("star3.png"), 100);
   anim.addFrame(loadImage("star4.png"), 100);
   coinSprite = new PowerUp.Star(anim);
   // create "music" sprite
   anim = new Animation();
   anim.addFrame(loadImage("music1.png"), 150);
   anim.addFrame(loadImage("music2.png"), 150);
   anim.addFrame(loadImage("music3.png"), 150);
   anim.addFrame(loadImage("music2.png"), 150);
   musicSprite = new PowerUp.Music(anim);
 }

} /**

* A Grub is a Creature that moves slowly on the ground.
*/

class Grub extends Creature {

 public Grub(Animation left, Animation right, Animation deadLeft,
     Animation deadRight) {
   super(left, right, deadLeft, deadRight);
 }
 public float getMaxSpeed() {
   return 0.05f;
 }

} /**

* A PowerUp class is a Sprite that the player can pick up.
*/

abstract class PowerUp extends Sprite {

 public PowerUp(Animation anim) {
   super(anim);
 }
 public Object clone() {
   // use reflection to create the correct subclass
   Constructor constructor = getClass().getConstructors()[0];
   try {
     return constructor.newInstance(new Object[] { (Animation) anim
         .clone() });
   } catch (Exception ex) {
     // should never happen
     ex.printStackTrace();
     return null;
   }
 }
 /**
  * A Star PowerUp. Gives the player points.
  */
 public static class Star extends PowerUp {
   public Star(Animation anim) {
     super(anim);
   }
 }
 /**
  * A Music PowerUp. Changes the game music.
  */
 public static class Music extends PowerUp {
   public Music(Animation anim) {
     super(anim);
   }
 }
 /**
  * A Goal PowerUp. Advances to the next map.
  */
 public static class Goal extends PowerUp {
   public Goal(Animation anim) {
     super(anim);
   }
 }

} /**

* A Fly is a Creature that fly slowly in the air.
*/

class Fly extends Creature {

 public Fly(Animation left, Animation right, Animation deadLeft,
     Animation deadRight) {
   super(left, right, deadLeft, deadRight);
 }
 public float getMaxSpeed() {
   return 0.2f;
 }
 public boolean isFlying() {
   return isAlive();
 }

} /**

* 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;
 }

} /**

* The TileMapRenderer class draws a TileMap on the screen. It draws all tiles,
* sprites, and an optional background image centered around the position of the
* player.
* 
* <p>
* If the width of background image is smaller the width of the tile map, the
* background image will appear to move slowly, creating a parallax background
* effect.
* 
* <p>
* Also, three static methods are provided to convert pixels to tile positions,
* and vice-versa.
* 
* <p>
* This TileMapRender uses a tile size of 64.
*/

class TileMapRenderer {

 private static final int TILE_SIZE = 64;
 // the size in bits of the tile
 // Math.pow(2, TILE_SIZE_BITS) == TILE_SIZE
 private static final int TILE_SIZE_BITS = 6;
 private Image background;
 /**
  * Converts a pixel position to a tile position.
  */
 public static int pixelsToTiles(float pixels) {
   return pixelsToTiles(Math.round(pixels));
 }
 /**
  * Converts a pixel position to a tile position.
  */
 public static int pixelsToTiles(int pixels) {
   // use shifting to get correct values for negative pixels
   return pixels >> TILE_SIZE_BITS;
   // or, for tile sizes that aren"t a power of two,
   // use the floor function:
   //return (int)Math.floor((float)pixels / TILE_SIZE);
 }
 /**
  * Converts a tile position to a pixel position.
  */
 public static int tilesToPixels(int numTiles) {
   // no real reason to use shifting here.
   // it"s slighty faster, but doesn"t add up to much
   // on modern processors.
   return numTiles << TILE_SIZE_BITS;
   // use this if the tile size isn"t a power of 2:
   //return numTiles * TILE_SIZE;
 }
 /**
  * Sets the background to draw.
  */
 public void setBackground(Image background) {
   this.background = background;
 }
 /**
  * Draws the specified TileMap.
  */
 public void draw(Graphics2D g, TileMap map, int screenWidth,
     int screenHeight) {
   Sprite player = map.getPlayer();
   int mapWidth = tilesToPixels(map.getWidth());
   // get the scrolling position of the map
   // based on player"s position
   int offsetX = screenWidth / 2 - Math.round(player.getX()) - TILE_SIZE;
   offsetX = Math.min(offsetX, 0);
   offsetX = Math.max(offsetX, screenWidth - mapWidth);
   // get the y offset to draw all sprites and tiles
   int offsetY = screenHeight - tilesToPixels(map.getHeight());
   // draw black background, if needed
   if (background == null || screenHeight > background.getHeight(null)) {
     g.setColor(Color.black);
     g.fillRect(0, 0, screenWidth, screenHeight);
   }
   // draw parallax background image
   if (background != null) {
     int x = offsetX * (screenWidth - background.getWidth(null))
         / (screenWidth - mapWidth);
     int y = screenHeight - background.getHeight(null);
     g.drawImage(background, x, y, null);
   }
   // draw the visible tiles
   int firstTileX = pixelsToTiles(-offsetX);
   int lastTileX = firstTileX + pixelsToTiles(screenWidth) + 1;
   for (int y = 0; y < map.getHeight(); y++) {
     for (int x = firstTileX; x <= lastTileX; x++) {
       Image image = map.getTile(x, y);
       if (image != null) {
         g.drawImage(image, tilesToPixels(x) + offsetX,
             tilesToPixels(y) + offsetY, null);
       }
     }
   }
   // draw player
   g.drawImage(player.getImage(), Math.round(player.getX()) + offsetX,
       Math.round(player.getY()) + offsetY, null);
   // draw sprites
   Iterator i = map.getSprites();
   while (i.hasNext()) {
     Sprite sprite = (Sprite) i.next();
     int x = Math.round(sprite.getX()) + offsetX;
     int y = Math.round(sprite.getY()) + offsetY;
     g.drawImage(sprite.getImage(), x, y, null);
     // wake up the creature when it"s on screen
     if (sprite instanceof Creature && x >= 0 && x < screenWidth) {
       ((Creature) sprite).wakeUp();
     }
   }
 }

} /**

* 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.rup = 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.
  * <p>
  * 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();
   }
 }

} /**

* The SoundManager class manages sound playback. The SoundManager is a
* ThreadPool, with each thread playing back one sound at a time. This allows
* the SoundManager to easily limit the number of simultaneous sounds being
* played.
* <p>
* Possible ideas to extend this class:
*
    *
  • add a setMasterVolume() method, which uses Controls to set the volume * for each line. *
  • don"t play a sound if more than, say, 500ms has passed since the request * to play *
*/

class SoundManager extends ThreadPool {

 private AudioFormat playbackFormat;
 private ThreadLocal localLine;
 private ThreadLocal localBuffer;
 private Object pausedLock;
 private boolean paused;
 /**
  * Creates a new SoundManager using the maximum number of simultaneous
  * sounds.
  */
 public SoundManager(AudioFormat playbackFormat) {
   this(playbackFormat, getMaxSimultaneousSounds(playbackFormat));
 }
 /**
  * Creates a new SoundManager with the specified maximum number of
  * simultaneous sounds.
  */
 public SoundManager(AudioFormat playbackFormat, int maxSimultaneousSounds) {
   super(Math.min(maxSimultaneousSounds,
       getMaxSimultaneousSounds(playbackFormat)));
   this.playbackFormat = playbackFormat;
   localLine = new ThreadLocal();
   localBuffer = new ThreadLocal();
   pausedLock = new Object();
   // notify threads in pool it"s ok to start
   synchronized (this) {
     notifyAll();
   }
 }
 /**
  * Gets the maximum number of simultaneous sounds with the specified
  * AudioFormat that the default mixer can play.
  */
 public static int getMaxSimultaneousSounds(AudioFormat playbackFormat) {
   DataLine.Info lineInfo = new DataLine.Info(SourceDataLine.class,
       playbackFormat);
   Mixer mixer = AudioSystem.getMixer(null);
   return mixer.getMaxLines(lineInfo);
 }
 /**
  * Does any clean up before closing.
  */
 protected void cleanUp() {
   // signal to unpause
   setPaused(false);
   // close the mixer (stops any running sounds)
   Mixer mixer = AudioSystem.getMixer(null);
   if (mixer.isOpen()) {
     mixer.close();
   }
 }
 public void close() {
   cleanUp();
   super.close();
 }
 public void join() {
   cleanUp();
   super.join();
 }
 /**
  * Sets the paused state. Sounds may not pause immediately.
  */
 public void setPaused(boolean paused) {
   if (this.paused != paused) {
     synchronized (pausedLock) {
       this.paused = paused;
       if (!paused) {
         // restart sounds
         pausedLock.notifyAll();
       }
     }
   }
 }
 /**
  * Returns the paused state.
  */
 public boolean isPaused() {
   return paused;
 }
 /**
  * Loads a Sound from the file system. Returns null if an error occurs.
  */
 public Sound getSound(String filename) {
   return getSound(getAudioInputStream(filename));
 }
 /**
  * Loads a Sound from an input stream. Returns null if an error occurs.
  */
 public Sound getSound(InputStream is) {
   return getSound(getAudioInputStream(is));
 }
 /**
  * Loads a Sound from an AudioInputStream.
  */
 public Sound getSound(AudioInputStream audioStream) {
   if (audioStream == null) {
     return null;
   }
   // get the number of bytes to read
   int length = (int) (audioStream.getFrameLength() * audioStream
       .getFormat().getFrameSize());
   // read the entire stream
   byte[] samples = new byte[length];
   DataInputStream is = new DataInputStream(audioStream);
   try {
     is.readFully(samples);
     is.close();
   } catch (IOException ex) {
     ex.printStackTrace();
   }
   // return the samples
   return new Sound(samples);
 }
 /**
  * Creates an AudioInputStream from a sound from the file system.
  */
 public AudioInputStream getAudioInputStream(String filename) {
   try {
     return getAudioInputStream(new FileInputStream(filename));
   } catch (IOException ex) {
     ex.printStackTrace();
     return null;
   }
 }
 /**
  * Creates an AudioInputStream from a sound from an input stream
  */
 public AudioInputStream getAudioInputStream(InputStream is) {
   try {
     if (!is.markSupported()) {
       is = new BufferedInputStream(is);
     }
     // open the source stream
     AudioInputStream source = AudioSystem.getAudioInputStream(is);
     // convert to playback format
     return AudioSystem.getAudioInputStream(playbackFormat, source);
   } catch (UnsupportedAudioFileException ex) {
     ex.printStackTrace();
   } catch (IOException ex) {
     ex.printStackTrace();
   } catch (IllegalArgumentException ex) {
     ex.printStackTrace();
   }
   return null;
 }
 /**
  * Plays a sound. This method returns immediately.
  */
 public InputStream play(Sound sound) {
   return play(sound, null, false);
 }
 /**
  * Plays a sound with an optional SoundFilter, and optionally looping. This
  * method returns immediately.
  */
 public InputStream play(Sound sound, SoundFilter filter, boolean loop) {
   InputStream is;
   if (sound != null) {
     if (loop) {
       is = new LoopingByteInputStream(sound.getSamples());
     } else {
       is = new ByteArrayInputStream(sound.getSamples());
     }
     return play(is, filter);
   }
   return null;
 }
 /**
  * Plays a sound from an InputStream. This method returns immediately.
  */
 public InputStream play(InputStream is) {
   return play(is, null);
 }
 /**
  * Plays a sound from an InputStream with an optional sound filter. This
  * method returns immediately.
  */
 public InputStream play(InputStream is, SoundFilter filter) {
   if (is != null) {
     if (filter != null) {
       is = new FilteredSoundStream(is, filter);
     }
     runTask(new SoundPlayer(is));
   }
   return is;
 }
 /**
  * Signals that a PooledThread has started. Creates the Thread"s line and
  * buffer.
  */
 protected void threadStarted() {
   // wait for the SoundManager constructor to finish
   synchronized (this) {
     try {
       wait();
     } catch (InterruptedException ex) {
     }
   }
   // use a short, 100ms (1/10th sec) buffer for filters that
   // change in real-time
   int bufferSize = playbackFormat.getFrameSize()
       * Math.round(playbackFormat.getSampleRate() / 10);
   // create, open, and start the line
   SourceDataLine line;
   DataLine.Info lineInfo = new DataLine.Info(SourceDataLine.class,
       playbackFormat);
   try {
     line = (SourceDataLine) AudioSystem.getLine(lineInfo);
     line.open(playbackFormat, bufferSize);
   } catch (LineUnavailableException ex) {
     // the line is unavailable - signal to end this thread
     Thread.currentThread().interrupt();
     return;
   }
   line.start();
   // create the buffer
   byte[] buffer = new byte[bufferSize];
   // set this thread"s locals
   localLine.set(line);
   localBuffer.set(buffer);
 }
 /**
  * Signals that a PooledThread has stopped. Drains and closes the Thread"s
  * Line.
  */
 protected void threadStopped() {
   SourceDataLine line = (SourceDataLine) localLine.get();
   if (line != null) {
     line.drain();
     line.close();
   }
 }
 /**
  * The SoundPlayer class is a task for the PooledThreads to run. It receives
  * the threads"s Line and byte buffer from the ThreadLocal variables and
  * plays a sound from an InputStream.
  * <p>
  * This class only works when called from a PooledThread.
  */
 protected class SoundPlayer implements Runnable {
   private InputStream source;
   public SoundPlayer(InputStream source) {
     this.source = source;
   }
   public void run() {
     // get line and buffer from ThreadLocals
     SourceDataLine line = (SourceDataLine) localLine.get();
     byte[] buffer = (byte[]) localBuffer.get();
     if (line == null || buffer == null) {
       // the line is unavailable
       return;
     }
     // copy data to the line
     try {
       int numBytesRead = 0;
       while (numBytesRead != -1) {
         // if paused, wait until unpaused
         synchronized (pausedLock) {
           if (paused) {
             try {
               pausedLock.wait();
             } catch (InterruptedException ex) {
               return;
             }
           }
         }
         // copy data
         numBytesRead = source.read(buffer, 0, buffer.length);
         if (numBytesRead != -1) {
           line.write(buffer, 0, numBytesRead);
         }
       }
     } catch (IOException ex) {
       ex.printStackTrace();
     }
   }
 }

} /**

* The LoopingByteInputStream is a ByteArrayInputStream that loops indefinitly.
* The looping stops when the close() method is called.
* <p>
* 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;
 }

} /**

* 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 Sound class is a container for sound samples. The sound samples are
* format-agnostic and are stored as a byte array.
*/

class Sound {

 private byte[] samples;
 /**
  * Create a new Sound object with the specified byte array. The array is not
  * copied.
  */
 public Sound(byte[] samples) {
   this.samples = samples;
 }
 /**
  * Returns this Sound"s objects samples as a byte array.
  */
 public byte[] getSamples() {
   return samples;
 }

} /**

* 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.
  * <p>
  * 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.
  * <p>
  * 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;
 }

} class MidiPlayer implements MetaEventListener {

 // Midi meta event
 public static final int END_OF_TRACK_MESSAGE = 47;
 private Sequencer sequencer;
 private boolean loop;
 private boolean paused;
 /**
  * Creates a new MidiPlayer object.
  */
 public MidiPlayer() {
   try {
     sequencer = MidiSystem.getSequencer();
     sequencer.open();
     sequencer.addMetaEventListener(this);
   } catch (MidiUnavailableException ex) {
     sequencer = null;
   }
 }
 /**
  * Loads a sequence from the file system. Returns null if an error occurs.
  */
 public Sequence getSequence(String filename) {
   try {
     return getSequence(new FileInputStream(filename));
   } catch (IOException ex) {
     ex.printStackTrace();
     return null;
   }
 }
 /**
  * Loads a sequence from an input stream. Returns null if an error occurs.
  */
 public Sequence getSequence(InputStream is) {
   try {
     if (!is.markSupported()) {
       is = new BufferedInputStream(is);
     }
     Sequence s = MidiSystem.getSequence(is);
     is.close();
     return s;
   } catch (InvalidMidiDataException ex) {
     ex.printStackTrace();
     return null;
   } catch (IOException ex) {
     ex.printStackTrace();
     return null;
   }
 }
 /**
  * Plays a sequence, optionally looping. This method returns immediately.
  * The sequence is not played if it is invalid.
  */
 public void play(Sequence sequence, boolean loop) {
   if (sequencer != null && sequence != null && sequencer.isOpen()) {
     try {
       sequencer.setSequence(sequence);
       sequencer.start();
       this.loop = loop;
     } catch (InvalidMidiDataException ex) {
       ex.printStackTrace();
     }
   }
 }
 /**
  * This method is called by the sound system when a meta event occurs. In
  * this case, when the end-of-track meta event is received, the sequence is
  * restarted if looping is on.
  */
 public void meta(MetaMessage event) {
   if (event.getType() == END_OF_TRACK_MESSAGE) {
     if (sequencer != null && sequencer.isOpen() && loop) {
       sequencer.start();
     }
   }
 }
 /**
  * Stops the sequencer and resets its position to 0.
  */
 public void stop() {
   if (sequencer != null && sequencer.isOpen()) {
     sequencer.stop();
     sequencer.setMicrosecondPosition(0);
   }
 }
 /**
  * Closes the sequencer.
  */
 public void close() {
   if (sequencer != null && sequencer.isOpen()) {
     sequencer.close();
   }
 }
 /**
  * Gets the sequencer.
  */
 public Sequencer getSequencer() {
   return sequencer;
 }
 /**
  * Sets the paused state. Music may not immediately pause.
  */
 public void setPaused(boolean paused) {
   if (this.paused != paused && sequencer != null && sequencer.isOpen()) {
     this.paused = paused;
     if (paused) {
       sequencer.stop();
     } else {
       sequencer.start();
     }
   }
 }
 /**
  * Returns the paused state.
  */
 public boolean isPaused() {
   return paused;
 }

} /**

* The TileMap class contains the data for a tile-based map, including Sprites.
* Each tile is a reference to an Image. Of course, Images are used multiple
* times in the tile map.
*/

class TileMap {

 private Image[][] tiles;
 private LinkedList sprites;
 private Sprite player;
 /**
  * Creates a new TileMap with the specified width and height (in number of
  * tiles) of the map.
  */
 public TileMap(int width, int height) {
   tiles = new Image[width][height];
   sprites = new LinkedList();
 }
 /**
  * Gets the width of this TileMap (number of tiles across).
  */
 public int getWidth() {
   return tiles.length;
 }
 /**
  * Gets the height of this TileMap (number of tiles down).
  */
 public int getHeight() {
   return tiles[0].length;
 }
 /**
  * Gets the tile at the specified location. Returns null if no tile is at
  * the location or if the location is out of bounds.
  */
 public Image getTile(int x, int y) {
   if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) {
     return null;
   } else {
     return tiles[x][y];
   }
 }
 /**
  * Sets the tile at the specified location.
  */
 public void setTile(int x, int y, Image tile) {
   tiles[x][y] = tile;
 }
 /**
  * Gets the player Sprite.
  */
 public Sprite getPlayer() {
   return player;
 }
 /**
  * Sets the player Sprite.
  */
 public void setPlayer(Sprite player) {
   this.player = player;
 }
 /**
  * Adds a Sprite object to this map.
  */
 public void addSprite(Sprite sprite) {
   sprites.add(sprite);
 }
 /**
  * Removes a Sprite object from this map.
  */
 public void removeSprite(Sprite sprite) {
   sprites.remove(sprite);
 }
 /**
  * Gets an Iterator of all the Sprites in this map, excluding the player
  * Sprite.
  */
 public Iterator getSprites() {
   return sprites.iterator();
 }

} class Sprite {

 protected 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();
 }
 /**
  * Clones this Sprite. Does not clone position or velocity info.
  */
 public Object clone() {
   return new Sprite(anim);
 }

}


 </source>