Mega Code Archive

 
Categories / Java / Game
 

Tetris Game

/**  * @(#)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.Component; 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.component = 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 <code>size</code>           * 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.Component for the board.       * @return the gui component for the board.       */      public Component getSquareBoardComponent()      {         return board.getComponent();      }            /**       * Gets the java.awt.Component 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.<p>    *     * 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);           }       }   }