Mega Code Archive

 
Categories / Java / J2ME
 

Dungeon game

/*  Title:  J2ME Games With MIDP2  Authors:  Carol Hamer  Publisher:  Apress  ISBN:   1590593820  */ import java.io.*; import javax.microedition.lcdui.*; import javax.microedition.lcdui.game.*; import javax.microedition.midlet.*; import javax.microedition.rms.*; /**  * This is the main class of the dungeon game.  *   * @author Carol Hamer  */ public class Dungeon extends MIDlet implements CommandListener {   //-----------------------------------------------------   //    game object fields   /**    * The canvas that the dungeon is drawn on.    */   private DungeonCanvas myCanvas;   /**    * the thread that advances the game clock.    */   private GameThread myGameThread;   //-----------------------------------------------------   //    command fields   /**    * The button to exit the game.    */   private Command myExitCommand = new Command("Exit", Command.EXIT, 99);   /**    * The command to save the game in progress.    */   private Command mySaveCommand = new Command("Save Game", Command.SCREEN, 2);   /**    * The command to restore a previously saved game.    */   private Command myRestoreCommand = new Command("Restore Game",       Command.SCREEN, 2);   /**    * the command to start moving when the game is paused.    */   private Command myGoCommand = new Command("Go", Command.SCREEN, 1);   /**    * the command to pause the game.    */   private Command myPauseCommand = new Command("Pause", Command.SCREEN, 1);   /**    * the command to start a new game.    */   private Command myNewCommand = new Command("Next Board", Command.SCREEN, 1);   //-----------------------------------------------------   //    initialization and game state changes   /**    * Initialize the canvas and the commands.    */   public Dungeon() {     try {       // create the canvas and set up the commands:       myCanvas = new DungeonCanvas(this);       myCanvas.addCommand(myExitCommand);       myCanvas.addCommand(mySaveCommand);       myCanvas.addCommand(myRestoreCommand);       myCanvas.addCommand(myPauseCommand);       myCanvas.setCommandListener(this);     } catch (Exception e) {       // if there's an error during creation, display it as an alert.       errorMsg(e);     }   }   /**    * Switch the command to the play again command. (removing other commands    * that are no longer relevant)    */   void setNewCommand() {     myCanvas.removeCommand(myPauseCommand);     myCanvas.removeCommand(myGoCommand);     myCanvas.addCommand(myNewCommand);   }   /**    * Switch the command to the go command. (removing other commands that are    * no longer relevant)    */   void setGoCommand() {     myCanvas.removeCommand(myPauseCommand);     myCanvas.removeCommand(myNewCommand);     myCanvas.addCommand(myGoCommand);   }   /**    * Switch the command to the pause command. (removing other commands that    * are no longer relevant)    */   void setPauseCommand() {     myCanvas.removeCommand(myNewCommand);     myCanvas.removeCommand(myGoCommand);     myCanvas.addCommand(myPauseCommand);   }   //----------------------------------------------------------------   //  implementation of MIDlet   // these methods may be called by the application management   // software at any time, so we always check fields for null   // before calling methods on them.   /**    * Start the application.    */   public void startApp() throws MIDletStateChangeException {     if (myCanvas != null) {       if (myGameThread == null) {         // create the thread and start the game:         myGameThread = new GameThread(myCanvas);         myCanvas.start();         myGameThread.start();       } else {         // in case this gets called again after         // the application has been started once:         myCanvas.removeCommand(myGoCommand);         myCanvas.addCommand(myPauseCommand);         myCanvas.flushKeys();         myGameThread.resumeGame();       }     }   }   /**    * Stop the threads and throw out the garbage.    */   public void destroyApp(boolean unconditional)       throws MIDletStateChangeException {     myCanvas = null;     if (myGameThread != null) {       myGameThread.requestStop();     }     myGameThread = null;     System.gc();   }   /**    * Pause the game.    */   public void pauseApp() {     if (myCanvas != null) {       setGoCommand();     }     if (myGameThread != null) {       myGameThread.pause();     }   }   //----------------------------------------------------------------   //  implementation of CommandListener   /*    * Respond to a command issued on the Canvas. (reset, exit, or change size    * prefs).    */   public void commandAction(Command c, Displayable s) {     try {       if (c == myGoCommand) {         myCanvas.setNeedsRepaint();         myCanvas.removeCommand(myGoCommand);         myCanvas.addCommand(myPauseCommand);         myCanvas.flushKeys();         myGameThread.resumeGame();       } else if (c == myPauseCommand) {         myCanvas.setNeedsRepaint();         myCanvas.removeCommand(myPauseCommand);         myCanvas.addCommand(myGoCommand);         myGameThread.pause();       } else if (c == myNewCommand) {         myCanvas.setNeedsRepaint();         // go to the next board and restart the game         myCanvas.removeCommand(myNewCommand);         myCanvas.addCommand(myPauseCommand);         myCanvas.reset();         myGameThread.resumeGame();       /*} else if (c == Alert.DISMISS_COMMAND) {         // if there was a serious enough error to         // cause an alert, then we end the game         // when the user is done reading the alert:         // (Alert.DISMISS_COMMAND is the default         // command that is placed on an Alert         // whose timeout is FOREVER)         destroyApp(false);         notifyDestroyed();*/       } else if (c == mySaveCommand) {         myCanvas.setNeedsRepaint();         myCanvas.saveGame();       } else if (c == myRestoreCommand) {         myCanvas.setNeedsRepaint();         myCanvas.removeCommand(myNewCommand);         myCanvas.removeCommand(myGoCommand);         myCanvas.addCommand(myPauseCommand);         myCanvas.revertToSaved();       } else if (c == myExitCommand) {         destroyApp(false);         notifyDestroyed();       }     } catch (Exception e) {       errorMsg(e);     }   }   //-------------------------------------------------------   //  error methods   /**    * Converts an exception to a message and displays the message..    */   void errorMsg(Exception e) {     if (e.getMessage() == null) {       errorMsg(e.getClass().getName());     } else {       errorMsg(e.getClass().getName() + ":" + e.getMessage());     }   }   /**    * Displays an error message alert if something goes wrong.    */   void errorMsg(String msg) {     Alert errorAlert = new Alert("error", msg, null, AlertType.ERROR);     errorAlert.setCommandListener(this);     errorAlert.setTimeout(Alert.FOREVER);     Display.getDisplay(this).setCurrent(errorAlert);   } } /**  * This class represents doors and keys.  *   * @author Carol Hamer  */ class DoorKey extends Sprite {   //---------------------------------------------------------   //    fields   /**    * The image file shared by all doors and keys.    */   public static Image myImage;   /**    * A code int that indicates the door or key's color.    */   private int myColor;   //---------------------------------------------------------   //    get/set data   /**    * @return the door or key's color.    */   public int getColor() {     return (myColor);   }   //---------------------------------------------------------   //    constructor and initializer   static {     try {       myImage = Image.createImage("/images/keys.png");     } catch (Exception e) {       throw (new RuntimeException(           "DoorKey.<init>-->failed to load image, caught "               + e.getClass() + ": " + e.getMessage()));     }   }   /**    * Standard constructor sets the image to the correct frame (according to    * whether this is a door or a key and what color it should be) and then    * puts it in the correct location.    */   public DoorKey(int color, boolean isKey, int[] gridCoordinates) {     super(myImage, DungeonManager.SQUARE_WIDTH, DungeonManager.SQUARE_WIDTH);     myColor = color;     int imageIndex = color * 2;     if (isKey) {       imageIndex++;     }     setFrame(imageIndex);     setPosition(gridCoordinates[0] * DungeonManager.SQUARE_WIDTH,         gridCoordinates[1] * DungeonManager.SQUARE_WIDTH);   } } /**  * This class is a set of simple utility functions that can be used to convert  * standard data types to bytes and back again. It is used especially for data  * storage, but also for sending and receiving data.  *   * @author Carol Hamer  */ class DataConverter {   //--------------------------------------------------------   //  utilities to encode small, compactly-stored small ints.   /**    * Encodes a coordinate pair into a byte.    *     * @param coordPair    *            a pair of integers to be compacted into a single byte for    *            storage. WARNING: each of the two values MUST BE between 0 and    *            15 (inclusive). This method does not verify the length of the    *            array (which must be 2!) nor does it verify that the ints are    *            of the right size.    */   public static byte encodeCoords(int[] coordPair) {     // get the byte value of the first coordinate:     byte retVal = (new Integer(coordPair[0])).byteValue();     // move the first coordinate's value up to the top     // half of the storage byte:     retVal = (new Integer(retVal << 4)).byteValue();     // store the second coordinate in the lower half     // of the byte:     retVal += (new Integer(coordPair[1])).byteValue();     return (retVal);   }   /**    * Encodes eight ints into a byte. This could be easily modified to encode    * eight booleans.    *     * @param eight    *            an array of at least eight ints. WARNING: all values must be 0    *            or 1! This method does not verify that the values are in the    *            correct range nor does it verify that the array is long    *            enough.    * @param offset    *            the index in the array eight to start reading data from.    *            (should usually be 0)    */   public static byte encode8(int[] eight, int offset) {     // get the byte value of the first int:     byte retVal = (new Integer(eight[offset])).byteValue();     // progressively move the data up one bit in the     // storage byte and then record the next int in     // the lowest spot in the storage byte:     for (int i = offset + 1; i < 8 + offset; i++) {       retVal = (new Integer(retVal << 1)).byteValue();       retVal += (new Integer(eight[i])).byteValue();     }     return (retVal);   }   //--------------------------------------------------------   //  utilities to decode small, compactly-stored small ints.   /**    * Turns a byte into a pair of coordinates.    */   public static int[] decodeCoords(byte coordByte) {     int[] retArray = new int[2];     // we perform a bitwise and with the value 15     // in order to just get the bits of the lower     // half of the byte:     retArray[1] = coordByte & 15;     // To get the bits of the upper half of the     // byte, we perform a shift to move them down:     retArray[0] = coordByte >> 4;     // bytes in Java are generally assumed to be     // signed, but in this coding algorithm we     // would like to treat them as unsigned:     if (retArray[0] < 0) {       retArray[0] += 16;     }     return (retArray);   }   /**    * Turns a byte into eight ints.    */   public static int[] decode8(byte data) {     int[] retArray = new int[8];     // The flag allows us to look at each bit individually     // to determine if it is 1 or 0. The number 128     // corresponds to the highest bit of a byte, so we     // start with that one.     int flag = 128;     // We use a loop that checks     // the data bit by bit by performing a bitwise     // and (&) between the data byte and a flag:     for (int i = 0; i < 8; i++) {       if ((flag & data) != 0) {         retArray[i] = 1;       } else {         retArray[i] = 0;       }       // move the flag down one bit so that we can       // check the next bit of data on the next pass       // through the loop:       flag = flag >> 1;     }     return (retArray);   }   //--------------------------------------------------------   //  standard integer interpretation   /**    * Uses an input stream to convert an array of bytes to an int.    */   public static int parseInt(byte[] data) throws IOException {     DataInputStream stream = new DataInputStream(new ByteArrayInputStream(         data));     int retVal = stream.readInt();     stream.close();     return (retVal);   }   /**    * Uses an output stream to convert an int to four bytes.    */   public static byte[] intToFourBytes(int i) throws IOException {     ByteArrayOutputStream baos = new ByteArrayOutputStream(4);     DataOutputStream dos = new DataOutputStream(baos);     dos.writeInt(i);     baos.close();     dos.close();     byte[] retArray = baos.toByteArray();     return (retArray);   }   //--------------------------------------------------------   //  integer interpretation illustrated   /**    * Java appears to treat a byte as being signed when returning it as an    * int--this function converts from the signed value to the corresponding    * unsigned value. This method is used by nostreamParseInt.    */   public static int unsign(int signed) {     int retVal = signed;     if (retVal < 0) {       retVal += 256;     }     return (retVal);   }   /**    * Takes an array of bytes and returns an int. This version will return the    * same value as the method parseInt above. This version is included in    * order to illustrate how Java encodes int values in terms of bytes.    *     * @param data    *            an array of 1, 2, or 4 bytes.    */   public static int nostreamParseInt(byte[] data) {     // byte 0 is the high byte which is assumed     // to be signed. As we add the lower bytes     // one by one, we unsign them because because     // a single byte alone is interpreted as signed,     // but in an int only the top byte should be signed.     // (note that the high byte is the first one in the array)     int retVal = data[0];     for (int i = 1; i < data.length; i++) {       retVal = retVal << 8;       retVal += unsign(data[i]);     }     return (retVal);   }   /**    * Takes an arbitrary int and returns an array of four bytes. This version    * will return the same byte array as the method intToFourBytes above. This    * version is included in order to illustrate how Java encodes int values in    * terms of bytes.    */   public static byte[] nostreamIntToFourBytes(int i) {     byte[] fourBytes = new byte[4];     // when you take the byte value of an int, it     // only gives you the lowest byte. So we     // get all four bytes by taking the lowest     // byte four times and moving the whole int     // down by one byte between each one.     // (note that the high byte is the first one in the array)     fourBytes[3] = (new Integer(i)).byteValue();     i = i >> 8;     fourBytes[2] = (new Integer(i)).byteValue();     i = i >> 8;     fourBytes[1] = (new Integer(i)).byteValue();     i = i >> 8;     fourBytes[0] = (new Integer(i)).byteValue();     return (fourBytes);   }   /**    * Takes an int between -32768 and 32767 and returns an array of two bytes.    * This does not verify that the argument is of the right size. If the    * absolute value of i is too high, it will not be encoded correctly.    */   public static byte[] nostreamIntToTwoBytes(int i) {     byte[] twoBytes = new byte[2];     // when you take the byte value of an int, it     // only gives you the lowest byte. So we     // get the lower two bytes by taking the lowest     // byte twice and moving the whole int     // down by one byte between each one.     twoBytes[1] = (new Integer(i)).byteValue();     i = i >> 8;     twoBytes[0] = (new Integer(i)).byteValue();     return (twoBytes);   } } /**  * This class contains the data for the map of the dungeon..  *   * @author Carol Hamer  */ class BoardDecoder {   //--------------------------------------------------------   //  fields   /**    * The coordinates of where the player starts on the map in terms of the    * array indices.    */   private int[] myPlayerSquare;   /**    * The coordinates of the goal (crown).    */   private int[] myGoalSquare;   /**    * The coordinates of the doors. the there should be two in a row of each    * color, following the same sequence as the keys.    */   private int[][] myDoors;   /**    * The coordinates of the Keys. the there should be of each color, following    * the same sequence as the doors.    */   private int[][] myKeys;   /**    * The coordinates of the stone walls of the maze, encoded bit by bit.    */   private TiledLayer myLayer;   /**    * The data in bytes that gives the various boards. This was created using    * EncodingUtils... This is a two-dimensional array: Each of the four main    * sections corresponds to one of the four possible boards.    */   private static byte[][] myData = {       { 0, 0, -108, -100, -24, 65, 21, 58, 53, -54, -116, -58, -56, -84,           115, -118, -1, -1, -128, 1, -103, -15, -128, 25, -97, -127,           -128, 79, -14, 1, -126, 121, -122, 1, -113, -49, -116, 1,           -100, -3, -124, 5, -25, -27, -128, 1, -1, -1 },       { 0, 1, 122, 90, -62, 34, -43, 72, -59, -29, 56, -55, 98, 126, -79,           61, -1, -1, -125, 1, -128, 17, -26, 29, -31, 57, -72, 1,           -128, -51, -100, 65, -124, 57, -2, 1, -126, 13, -113, 1,           -97, 25, -127, -99, -8, 1, -1, -1 },       { 0, 2, 108, -24, 18, -26, 102, 30, -58, 46, -28, -88, 34, -98, 97,           -41, -1, -1, -96, 1, -126, 57, -9, 97, -127, 69, -119, 73,           -127, 1, -109, 59, -126, 1, -26, 103, -127, 65, -103, 115,           -127, 65, -25, 73, -128, 1, -1, -1 },       { 0, 3, -114, 18, -34, 27, -39, -60, -76, -50, 118, 90, 82, -88,           34, -74, -1, -1, -66, 1, -128, 121, -26, 125, -128, -123,           -103, 29, -112, 1, -109, 49, -112, 1, -116, -31, -128, 5,           -122, 5, -32, 13, -127, -51, -125, 1, -1, -1 }, };   //--------------------------------------------------------   //  initialization   /**    * Constructor fills data fields by interpreting the data bytes.    */   public BoardDecoder(int boardNum) throws Exception {     // we start by selecting the two dimensional     // array corresponding to the desired board:     byte[] data = myData[boardNum];     // The first two bytes give the version number and     // the board number, but we ignore them because     // they are assumed to be correct.     // The third byte of the first array is the first one     // we read: it gives the player's starting coordinates:     myPlayerSquare = DataConverter.decodeCoords(data[2]);     // the next byte gives the coordinates of the crown:     myGoalSquare = DataConverter.decodeCoords(data[3]);     // the next four bytes give the coordinates of the keys:     myKeys = new int[4][];     for (int i = 0; i < myKeys.length; i++) {       myKeys[i] = DataConverter.decodeCoords(data[i + 4]);     }     // the next eight bytes give the coordinates of the doors:     myDoors = new int[8][];     for (int i = 0; i < myDoors.length; i++) {       myDoors[i] = DataConverter.decodeCoords(data[i + 8]);     }     // now we create the TiledLayer object that is the     // background dungeon map:     myLayer = new TiledLayer(16, 16,         Image.createImage("/images/stone.png"),         DungeonManager.SQUARE_WIDTH, DungeonManager.SQUARE_WIDTH);     // now we call an internal utility that reads the array     // of data that gives the positions of the blocks in the     // walls of this dungeon:     decodeDungeon(data, myLayer, 16);   }   //--------------------------------------------------------   //  get/set data   /**    * @return the number of boards currently stored in this class.    */   public static int getNumBoards() {     return (myData.length);   }   /**    * get the coordinates of where the player starts on the map in terms of the    * array indices.    */   public int[] getPlayerSquare() {     return (myPlayerSquare);   }   /**    * get the coordinates of the goal crown in terms of the array indices.    */   public int[] getGoalSquare() {     return (myGoalSquare);   }   /**    * get the tiled layer that gives the map of the dungeon.    */   public TiledLayer getLayer() {     return (myLayer);   }   /**    * Creates the array of door sprites. (call this only once to avoid creating    * redundant sprites).    */   DoorKey[] createDoors() {     DoorKey[] retArray = new DoorKey[8];     for (int i = 0; i < 4; i++) {       retArray[2 * i] = new DoorKey(i, false, myDoors[2 * i]);       retArray[2 * i + 1] = new DoorKey(i, false, myDoors[2 * i + 1]);     }     return (retArray);   }   /**    * Creates the array of key sprites. (call this only once to avoid creating    * redundant sprites.)    */   DoorKey[] createKeys() {     DoorKey[] retArray = new DoorKey[4];     for (int i = 0; i < 4; i++) {       retArray[i] = new DoorKey(i, true, myKeys[i]);     }     return (retArray);   }   //--------------------------------------------------------   //  decoding utilities   /**    * Takes a dungeon given as a byte array and uses it to set the tiles of a    * tiled layer.    *     * The TiledLayer in this case is a 16 x 16 grid in which each square can be    * either blank (value of 0) or can be filled with a stone block (value of    * 1). Therefore each square requires only one bit of information. Each byte    * of data in the array called "data" records the frame indices of eight    * squares in the grid.    */   private static void decodeDungeon(byte[] data, TiledLayer dungeon,       int offset) throws Exception {     if (data.length + offset < 32) {       throw (new Exception(           "BoardDecoder.decodeDungeon-->not enough data!!!"));     }     // a frame index of zero indicates a blank square     // (this is always true in a TiledLayer).     // This TiledLayer has only one possible (non-blank)     // frame, so a frame index of 1 indicates a stone block     int frame = 0;     // Each of the 32 bytes in the data array records     // the frame indices of eight block in the 16 x 16     // grid. Two bytes give one row of the dungeon,     // so we have the array index go from zero to 16     // to set the frame indices fro each of the 16 rows.     for (int i = 0; i < 16; i++) {       // The flag allows us to look at each bit individually       // to determine if it is 1 or 0. The number 128       // corresponds to the highest bit of a byte, so we       // start with that one.       int flag = 128;       // Here we check two bytes at the same time       // (the two bytes together correspond to one row       // of the dungeon). We use a loop that checks       // the bytes bit by bit by performing a bitwise       // and (&) between the data byte and a flag:       for (int j = 0; j < 8; j++) {         if ((data[offset + 2 * i] & flag) != 0) {           frame = 1;         } else {           frame = 0;         }         dungeon.setCell(j, i, frame);         if ((data[offset + 2 * i + 1] & flag) != 0) {           frame = 1;         } else {           frame = 0;         }         dungeon.setCell(j + 8, i, frame);         // move the flag down one bit so that we can         // check the next bit of data on the next pass         // through the loop:         flag = flag >> 1;       }     }   } } /**  * This class contains the loop that keeps the game running.  *   * @author Carol Hamer  */ class GameThread extends Thread {   //---------------------------------------------------------   //   fields   /**    * Whether or not the main thread would like this thread to pause.    */   private boolean myShouldPause;   /**    * Whether or not the main thread would like this thread to stop.    */   private static boolean myShouldStop;   /**    * A handle back to the graphical components.    */   private DungeonCanvas myDungeonCanvas;   /**    * The System.time of the last screen refresh, used to regulate refresh    * speed.    */   private long myLastRefreshTime;   //----------------------------------------------------------   //   initialization   /**    * standard constructor.    */   GameThread(DungeonCanvas canvas) {     myDungeonCanvas = canvas;   }   //----------------------------------------------------------   //   utilities   /**    * Get the amount of time to wait between screen refreshes. Normally we wait    * only a single millisecond just to give the main thread a chance to update    * the keystroke info, but this method ensures that the game will not    * attempt to show too many frames per second.    */   private long getWaitTime() {     long retVal = 1;     long difference = System.currentTimeMillis() - myLastRefreshTime;     if (difference < 75) {       retVal = 75 - difference;     }     return (retVal);   }   //----------------------------------------------------------   //   actions   /**    * pause the game.    */   void pause() {     myShouldPause = true;   }   /**    * restart the game after a pause.    */   synchronized void resumeGame() {     myShouldPause = false;     notify();   }   /**    * stops the game.    */   synchronized void requestStop() {     myShouldStop = true;     this.notify();   }   /**    * start the game..    */   public void run() {     // flush any keystrokes that occurred before the     // game started:     myDungeonCanvas.flushKeys();     myShouldStop = false;     myShouldPause = false;     while (true) {       myLastRefreshTime = System.currentTimeMillis();       if (myShouldStop) {         break;       }       myDungeonCanvas.checkKeys();       myDungeonCanvas.updateScreen();       // we do a very short pause to allow the other thread       // to update the information about which keys are pressed:       synchronized (this) {         try {           wait(getWaitTime());         } catch (Exception e) {         }       }       if (myShouldPause) {         synchronized (this) {           try {             wait();           } catch (Exception e) {           }         }       }     }   } } /**  * This class contains the data for a game currently in progress. used to store  * a game and to resume a stored game.  *   * @author Carol Hamer  */ class GameInfo {   //--------------------------------------------------------   //  fields   /**    * The name of the datastore.    */   public static final String STORE = "GameInfo";   /**    * This is set to true if an attempt is made to read a game when no game has    * been saved.    */   private boolean myNoDataSaved;   /**    * The number that indicates which board the player is currently on.    */   private int myBoardNum;   /**    * The amount of time that has passed.    */   private int myTime;   /**    * The coordinates of where the player is on the board. coordinate values    * must be between 0 and 15.    */   private int[] myPlayerSquare;   /**    * The coordinates of where the keys are currently found. MUST BE four sets    * of two integer coordinates. coordinate values must be between 0 and 15.    */   private int[][] myKeyCoords;   /**    * The list of which doors are currently open. 0 = open 1 = closed WARNING:    * this array MUST have length 8.    */   private int[] myDoorsOpen;   /**    * The number of the key that is currently being held by the player. if no    * key is held, then the value is -1.    */   private int myHeldKey;   //--------------------------------------------------------   //  data gets/sets   /**    * @return true if no saved game records were found.    */   boolean getIsEmpty() {     return (myNoDataSaved);   }   /**    * @return The number that indicates which board the player is currently on.    */   int getBoardNum() {     return (myBoardNum);   }   /**    * @return The number of the key that is currently being held by the player.    *         if no key is held, then the value is -1.    */   int getHeldKey() {     return (myHeldKey);   }   /**    * @return The amount of time that has passed.    */   int getTime() {     return (myTime);   }   /**    * @return The coordinates of where the player is on the board. coordinate    *         values must be between 0 and 15.    */   int[] getPlayerSquare() {     return (myPlayerSquare);   }   /**    * @return The coordinates of where the keys are currently found. MUST BE    *         four sets of two integer coordinates. coordinate values must be    *         between 0 and 15.    */   int[][] getKeyCoords() {     return (myKeyCoords);   }   /**    * @return The list of which doors are currently open. 0 = open 1 = closed    *         WARNING: this array MUST have length 8.    */   int[] getDoorsOpen() {     return (myDoorsOpen);   }   //--------------------------------------------------------   //  constructors   /**    * This constructor records the game info of a game currently in progress.    */   GameInfo(int boardNum, int time, int[] playerSquare, int[][] keyCoords,       int[] doorsOpen, int heldKey) throws Exception {     myBoardNum = boardNum;     myTime = time;     myPlayerSquare = playerSquare;     myKeyCoords = keyCoords;     myDoorsOpen = doorsOpen;     myHeldKey = heldKey;     encodeInfo();   }   /**    * This constructor reads the game configuration from memory. This is used    * to reconstruct a saved game.    */   GameInfo() {     RecordStore store = null;     try {       // if the record store does not yet exist, don't       // create it       store = RecordStore.openRecordStore(STORE, false);       if ((store != null) && (store.getNumRecords() > 0)) {         // the first record has id number 1         // it should also be the only record since this         // particular game stores only one game.         byte[] data = store.getRecord(1);         myBoardNum = data[0];         myPlayerSquare = DataConverter.decodeCoords(data[1]);         myKeyCoords = new int[4][];         myKeyCoords[0] = DataConverter.decodeCoords(data[2]);         myKeyCoords[1] = DataConverter.decodeCoords(data[3]);         myKeyCoords[2] = DataConverter.decodeCoords(data[4]);         myKeyCoords[3] = DataConverter.decodeCoords(data[5]);         myDoorsOpen = DataConverter.decode8(data[6]);         myHeldKey = data[7];         byte[] fourBytes = new byte[4];         System.arraycopy(data, 8, fourBytes, 0, 4);         myTime = DataConverter.parseInt(fourBytes);       } else {         myNoDataSaved = true;       }     } catch (Exception e) {       // this throws when the record store doesn't exist.       // for that or any error, we assume no data is saved:       myNoDataSaved = true;     } finally {       try {         if (store != null) {           store.closeRecordStore();         }       } catch (Exception e) {         // if the record store is open this shouldn't throw.       }     }   }   //--------------------------------------------------------   //  encoding method   /**    * Turn the data into a byte array and save it.    */   private void encodeInfo() throws Exception {     RecordStore store = null;     try {       byte[] data = new byte[12];       data[0] = (new Integer(myBoardNum)).byteValue();       data[1] = DataConverter.encodeCoords(myPlayerSquare);       data[2] = DataConverter.encodeCoords(myKeyCoords[0]);       data[3] = DataConverter.encodeCoords(myKeyCoords[1]);       data[4] = DataConverter.encodeCoords(myKeyCoords[2]);       data[5] = DataConverter.encodeCoords(myKeyCoords[3]);       data[6] = DataConverter.encode8(myDoorsOpen, 0);       data[7] = (new Integer(myHeldKey)).byteValue();       byte[] timeBytes = DataConverter.intToFourBytes(myTime);       System.arraycopy(timeBytes, 0, data, 8, 4);       // if the record store does not yet exist, the second       // arg "true" tells it to create.       store = RecordStore.openRecordStore(STORE, true);       int numRecords = store.getNumRecords();       if (numRecords > 0) {         store.setRecord(1, data, 0, data.length);       } else {         store.addRecord(data, 0, data.length);       }     } catch (Exception e) {       throw (e);     } finally {       try {         if (store != null) {           store.closeRecordStore();         }       } catch (Exception e) {         // if the record store is open this shouldn't throw.       }     }   } } /**  * This class contains the data for the map of the dungeon. This is a utility  * class that allows a developer to write the data for a board in a simple  * format, then this class encodes the data in a format that the game can use.  *   * note that the data that this class encodes is hard-coded. that is because  * this class is intended to be used only a few times to encode the data. Once  * the board data has been encoded, it never needs to be encoded again. The  * encoding methods used in this class could be generalized to be used to create  * a board editor which would allow a user to easily create new boards, but that  * is an exercise for another day...  *   * @author Carol Hamer  */ class EncodingUtils {   //--------------------------------------------------------   //  fields   /**    * data for which squares are filled and which are blank. 0 = empty 1 =    * filled    */   private int[][] mySquares = {       { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },       { 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1 },       { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1 },       { 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1 },       { 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1 },       { 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },       { 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1 },       { 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1 },       { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1 },       { 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1 },       { 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1 },       { 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1 },       { 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1 },       { 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1 },       { 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },       { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, };   /**    * The coordinates of where the player starts on the map in terms of the    * array indices.    */   private int[] myPlayerSquare = { 7, 10 };   /**    * The coordinates of the goal (crown).    */   private int[] myGoalSquare = { 5, 10 };   //--------------------------------------------------------   //  get/set data   /**    * Creates the array of door sprites. (call this only once to avoid creating    * redundant sprites).    */   int[][] getDoorCoords() {     int[][] retArray = new int[8][];     for (int i = 0; i < retArray.length; i++) {       retArray[i] = new int[2];     }     // red     retArray[0][0] = 12;     retArray[0][1] = 5;     retArray[1][0] = 14;     retArray[1][1] = 3;     // green     retArray[2][0] = 3;     retArray[2][1] = 8;     retArray[3][0] = 12;     retArray[3][1] = 9;     // blue     retArray[4][0] = 6;     retArray[4][1] = 2;     retArray[5][0] = 7;     retArray[5][1] = 14;     // yellow     retArray[6][0] = 11;     retArray[6][1] = 1;     retArray[7][0] = 3;     retArray[7][1] = 13;     return (retArray);   }   /**    * Creates the array of key sprites. (call this only once to avoid creating    * redundant sprites.)    */   int[][] getKeyCoords() {     int[][] retArray = new int[4][];     for (int i = 0; i < retArray.length; i++) {       retArray[i] = new int[2];     }     // red     retArray[0][0] = 12;     retArray[0][1] = 2;     // green     retArray[1][0] = 2;     retArray[1][1] = 2;     // blue     retArray[2][0] = 13;     retArray[2][1] = 5;     // yellow     retArray[3][0] = 4;     retArray[3][1] = 8;     return (retArray);   }   //--------------------------------------------------------   //  encoding / decoding utilities   /**    * Encodes the entire dungeon.    */   byte[][] encodeDungeon() {     byte[][] retArray = new byte[2][];     retArray[0] = new byte[16];     // the first byte is the version number:     retArray[0][0] = 0;     // the second byte is the board number:     retArray[0][1] = 0;     // the player's start square:     retArray[0][2] = DataConverter.encodeCoords(myPlayerSquare);     // the goal (crown) square:     retArray[0][3] = DataConverter.encodeCoords(myGoalSquare);     //encode the keys:     int[][] keyCoords = getKeyCoords();     for (int i = 0; i < keyCoords.length; i++) {       retArray[0][i + 4] = DataConverter.encodeCoords(keyCoords[i]);     }     //encode the doors:     int[][] doorCoords = getDoorCoords();     for (int i = 0; i < doorCoords.length; i++) {       retArray[0][i + 8] = DataConverter.encodeCoords(doorCoords[i]);     }     //encode the maze:     try {       retArray[1] = encodeDungeon(mySquares);     } catch (Exception e) {       e.printStackTrace();     }     return (retArray);   }   /**    * Takes a dungeon given in terms of an array of 1s and 0s and turns it into    * an array of bytes. WARNING: the array MUST BE 16 X 16.    */   static byte[] encodeDungeon(int[][] dungeonMap) throws Exception {     if ((dungeonMap.length != 16) || (dungeonMap[0].length != 16)) {       throw (new Exception(           "EncodingUtils.encodeDungeon-->must be 16x16!!!"));     }     byte[] retArray = new byte[32];     for (int i = 0; i < 16; i++) {       retArray[2 * i] = DataConverter.encode8(dungeonMap[i], 0);       retArray[2 * i + 1] = DataConverter.encode8(dungeonMap[i], 8);     }     return (retArray);   }   //--------------------------------------------------------   //  main prints the bytes to standard out.   // (note that this class is not intended to be run as a MIDlet)   /**    * Prints the byte version of the board to standard out.    */   public static void main(String[] args) {     try {       EncodingUtils map = new EncodingUtils();       byte[][] data = map.encodeDungeon();       System.out.println("EncodingUtils.main-->dungeon encoded");       System.out.print("{\n   " + data[0][0]);       for (int i = 1; i < data[0].length; i++) {         System.out.print(", " + data[0][i]);       }       for (int i = 1; i < data[1].length; i++) {         System.out.print(", " + data[1][i]);       }       System.out.println("\n};");     } catch (Exception e) {       e.printStackTrace();     }   } } /**  * This class handles the graphics objects.  *   * @author Carol Hamer  */ class DungeonManager extends LayerManager {   //---------------------------------------------------------   //   dimension fields   //  (constant after initialization)   /**    * The x-coordinate of the place on the game canvas where the LayerManager    * window should appear, in terms of the coordiantes of the game canvas.    */   static int CANVAS_X;   /**    * The y-coordinate of the place on the game canvas where the LayerManager    * window should appear, in terms of the coordiantes of the game canvas.    */   static int CANVAS_Y;   /**    * The width of the display window.    */   static int DISP_WIDTH;   /**    * The height of this object's visible region.    */   static int DISP_HEIGHT;   /**    * the (right or left) distance the player goes in a single keystroke.    */   static final int MOVE_LENGTH = 8;   /**    * The width of the square tiles that this game is divided into. This is the    * width of the stone walls as well as the princess and the ghost.    */   static final int SQUARE_WIDTH = 24;   /**    * The jump index that indicates that no jump is currently in progress..    */   static final int NO_JUMP = -6;   /**    * The maximum speed for the player's fall..    */   static final int MAX_FREE_FALL = 3;   //---------------------------------------------------------   //   game object fields   /**    * the handle back to the canvas.    */   private DungeonCanvas myCanvas;   /**    * the background dungeon.    */   private TiledLayer myBackground;   /**    * the player.    */   private Sprite myPrincess;   /**    * the goal.    */   private Sprite myCrown;   /**    * the doors.    */   private DoorKey[] myDoors;   /**    * the keys.    */   private DoorKey[] myKeys;   /**    * the key currently held by the player.    */   private DoorKey myHeldKey;   /**    * The leftmost x-coordinate that should be visible on the screen in terms    * of this objects internal coordinates.    */   private int myViewWindowX;   /**    * The top y-coordinate that should be visible on the screen in terms of    * this objects internal coordinates.    */   private int myViewWindowY;   /**    * Where the princess is in the jump sequence.    */   private int myIsJumping = NO_JUMP;   /**    * Whether or not the screen needs to be repainted.    */   private boolean myModifiedSinceLastPaint = true;   /**    * Which board we're playing on.    */   private int myCurrentBoardNum = 0;   //-----------------------------------------------------   //    gets/sets   /**    * Tell the layer manager that it needs to repaint.    */   public void setNeedsRepaint() {     myModifiedSinceLastPaint = true;   }   //-----------------------------------------------------   //    initialization   //    set up or save game data.   /**    * Constructor merely sets the data.    *     * @param x    *            The x-coordinate of the place on the game canvas where the    *            LayerManager window should appear, in terms of the coordiantes    *            of the game canvas.    * @param y    *            The y-coordinate of the place on the game canvas where the    *            LayerManager window should appear, in terms of the coordiantes    *            of the game canvas.    * @param width    *            the width of the region that is to be occupied by the    *            LayoutManager.    * @param height    *            the height of the region that is to be occupied by the    *            LayoutManager.    * @param canvas    *            the DungeonCanvas that this LayerManager should appear on.    */   public DungeonManager(int x, int y, int width, int height,       DungeonCanvas canvas) throws Exception {     myCanvas = canvas;     CANVAS_X = x;     CANVAS_Y = y;     DISP_WIDTH = width;     DISP_HEIGHT = height;     // create a decoder object that creates the dungeon and     // its associated Sprites from data.     BoardDecoder decoder = new BoardDecoder(myCurrentBoardNum);     // get the background TiledLayer     myBackground = decoder.getLayer();     // get the coordinates of the square that the princess     // starts on.     int[] playerCoords = decoder.getPlayerSquare();     // create the player sprite     myPrincess = new Sprite(Image.createImage("/images/princess.png"),         SQUARE_WIDTH, SQUARE_WIDTH);     myPrincess.setFrame(1);     // we define the reference pixel to be in the middle     // of the princess image so that when the princess turns     // from right to left (and vice versa) she does not     // appear to move to a different location.     myPrincess.defineReferencePixel(SQUARE_WIDTH / 2, 0);     // the dungeon is a 16x16 grid, so the array playerCoords     // gives the player's location in terms of the grid, and     // then we multiply those coordinates by the SQUARE_WIDTH     // to get the precise pixel where the player should be     // placed (in terms of the LayerManager's coordinate system)     myPrincess.setPosition(SQUARE_WIDTH * playerCoords[0], SQUARE_WIDTH         * playerCoords[1]);     // we append all of the Layers (TiledLayer and Sprite)     // so that this LayerManager will paint them when     // flushGraphics is called.     append(myPrincess);     // get the coordinates of the square where the crown     // should be placed.     int[] goalCoords = decoder.getGoalSquare();     myCrown = new Sprite(Image.createImage("/images/crown.png"));     myCrown.setPosition(         (SQUARE_WIDTH * goalCoords[0]) + (SQUARE_WIDTH / 4),         (SQUARE_WIDTH * goalCoords[1]) + (SQUARE_WIDTH / 2));     append(myCrown);     // The decoder creates the door and key sprites and places     // them in the correct locations in terms of the LayerManager's     // coordinate system.     myDoors = decoder.createDoors();     myKeys = decoder.createKeys();     for (int i = 0; i < myDoors.length; i++) {       append(myDoors[i]);     }     for (int i = 0; i < myKeys.length; i++) {       append(myKeys[i]);     }     // append the background last so it will be painted first.     append(myBackground);     // this sets the view screen so that the player is     // in the center.     myViewWindowX = SQUARE_WIDTH * playerCoords[0]         - ((DISP_WIDTH - SQUARE_WIDTH) / 2);     myViewWindowY = SQUARE_WIDTH * playerCoords[1]         - ((DISP_HEIGHT - SQUARE_WIDTH) / 2);     // a number of objects are created in order to set up the game,     // but they should be eliminated to free up memory:     decoder = null;     System.gc();   }   /**    * sets all variables back to their initial positions.    */   void reset() throws Exception {     // first get rid of the old board:     for (int i = 0; i < myDoors.length; i++) {       remove(myDoors[i]);     }     myHeldKey = null;     for (int i = 0; i < myKeys.length; i++) {       remove(myKeys[i]);     }     remove(myBackground);     // now create the new board:     myCurrentBoardNum++;     // in this version we go back to the beginning if     // all boards have been completed.     if (myCurrentBoardNum == BoardDecoder.getNumBoards()) {       myCurrentBoardNum = 0;     }     // we create a new decoder object to read and interpret     // all of the data for the current board.     BoardDecoder decoder = new BoardDecoder(myCurrentBoardNum);     // get the background TiledLayer     myBackground = decoder.getLayer();     // get the coordinates of the square that the princess     // starts on.     int[] playerCoords = decoder.getPlayerSquare();     // the dungeon is a 16x16 grid, so the array playerCoords     // gives the player's location in terms of the grid, and     // then we multiply those coordinates by the SQUARE_WIDTH     // to get the precise pixel where the player should be     // placed (in terms of the LayerManager's coordinate system)     myPrincess.setPosition(SQUARE_WIDTH * playerCoords[0], SQUARE_WIDTH         * playerCoords[1]);     myPrincess.setFrame(1);     // get the coordinates of the square where the crown     // should be placed.     int[] goalCoords = decoder.getGoalSquare();     myCrown.setPosition(         (SQUARE_WIDTH * goalCoords[0]) + (SQUARE_WIDTH / 4),         (SQUARE_WIDTH * goalCoords[1]) + (SQUARE_WIDTH / 2));     // The decoder creates the door and key sprites and places     // them in the correct locations in terms of the LayerManager's     // coordinate system.     myDoors = decoder.createDoors();     myKeys = decoder.createKeys();     for (int i = 0; i < myDoors.length; i++) {       append(myDoors[i]);     }     for (int i = 0; i < myKeys.length; i++) {       append(myKeys[i]);     }     // append the background last so it will be painted first.     append(myBackground);     // this sets the view screen so that the player is     // in the center.     myViewWindowX = SQUARE_WIDTH * playerCoords[0]         - ((DISP_WIDTH - SQUARE_WIDTH) / 2);     myViewWindowY = SQUARE_WIDTH * playerCoords[1]         - ((DISP_HEIGHT - SQUARE_WIDTH) / 2);     // a number of objects are created in order to set up the game,     // but they should be eliminated to free up memory:     decoder = null;     System.gc();   }   /**    * sets all variables back to the position in the saved game.    *     * @return the time on the clock of the saved game.    */   int revertToSaved() throws Exception {     int retVal = 0;     // first get rid of the old board:     for (int i = 0; i < myDoors.length; i++) {       remove(myDoors[i]);     }     myHeldKey = null;     for (int i = 0; i < myKeys.length; i++) {       remove(myKeys[i]);     }     remove(myBackground);     // now get the info of the saved game     // only one game is saved at a time, and the GameInfo object     // will read the saved game's data from memory.     GameInfo info = new GameInfo();     if (info.getIsEmpty()) {       // if no game has been saved, we start from the beginning.       myCurrentBoardNum = 0;       reset();     } else {       // get the time on the clock of the saved game.       retVal = info.getTime();       // get the number of the board the saved game was on.       myCurrentBoardNum = info.getBoardNum();       // create the BoradDecoder that gives the data for the       // desired board.       BoardDecoder decoder = new BoardDecoder(myCurrentBoardNum);       // get the background TiledLayer       myBackground = decoder.getLayer();       // get the coordinates of the square that the princess       // was on in the saved game.       int[] playerCoords = info.getPlayerSquare();       myPrincess.setPosition(SQUARE_WIDTH * playerCoords[0], SQUARE_WIDTH           * playerCoords[1]);       myPrincess.setFrame(1);       // get the coordinates of the square where the crown       // should be placed (this is given by the BoardDecoder       // and not from the data of the saved game because the       // crown does not move during the game.       int[] goalCoords = decoder.getGoalSquare();       myCrown.setPosition((SQUARE_WIDTH * goalCoords[0])           + (SQUARE_WIDTH / 4), (SQUARE_WIDTH * goalCoords[1])           + (SQUARE_WIDTH / 2));       // The decoder creates the door and key sprites and places       // them in the correct locations in terms of the LayerManager's       // coordinate system.       myDoors = decoder.createDoors();       myKeys = decoder.createKeys();       // get an array of ints that lists whether each door is       // open or closed in the saved game       int[] openDoors = info.getDoorsOpen();       for (int i = 0; i < myDoors.length; i++) {         append(myDoors[i]);         if (openDoors[i] == 0) {           // if the door was open, make it invisible           myDoors[i].setVisible(false);         }       }       // the keys can be moved by the player, so we get their       // coordinates from the GameInfo saved data.       int[][] keyCoords = info.getKeyCoords();       for (int i = 0; i < myKeys.length; i++) {         append(myKeys[i]);         myKeys[i].setPosition(SQUARE_WIDTH * keyCoords[i][0],             SQUARE_WIDTH * keyCoords[i][1]);       }       // if the player was holding a key in the saved game,       // we have the player hold that key and set it to invisible.       int heldKey = info.getHeldKey();       if (heldKey != -1) {         myHeldKey = myKeys[heldKey];         myHeldKey.setVisible(false);       }       // append the background last so it will be painted first.       append(myBackground);       // this sets the view screen so that the player is       // in the center.       myViewWindowX = SQUARE_WIDTH * playerCoords[0]           - ((DISP_WIDTH - SQUARE_WIDTH) / 2);       myViewWindowY = SQUARE_WIDTH * playerCoords[1]           - ((DISP_HEIGHT - SQUARE_WIDTH) / 2);       // a number of objects are created in order to set up the game,       // but they should be eliminated to free up memory:       decoder = null;       System.gc();     }     return (retVal);   }   /**    * save the current game in progress.    */   void saveGame(int gameTicks) throws Exception {     int[] playerSquare = new int[2];     // the coordinates of the player are given in terms of     // the 16 x 16 dungeon grid. We divide the player's     // pixel coordinates to ge the right grid square.     // If the player was not precisely alligned with a     // grid square when the game was saved, the difference     // will be shaved off.     playerSquare[0] = myPrincess.getX() / SQUARE_WIDTH;     playerSquare[1] = myPrincess.getY() / SQUARE_WIDTH;     // save the coordinates of the current locations of     // the keys, and if a key is currently held by the     // player, we save the info of which one it was.     int[][] keyCoords = new int[4][];     int heldKey = -1;     for (int i = 0; i < myKeys.length; i++) {       keyCoords[i] = new int[2];       keyCoords[i][0] = myKeys[i].getX() / SQUARE_WIDTH;       keyCoords[i][1] = myKeys[i].getY() / SQUARE_WIDTH;       if ((myHeldKey != null) && (myKeys[i] == myHeldKey)) {         heldKey = i;       }     }     // save the information of which doors were open.     int[] doorsOpen = new int[8];     for (int i = 0; i < myDoors.length; i++) {       if (myDoors[i].isVisible()) {         doorsOpen[i] = 1;       }     }     // take all of the information we've gathered and     // create a GameInfo object that will save the info     // in the device's memory.     GameInfo info = new GameInfo(myCurrentBoardNum, gameTicks,         playerSquare, keyCoords, doorsOpen, heldKey);   }   //-------------------------------------------------------   //  graphics methods   /**    * paint the game graphic on the screen.    */   public void paint(Graphics g) throws Exception {     // only repaint if something has changed:     if (myModifiedSinceLastPaint) {       g.setColor(DungeonCanvas.WHITE);       // paint the background white to cover old game objects       // that have changed position since last paint.       // here coordinates are given       // with respect to the graphics (canvas) origin:       g.fillRect(0, 0, DISP_WIDTH, DISP_HEIGHT);       // here coordinates are given       // with respect to the LayerManager origin:       setViewWindow(myViewWindowX, myViewWindowY, DISP_WIDTH, DISP_HEIGHT);       // call the paint funstion of the superclass LayerManager       // to paint all of the Layers       paint(g, CANVAS_X, CANVAS_Y);       // don't paint again until something changes:       myModifiedSinceLastPaint = false;     }   }   //-------------------------------------------------------   //  game movements   /**    * respond to keystrokes by deciding where to move and then moving the    * pieces and the view window correspondingly.    */   void requestMove(int horizontal, int vertical) {     if (horizontal != 0) {       // see how far the princess can move in the desired       // horizontal direction (if not blocked by a wall       // or closed door)       horizontal = requestHorizontal(horizontal);     }     // vertical < 0 indicates that the user has     // pressed the UP button and would like to jump.     // therefore, if we're not currently jumping,     // we begin the jump.     if ((myIsJumping == NO_JUMP) && (vertical < 0)) {       myIsJumping++;     } else if (myIsJumping == NO_JUMP) {       // if we're not jumping at all, we need to check       // if the princess should be falling:       // we (temporarily) move the princess down and see if that       // causes a collision with the floor:       myPrincess.move(0, MOVE_LENGTH);       // if the princess can move down without colliding       // with the floor, then we set the princess to       // be falling. The variable myIsJumping starts       // negative while the princess is jumping up and       // is zero or positive when the princess is coming       // back down. We therefore set myIsJumping to       // zero to indicate that the princess should start       // falling.       if (!checkCollision()) {         myIsJumping = 0;       }       // we move the princess Sprite back to the correct       // position she was at before we (temporarily) moved       // her down to see if she would fall.       myPrincess.move(0, -MOVE_LENGTH);     }     // if the princess is currently jumping or falling,     // we calculate the vertical distance she should move     // (taking into account the horizontal distance that     // she is also moving).     if (myIsJumping != NO_JUMP) {       vertical = jumpOrFall(horizontal);     }     // now that we've calculated how far the princess     // should move, we move her. (this is a call to     // another internal method of this method     // suite, it is not a built-in LayerManager method):     move(horizontal, vertical);   }   /**    * Internal to requestMove. Calculates what the real horizontal distance    * moved should be after taking obstacles into account.    *     * @return the horizontal distance that the player can move.    */   private int requestHorizontal(int horizontal) {     // we (temporarily) move her to the right or left     // and see if she hits a wall or a door:     myPrincess.move(horizontal * MOVE_LENGTH, 0);     if (checkCollision()) {       // if she hits something, then she's not allowed       // to go in that direction, so we set the horizontal       // move distance to zero and then move the princess       // back to where she was.       myPrincess.move(-horizontal * MOVE_LENGTH, 0);       horizontal = 0;     } else {       // if she doesn't hit anything then the move request       // succeeds, but we still move her back to the       // earlier position because this was just the checking       // phase.       myPrincess.move(-horizontal * MOVE_LENGTH, 0);       horizontal *= MOVE_LENGTH;     }     return (horizontal);   }   /**    * Internal to requestMove. Calculates the vertical change in the player's    * position if jumping or falling. this method should only be called if the    * player is currently jumping or falling.    *     * @return the vertical distance that the player should move this turn.    *         (negative moves up, positive moves down)    */   private int jumpOrFall(int horizontal) {     // by default we do not move vertically     int vertical = 0;     // The speed of rise or descent is computed using     // the int myIsJumping. Since we are in a jump or     // fall, we advance the jump by one (which simulates     // the downward pull of gravity by slowing the rise     // or accellerating the fall) unless the player is     // already falling at maximum speed. (a maximum     // free fall speed is necessary because otherwise     // it is possible for the player to fall right through     // the bottom of the maze...)     if (myIsJumping <= MAX_FREE_FALL) {       myIsJumping++;     }     if (myIsJumping < 0) {       // if myIsJumping is negative, that means that       // the princess is rising. We calculate the       // number of pixels to go up by raising 2 to       // the power myIsJumping (absolute value).       // note that we make the result negative because       // the up and down coordinates in Java are the       // reverse of the vertical coordinates we learned       // in math class: as you go up, the coordinate       // values go down, and as you go down the screen,       // the coordinate numbers go up.       vertical = -(2 << (-myIsJumping));     } else {       // if myIsJumping is positive, the princess is falling.       // we calculate the distance to fall by raising two       // to the power of the absolute value of myIsJumping.       vertical = (2 << (myIsJumping));     }     // now we temporarily move the princess the desired     // vertical distance (with the corresponding horizontal     // distance also thrown in), and see if she hits anything:     myPrincess.move(horizontal, vertical);     if (checkCollision()) {       // here we're in the case where she did hit something.       // we move her back into position and then see what       // to do about it.       myPrincess.move(-horizontal, -vertical);       if (vertical > 0) {         // in this case the player is falling.         // so we need to determine precisely how         // far she can fall before she hit the bottom         vertical = 0;         // we temporarily move her the desired horizontal         // distance while calculating the corresponding         // vertical distance.         myPrincess.move(horizontal, 0);         while (!checkCollision()) {           vertical++;           myPrincess.move(0, 1);         }         // now that we've calculated how far she can fall,         // we move her back to her earlier position         myPrincess.move(-horizontal, -vertical);         // we subtract 1 pixel from the distance calculated         // because once she has actually collided with the         // floor, she's gone one pixel too far...         vertical--;         // now that she's hit the floor, she's not jumping         // anymore.         myIsJumping = NO_JUMP;       } else {         // in this case we're going up, so she         // must have hit her head.         // This next if is checking for a special         // case where there's room to jump up exactly         // one square. In that case we increase the         // value of myIsJumping in order to make the         // princess not rise as high. The details         // of the calculation in this case were found         // through trial and error:         if (myIsJumping == NO_JUMP + 2) {           myIsJumping++;           vertical = -(2 << (-myIsJumping));           // now we see if the special shortened jump           // still makes her hit her head:           // (as usual, temporarily move her to test           // for collisions)           myPrincess.move(horizontal, vertical);           if (checkCollision()) {             // if she still hits her head even             // with this special shortened jump,             // then she was not meant to jump...             myPrincess.move(-horizontal, -vertical);             vertical = 0;             myIsJumping = NO_JUMP;           } else {             // now that we've chhecked for collisions,             // we move the player back to her earlier             // position:             myPrincess.move(-horizontal, -vertical);           }         } else {           // if she hit her head, then she should not           // jump up.           vertical = 0;           myIsJumping = NO_JUMP;         }       }     } else {       // since she didn't hit anything when we moved       // her, then all we have to do is move her back.       myPrincess.move(-horizontal, -vertical);     }     return (vertical);   }   /**    * Internal to requestMove. Once the moves have been determined, actually    * perform the move.    */   private void move(int horizontal, int vertical) {     // repaint only if we actually change something:     if ((horizontal != 0) || (vertical != 0)) {       myModifiedSinceLastPaint = true;     }     // if the princess is moving left or right, we set     // her image to be facing the right direction:     if (horizontal > 0) {       myPrincess.setTransform(Sprite.TRANS_NONE);     } else if (horizontal < 0) {       myPrincess.setTransform(Sprite.TRANS_MIRROR);     }     // if she's jumping or falling, we set the image to     // the frame where the skirt is inflated:     if (vertical != 0) {       myPrincess.setFrame(0);       // if she's just running, we alternate between the       // two frames:     } else if (horizontal != 0) {       if (myPrincess.getFrame() == 1) {         myPrincess.setFrame(0);       } else {         myPrincess.setFrame(1);       }     }     // move the position of the view window so that     // the player stays in the center:     myViewWindowX += horizontal;     myViewWindowY += vertical;     // after all that work, we finally move the     // princess for real!!!     myPrincess.move(horizontal, vertical);   }   //-------------------------------------------------------   //  sprite interactions   /**    * Drops the currently held key and picks up another.    */   void putDownPickUp() {     // we do not want to allow the player to put     // down the key in the air, so we verify that     // we're not jumping or falling first:     if ((myIsJumping == NO_JUMP) && (myPrincess.getY() % SQUARE_WIDTH == 0)) {       // since we're picking something up or putting       // something down, the display changes and needs       // to be repainted:       setNeedsRepaint();       // if the thing we're picking up is the crown,       // we're done, the player has won:       if (myPrincess.collidesWith(myCrown, true)) {         myCanvas.setGameOver();         return;       }       // keep track of the key we're putting down in       // order to place it correctly:       DoorKey oldHeld = myHeldKey;       myHeldKey = null;       // if the princess is on top of another key,       // that one becomes the held key and is hence       // made invisible:       for (int i = 0; i < myKeys.length; i++) {         // we check myHeldKey for null because we don't         // want to accidentally pick up two keys.         if ((myPrincess.collidesWith(myKeys[i], true))             && (myHeldKey == null)) {           myHeldKey = myKeys[i];           myHeldKey.setVisible(false);         }       }       if (oldHeld != null) {         // place the key we're putting down in the Princess's         // current position and make it visible:         oldHeld.setPosition(myPrincess.getX(), myPrincess.getY());         oldHeld.setVisible(true);       }     }   }   /**    * Checks of the player hits a stone wall or a door.    */   boolean checkCollision() {     boolean retVal = false;     // the "true" arg meand to check for a pixel-level     // collision (so merely an overlap in image     // squares does not register as a collision)     if (myPrincess.collidesWith(myBackground, true)) {       retVal = true;     } else {       // Note: it is not necessary to synchronize       // this block because the thread that calls this       // method is the same as the one that puts down the       // keys, so there's no danger of the key being put down       // between the moment we check for the key and       // the moment we open the door:       for (int i = 0; i < myDoors.length; i++) {         // if she's holding the right key, then open the door         // otherwise bounce off         if (myPrincess.collidesWith(myDoors[i], true)) {           if ((myHeldKey != null)               && (myDoors[i].getColor() == myHeldKey.getColor())) {             setNeedsRepaint();             myDoors[i].setVisible(false);           } else {             // if she's not holding the right key, then             // she has collided with the door just the same             // as if she had collided with a wall:             retVal = true;           }         }       }     }     return (retVal);   } } /**  * This class is the display of the game.  *   * @author Carol Hamer  */ class DungeonCanvas extends GameCanvas {   //---------------------------------------------------------   //   dimension fields   //  (constant after initialization)   /**    * the height of the black region below the play area.    */   static int TIMER_HEIGHT = 32;   /**    * the top corner x coordinate according to this object's coordinate    * system:.    */   static final int CORNER_X = 0;   /**    * the top corner y coordinate according to this object's coordinate    * system:.    */   static final int CORNER_Y = 0;   /**    * the width of the portion of the screen that this canvas can use.    */   static int DISP_WIDTH;   /**    * the height of the portion of the screen that this canvas can use.    */   static int DISP_HEIGHT;   /**    * the height of the font used for this game.    */   static int FONT_HEIGHT;   /**    * the font used for this game.    */   static Font FONT;   /**    * color constant    */   public static final int BLACK = 0;   /**    * color constant    */   public static final int WHITE = 0xffffff;   //---------------------------------------------------------   //   game object fields   /**    * a handle to the display.    */   private Display myDisplay;   /**    * a handle to the MIDlet object (to keep track of buttons).    */   private Dungeon myDungeon;   /**    * the LayerManager that handles the game graphics.    */   private DungeonManager myManager;   /**    * whether or not the game has ended.    */   private static boolean myGameOver;   /**    * The number of ticks on the clock the last time the time display was    * updated. This is saved to determine if the time string needs to be    * recomputed.    */   private int myOldGameTicks = 0;   /**    * the number of game ticks that have passed since the beginning of the    * game.    */   private int myGameTicks = myOldGameTicks;   /**    * we save the time string to avoid recreating it unnecessarily.    */   private static String myInitialString = "0:00";   /**    * we save the time string to avoid recreating it unnecessarily.    */   private String myTimeString = myInitialString;   //-----------------------------------------------------   //    gets/sets   /**    * This is called when the game ends.    */   void setGameOver() {     myGameOver = true;     myDungeon.pauseApp();   }   /**    * Find out if the game has ended.    */   static boolean getGameOver() {     return (myGameOver);   }   /**    * Tell the layer manager that it needs to repaint.    */   public void setNeedsRepaint() {     myManager.setNeedsRepaint();   }   //-----------------------------------------------------   //    initialization and game state changes   /**    * Constructor sets the data, performs dimension calculations, and creates    * the graphical objects.    */   public DungeonCanvas(Dungeon midlet) throws Exception {     super(false);     myDisplay = Display.getDisplay(midlet);     myDungeon = midlet;     // calculate the dimensions     DISP_WIDTH = getWidth();     DISP_HEIGHT = getHeight();     if ((!myDisplay.isColor()) || (myDisplay.numColors() < 256)) {       throw (new Exception("game requires full-color screen"));     }     if ((DISP_WIDTH < 150) || (DISP_HEIGHT < 170)) {       throw (new Exception("Screen too small"));     }     if ((DISP_WIDTH > 250) || (DISP_HEIGHT > 250)) {       throw (new Exception("Screen too large"));     }     // since the time is painted in white on black,     // it shows up better if the font is bold:     FONT = Font         .getFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_MEDIUM);     // calculate the height of the black region that the     // timer is painted on:     FONT_HEIGHT = FONT.getHeight();     TIMER_HEIGHT = FONT_HEIGHT + 8;     // create the LayerManager (where all of the interesting     // graphics go!) and give it the dimensions of the     // region it is supposed to paint:     if (myManager == null) {       myManager = new DungeonManager(CORNER_X, CORNER_Y, DISP_WIDTH,           DISP_HEIGHT - TIMER_HEIGHT, this);     }   }   /**    * This is called as soon as the application begins.    */   void start() {     myGameOver = false;     myDisplay.setCurrent(this);     setNeedsRepaint();   }   /**    * sets all variables back to their initial positions.    */   void reset() throws Exception {     // most of the variables that need to be reset     // are held by the LayerManager:     myManager.reset();     myGameOver = false;     setNeedsRepaint();   }   /**    * sets all variables back to the positions from a previously saved game.    */   void revertToSaved() throws Exception {     // most of the variables that need to be reset     // are held by the LayerManager, so we     // prompt the LayerManager to get the     // saved data:     myGameTicks = myManager.revertToSaved();     myGameOver = false;     myOldGameTicks = myGameTicks;     myTimeString = formatTime();     setNeedsRepaint();   }   /**    * save the current game in progress.    */   void saveGame() throws Exception {     myManager.saveGame(myGameTicks);   }   /**    * clears the key states.    */   void flushKeys() {     getKeyStates();   }   /**    * If the game is hidden by another app (or a menu) ignore it since not much    * happens in this game when the user is not actively interacting with it.    * (we could pause the timer, but it's not important enough to bother with    * when the user is just pulling up a menu for a few seconds)    */   protected void hideNotify() {   }   /**    * When it comes back into view, just make sure the manager knows that it    * needs to repaint.    */   protected void showNotify() {     setNeedsRepaint();   }   //-------------------------------------------------------   //  graphics methods   /**    * paint the game graphics on the screen.    */   public void paint(Graphics g) {     // color the bottom segment of the screen black     g.setColor(BLACK);     g.fillRect(CORNER_X, CORNER_Y + DISP_HEIGHT - TIMER_HEIGHT, DISP_WIDTH,         TIMER_HEIGHT);     // paint the LayerManager (which paints     // all of the interesting graphics):     try {       myManager.paint(g);     } catch (Exception e) {       myDungeon.errorMsg(e);     }     // draw the time     g.setColor(WHITE);     g.setFont(FONT);     g.drawString("Time: " + formatTime(), DISP_WIDTH / 2, CORNER_Y         + DISP_HEIGHT - 4, g.BOTTOM | g.HCENTER);     // write "Dungeon Completed" when the user finishes a board:     if (myGameOver) {       myDungeon.setNewCommand();       // clear the top region:       g.setColor(WHITE);       g.fillRect(CORNER_X, CORNER_Y, DISP_WIDTH, FONT_HEIGHT * 2 + 1);       int goWidth = FONT.stringWidth("Dungeon Completed");       g.setColor(BLACK);       g.setFont(FONT);       g.drawString("Dungeon Completed", (DISP_WIDTH - goWidth) / 2,           CORNER_Y + FONT_HEIGHT, g.TOP | g.LEFT);     }   }   /**    * a simple utility to make the number of ticks look like a time...    */   public String formatTime() {     if ((myGameTicks / 16) != myOldGameTicks) {       myTimeString = "";       myOldGameTicks = (myGameTicks / 16) + 1;       int smallPart = myOldGameTicks % 60;       int bigPart = myOldGameTicks / 60;       myTimeString += bigPart + ":";       if (smallPart / 10 < 1) {         myTimeString += "0";       }       myTimeString += smallPart;     }     return (myTimeString);   }   //-------------------------------------------------------   //  game movements   /**    * update the display.    */   void updateScreen() {     myGameTicks++;     // paint the display     try {       paint(getGraphics());       flushGraphics(CORNER_X, CORNER_Y, DISP_WIDTH, DISP_HEIGHT);     } catch (Exception e) {       myDungeon.errorMsg(e);     }   }   /**    * Respond to keystrokes.    */   public void checkKeys() {     if (!myGameOver) {       int vertical = 0;       int horizontal = 0;       // determine which moves the user would like to make:       int keyState = getKeyStates();       if ((keyState & LEFT_PRESSED) != 0) {         horizontal = -1;       }       if ((keyState & RIGHT_PRESSED) != 0) {         horizontal = 1;       }       if ((keyState & UP_PRESSED) != 0) {         vertical = -1;       }       if ((keyState & DOWN_PRESSED) != 0) {         // if the user presses the down key,         // we put down or pick up a key object         // or pick up the crown:         myManager.putDownPickUp();       }       // tell the manager to move the player       // accordingly if possible:       myManager.requestMove(horizontal, vertical);     }   } }