Java/J2ME/Game
Содержание
Checkers game
<source lang="java">
/* Title: J2ME Games With MIDP2 Authors: Carol Hamer Publisher: Apress ISBN: 1590593820
- /
import java.util.Vector; import java.io.*; import javax.microedition.io.*; import javax.microedition.rms.*;
import javax.microedition.midlet.*; import javax.microedition.lcdui.*; /**
* This is the main class of the checkers game. * * @author Carol Hamer */
public class Checkers extends MIDlet implements CommandListener {
//----------------------------------------------------- // game object fields /** * The canvas that the checkerboard is drawn on. */ private CheckersCanvas myCanvas; /** * The class that makes the http connection. */ private Communicator myCommunicator; //----------------------------------------------------- // command fields /** * The button to exit the game. */ private Command myExitCommand = new Command("Exit", Command.EXIT, 99); //----------------------------------------------------- // initialization and game state changes /** * Initialize the canvas and the commands. */ public Checkers() { try { //create the canvas and set up the commands: myCanvas = new CheckersCanvas(Display.getDisplay(this)); myCanvas.addCommand(myExitCommand); myCanvas.setCommandListener(this); CheckersGame game = myCanvas.getGame(); myCommunicator = new Communicator(this, myCanvas, game); game.setCommunicator(myCommunicator); } catch(Exception e) { // if there"s an error during creation, display it as an alert. errorMsg(e); } } //---------------------------------------------------------------- // 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 { // tell the canvas to set up the game data and paint the // checkerboard. if(myCanvas != null) { myCanvas.start(); } // tell the communicator to start its thread and make a // connection. if(myCommunicator != null) { myCommunicator.start(); } } /** * Throw out the garbage. */ public void destroyApp(boolean unconditional) throws MIDletStateChangeException { // tell the communicator to send the end game // message to the other player and then disconnect: if(myCommunicator != null) { myCommunicator.endGame(); } // throw the larger game objects in the garbage: myCommunicator = null; myCanvas = null; System.gc(); } /** * Pause the game. * This method merely ends the game because this * version of the Checkers game does not support * re-entering a game that is in play. A possible * improvement to the game would be to allow * a player to diconeect and leave a game and then * later return to it, using some sort of session * token to find the correct game in progress on * the server side. */ public void pauseApp() { try { destroyApp(false); notifyDestroyed(); } catch (MIDletStateChangeException ex) { } } //---------------------------------------------------------------- // implementation of CommandListener /* * Respond to a command issued on the Canvas. */ public void commandAction(Command c, Displayable s) { if(c == myExitCommand) { try { destroyApp(false); notifyDestroyed(); } catch (MIDletStateChangeException ex) { } } } //------------------------------------------------------- // error methods /** * Converts an exception to a message and displays * the message.. */ void errorMsg(Exception e) { e.printStackTrace(); if(e.getMessage() == null) { errorMsg(e.getClass().getName()); } else { errorMsg(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 is the display of the game. * * @author Carol Hamer */
class CheckersCanvas extends Canvas {
//--------------------------------------------------------- // static fields /** * color constant */ public static final int BLACK = 0; /** * color constant */ public static final int WHITE = 0xffffff; /** * color constant. * (not quite bright red) */ public static final int RED = 0xf96868; /** * color constant */ public static final int GREY = 0xc6c6c6; /** * color constant */ public static final int LT_GREY = 0xe5e3e3; /** * how many rows and columns the display is divided into. */ public static final int GRID_WIDTH = 8; //--------------------------------------------------------- // instance fields /** * The black crown to draw on the red pieces.. */ private Image myBlackCrown; /** * The red crown to draw on the black pieces.. */ private Image myWhiteCrown; /** * a handle to the display. */ private Display myDisplay; /** * a handle to the object that stores the game logic * and game data. */ private CheckersGame myGame; /** * checkers dimension: the width of the squares of the checkerboard. */ private int mySquareSize; /** * checkers dimension: the minimum width possible for the * checkerboard squares. */ private int myMinSquareSize = 15; /** * whether or not we"re waiting for another player to join * the game. */ private boolean myIsWaiting; //----------------------------------------------------- // gets / sets /** * @return a handle to the class that holds the logic of the * checkers game. */ CheckersGame getGame() { return(myGame); } /** * Display a screen to inform the player that we"re * waiting for another player. */ void setWaitScreen(boolean wait) { myIsWaiting = wait; } //----------------------------------------------------- // initialization and game state changes /** * Constructor performs size calculations. * @throws Exception if the display size is too * small to make a checkers. */ CheckersCanvas(Display d) throws Exception { myDisplay = d; myGame = new CheckersGame(); // a few calculations to make the right checkerboard // for the current display. int width = getWidth(); int height = getHeight(); // get the smaller dimension fo the two possible // screen dimensions in order to determine how // big to make the checkerboard. int screenSquareWidth = height; if(width < height) { screenSquareWidth = width; } mySquareSize = screenSquareWidth / GRID_WIDTH; // if the display is too small to make a reasonable checkerboard, // then we throw an Exception if(mySquareSize < myMinSquareSize) { throw(new Exception("Display too small")); } // initialize the crown images: myBlackCrown = Image.createImage("/blackCrown.png"); myWhiteCrown = Image.createImage("/whiteCrown.png"); } /** * This is called as soon as the application begins. */ void start() { myDisplay.setCurrent(this); // prepare the game data for the first move: myGame.start(); } //------------------------------------------------------- // graphics methods /** * Repaint the checkerboard.. */ protected void paint(Graphics g) { int width = getWidth(); int height = getHeight(); g.setColor(WHITE); // clear the board (including the region around // the board, which can get menu stuff and other // garbage painted onto it...) g.fillRect(0, 0, width, height); // If we need to wait for another player to join the // game before we can start, this displays the appropriate // message: if(myIsWaiting) { // perform some calculations to place the text correctly: Font font = g.getFont(); int fontHeight = font.getHeight(); int fontWidth = font.stringWidth("waiting for another player"); g.setColor(WHITE); g.fillRect((width - fontWidth)/2, (height - fontHeight)/2, fontWidth + 2, fontHeight); // write in black g.setColor(BLACK); g.setFont(font); g.drawString("waiting for another player", (width - fontWidth)/2, (height - fontHeight)/2, g.TOP|g.LEFT); return; } // now draw the checkerboard: // first the dark squares: byte offset = 0; for(byte i = 0; i < 4; i++) { for(byte j = 0; j < 8; j++) { // the offset is used to handle the fact that in every // other row the dark squares are shifted one place // to the right. if(j % 2 != 0) { offset = 1; } else { offset = 0; } // now if this is a selected square, we draw it lighter: if(myGame.isSelected(i, j)) { g.setColor(LT_GREY); g.fillRect((2*i + offset)*mySquareSize, j*mySquareSize, mySquareSize, mySquareSize); } else { // if it"s not selected, we draw it dark grey: g.setColor(GREY); g.fillRect((2*i + offset)*mySquareSize, j*mySquareSize, mySquareSize, mySquareSize); } // now put the pieces in their places: g.setColor(RED); int piece = myGame.getPiece(i, j); int circleOffset = 2; int circleSize = mySquareSize - 2*circleOffset; if(piece < 0) { // color the piece in black g.setColor(BLACK); g.fillRoundRect((2*i + offset)*mySquareSize + circleOffset, j*mySquareSize + circleOffset, circleSize, circleSize, circleSize, circleSize); // if the player is a king, draw a crown on: if(piece < -1) { g.drawImage(myWhiteCrown, (2*i + offset)*mySquareSize + mySquareSize/2, j*mySquareSize + 1 + mySquareSize/2, Graphics.VCENTER|Graphics.HCENTER); } } else if(piece > 0) { // color the piece in red g.fillRoundRect((2*i + offset)*mySquareSize + circleOffset, j*mySquareSize + circleOffset, circleSize, circleSize, circleSize, circleSize); // if the player is a king, draw a crown on: if(piece > 1) { g.drawImage(myBlackCrown, (2*i + offset)*mySquareSize + mySquareSize/2, j*mySquareSize + 1 + mySquareSize/2, Graphics.VCENTER|Graphics.HCENTER); } } } } // now the blank squares: // actually, this part is probably not necessary... g.setColor(WHITE); for(int i = 0; i < 4; i++) { for(int j = 0; j < 8; j++) { if(j % 2 == 0) { offset = 1; } else { offset = 0; } g.fillRect((2*i + offset)*mySquareSize, j*mySquareSize, mySquareSize, mySquareSize); } } // if the player has reached the end of the game, // we display the end message. if(myGame.getGameOver()) { // perform some calculations to place the text correctly: Font font = g.getFont(); int fontHeight = font.getHeight(); int fontWidth = font.stringWidth("Game Over"); g.setColor(WHITE); g.fillRect((width - fontWidth)/2, (height - fontHeight)/2, fontWidth + 2, fontHeight); // write in black g.setColor(BLACK); g.setFont(font); g.drawString("Game Over", (width - fontWidth)/2, (height - fontHeight)/2, g.TOP|g.LEFT); } } //------------------------------------------------------- // handle keystrokes /** * Move the player. */ public void keyPressed(int keyCode) { if(myGame.isMyTurn()) { int action = getGameAction(keyCode); switch (action) { case LEFT: myGame.leftPressed(); break; case RIGHT: myGame.rightPressed(); break; case UP: myGame.upPressed(); break; case DOWN: myGame.deselect(); break; } repaint(); serviceRepaints(); } }
} /**
* This class contacts a remote server in order to * play a game of checkers against an opponent.. * * @author Carol Hamer */
class Communicator extends Thread {
//-------------------------------------------------------- // static fields /** * This is the URL to contact. * IMPORTANT: before compiling, the following URL * must be changed to the correct URL of the * machine running the server code. */ public static final String SERVER_URL = "socket://malbec:8007"; /** * The int to signal that the game is to begin. */ public static final byte START_GAME_FLAG = -4; /** * The byte to signal that the game is to end. */ public static final byte END_GAME_FLAG = -3; /** * The byte to signal the end of a turn. */ public static final byte END_TURN_FLAG = -2; //-------------------------------------------------------- // game instance fields /** * The MIDlet subclass, used to set the Display * in the case where an error message needs to be sent.. */ private Checkers myCheckers; /** * The Canvas subclass, used to set the Display * in the case where an error message needs to be sent.. */ private CheckersCanvas myCanvas; /** * The game logic class that we send the opponent"s * moves to.. */ private CheckersGame myGame; /** * Whether or not the MIDlet class has requested the * game to end. */ private boolean myShouldStop; //-------------------------------------------------------- // data exchange instance fields /** * The data from the local player that is to * be sent to the opponent. */ private byte[] myMove; /** * Whether or not the current turn is done and * should be sent. */ private boolean myTurnIsDone = true; //-------------------------------------------------------- // initialization /** * Constructor is used only when the program wants * to spawn a data-fetching thread, not for merely * reading local data with static methods. */ Communicator(Checkers checkers, CheckersCanvas canvas, CheckersGame game) { myCheckers = checkers; myCanvas = canvas; myGame = game; } //-------------------------------------------------------- // methods called by CheckersGame to send move // information to the opponent. /** * Stop the game entirely. Notify the servlet that * the user is exiting the game. */ synchronized void endGame() { myShouldStop = true; if(myGame != null) { myGame.setGameOver(); } notify(); } /** * This is called when the player moves a piece. */ synchronized void move(byte sourceX, byte sourceY, byte destinationX, byte destinationY) { myMove = new byte[4]; myMove[0] = sourceX; myMove[1] = sourceY; myMove[2] = destinationX; myMove[3] = destinationY; myTurnIsDone = false; notify(); } /** * This is called when the local player"s turn is over. */ synchronized void endTurn() { myTurnIsDone = true; notify(); } //-------------------------------------------------------- // main communication method /** * Makes a connection to the server and sends and receives * information about moves. */ public void run() { DataInputStream dis = null; DataOutputStream dos = null; SocketConnection conn = null; byte[] fourBytes = new byte[4]; try { // tell the user that we"re waiting for the other player to join: myCanvas.setWaitScreen(true); myCanvas.repaint(); myCanvas.serviceRepaints(); // now make the connection: conn = (SocketConnection)Connector.open(SERVER_URL); conn.setSocketOption(SocketConnection.KEEPALIVE, 1); dos = conn.openDataOutputStream(); dis = conn.openDataInputStream(); // we read four bytes to make sure the connection works... dis.readFully(fourBytes); if(fourBytes[0] != START_GAME_FLAG) { throw(new Exception("server-side error")); } // On this line it will block waiting for another // player to join the game or make a move: dis.readFully(fourBytes); // if the server sends the start game flag again, // that means that we start with the local player"s turn. // Otherwise, we read the other player"s first move from the // stream: if(fourBytes[0] != START_GAME_FLAG) { // verify that the other player sent a move // and not just a message ending the game... if(fourBytes[0] == END_GAME_FLAG) { throw(new Exception("other player quit")); } // we move the opponent on the local screen. // then we read from the opponent again, // in case there"s a double-jump: while(fourBytes[0] != END_TURN_FLAG) { myGame.moveOpponent(fourBytes); dis.readFully(fourBytes); } } // now signal the local game that the opponent is done // so the board must be updated and the local player // prompted to make a move: myGame.endOpponentTurn(); myCanvas.setWaitScreen(false); myCanvas.repaint(); myCanvas.serviceRepaints(); // begin main game loop: while(! myShouldStop) { // now it"s the local player"s turn. // wait for the player to move a piece: synchronized(this) { wait(); } // after every wait, we check if the game // ended while we were waiting... if(myShouldStop) { break; } while(! myTurnIsDone) { // send the current move: if(myMove != null) { dos.write(myMove, 0, myMove.length); myMove = null; } // If the player can continue the move with a double // jump, we wait for the player to do it: synchronized(this) { // make sure the turn isn"t done before we start waiting // (the end turn notify might accidentally be called // before we start waiting...) if(! myTurnIsDone) { wait(); } } } // after every wait, we check if the game // ended while we were waiting... if(myShouldStop) { break; } // now we tell the other player the this player"s // turn is over: fourBytes[0] = END_TURN_FLAG; dos.write(fourBytes, 0, fourBytes.length); // now that we"ve sent the move, we wait for a response: dis.readFully(fourBytes); while((fourBytes[0] != END_TURN_FLAG) && (fourBytes[0] != END_GAME_FLAG) && (!myShouldStop)) { // we move the opponent on the local screen. // then we read from the opponent again, // in case there"s a double-jump: myGame.moveOpponent(fourBytes); dis.readFully(fourBytes); } // if the other player has left the game, we tell the // local user that the game is over. if((fourBytes[0] == END_GAME_FLAG) || (myShouldStop)) { endGame(); break; } myGame.endOpponentTurn(); myCanvas.repaint(); myCanvas.serviceRepaints(); } // end while loop } catch(Exception e) { // if there"s an error, we display its messsage and // end the game. myCheckers.errorMsg(e.getMessage()); } finally { // now we send the information that we"re leaving the game, // then close up and delete everything. try { if(dos != null) { dos.write(END_GAME_FLAG); dos.close(); } if(dis != null) { dis.close(); } if(conn != null) { conn.close(); } dis = null; dos = null; conn = null; } catch(Exception e) { // if this throws, at least we made our best effort // to close everything up.... } } // one last paint job to display the "Game Over" myCanvas.repaint(); myCanvas.serviceRepaints(); }
}
/**
* 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 takes care of the underlying logic and data of * the checkers game being played. That includes where * all of the pieces are on the board and where it is okay * for them to move to. * * @author Carol Hamer */
class CheckersGame {
//------------------------------------------------------- // static fields /** * The length of the checkerboard in the x-direction. */ public static final byte X_LENGTH = 4; /** * The length of the checkerboard in the y-direction. */ public static final byte Y_LENGTH = 8; //------------------------------------------------------- // instance fields /** * a handle to the communications class that exchanges * data with the server. */ private Communicator myCommunicator; /** * This array represents the black squares of the * checkerboard. The two dimensions of the array * represent the two dimensions of the checkerboard. * The value represents what type of piece is on * the square. * 0 = empty * 1 = local player"s piece * 2 = local player"s king * -1 = remote player"s piece * -2 = remote player"s king */ private byte[][] myGrid; /** * If the user has currently selected a piece to move, * this is its X grid coordinate. (-1 if none selected) */ private byte mySelectedX = -1; /** * If the user has currently selected a piece to move, * this is its Y grid coordinate.(-1 if none selected) */ private byte mySelectedY = -1; /** * If the user has currently selected a possible * destination square for a move, this is its X coordinate.. * (-1 if none selected) */ private byte myDestinationX = -1; /** * If the user has currently selected a possible * destination square for a move, this is its Y coordinate.. * (-1 if none selected) */ private byte myDestinationY = -1; /** * This Vector contains the coordinates of all of the * squares that the player could currently move to. */ private Vector myPossibleMoves = new Vector(4); /** * Whether or not the currently displayed checkers has * been completed. */ private boolean myGameOver = false; /** * Whether or not it is currently this player"s turn. */ private boolean myTurn = false; /** * This is true if the player has just jumped and can * jump again. */ private boolean myIsJumping = false; //------------------------------------------------------- // get/set data /** * get the piece on the given grid square. */ byte getPiece(byte x, byte y) { return(myGrid[x][y]); } /** * This is callsed by CheckersCanvas to determine if * the square is currently selected (as containing * a piece to move or a destination square). */ boolean isSelected(byte x, byte y) { boolean retVal = false; if((x == mySelectedX) && (y == mySelectedY)) { retVal = true; } else if((x == myDestinationX) && (y == myDestinationY)) { retVal = true; } return(retVal); } /** * This tells whether or not the keystrokes should currently * be taken into account. */ boolean isMyTurn() { boolean retVal = false; if((!myGameOver) && ((myTurn) || (myIsJumping))) { retVal = true; } return(retVal); } /** * This tells whether or not the game has ended. */ boolean getGameOver() { boolean retVal = false; if(myGameOver) { retVal = true; } return(retVal); } /** * tell the CheckersGame that the other player has ended the game. */ void setGameOver() { myGameOver = true; } /** * set the communicator object. */ void setCommunicator(Communicator comm) { myCommunicator = comm; } //------------------------------------------------------- // initialization /** * Constructor puts the pieces in their initial positions: */ CheckersGame() { myGrid = new byte[X_LENGTH][]; for(byte i = 0; i < myGrid.length; i++) { myGrid[i] = new byte[Y_LENGTH]; for(byte j = 0; j < myGrid[i].length; j++) { if(j < 3) { // fill the top of the board with remote players myGrid[i][j] = -1; } else if(j > 4) { // fill the bottom of the board with local players myGrid[i][j] = 1; } } } } /** * This is called just before the player makes the * first move. */ void start() { mySelectedX = 0; mySelectedY = 5; myTurn = true; getMoves(mySelectedX, mySelectedY, myPossibleMoves, false); } //------------------------------------------------------- // move the opponent // to be called by Communicator /** * This is called when the opponent wants to move * its piece. * @param moveData an array of four bytes: * moveData[0] = opponent"s initial X coordinate * moveData[1] = opponent"s initial Y coordinate * moveData[2] = opponent"s destination X coordinate * moveData[3] = opponent"s destination Y coordinate */ void moveOpponent(byte[] moveData) { // since both players appear on their own screens // as the red side (bottom of the screen), we need // to invert the opponent"s move: moveData[0] = (new Integer(X_LENGTH - moveData[0] - 1)).byteValue(); moveData[2] = (new Integer(X_LENGTH - moveData[2] - 1)).byteValue(); moveData[1] = (new Integer(Y_LENGTH - moveData[1] - 1)).byteValue(); moveData[3] = (new Integer(Y_LENGTH - moveData[3] - 1)).byteValue(); myGrid[moveData[2]][moveData[3]] = myGrid[moveData[0]][moveData[1]]; myGrid[moveData[0]][moveData[1]] = 0; // deal with an opponent"s jump: if((moveData[1] - moveData[3] > 1) || (moveData[3] - moveData[1] > 1)) { int jumpedY = (moveData[1] + moveData[3])/2; int jumpedX = moveData[0]; int parity = moveData[1] % 2; if((parity > 0) && (moveData[2] > moveData[0])) { jumpedX++; } else if((parity == 0) && (moveData[0] > moveData[2])) { jumpedX--; } myGrid[jumpedX][jumpedY] = 0; } // if the opponent reaches the far side, // make him a king: if(moveData[3] == Y_LENGTH - 1) { myGrid[moveData[2]][moveData[3]] = -2; } } /** * This is called when the opponent"s turn is over. * Note that the turn doesn"t automatically end after * the opponent moves because the opponent may make * a double or triple jump. */ void endOpponentTurn() { myTurn = true; // Now begin the local player"s turn: // First select the first local piece that can be // moved. (rightPressed will select an appropriate // piece or end the game if the local player has // no possible moves to make) mySelectedX = 0; mySelectedY = 0; myDestinationX = -1; myDestinationY = -1; rightPressed(); // the local player"s thread has been waiting // for the opponent"s turn to end. synchronized(this) { notify(); } } //------------------------------------------------------- // handle keystrokes // to be called by CheckersCanvas /** * if the left button is pressed, this method takes * the correct course of action depending on the situation. */ void leftPressed() { // in the first case the user has not yet selected a // piece to move: if(myDestinationX == -1) { // find the next possible piece (to the left) // that can move: selectPrevious(); // if selectPrevious fails to fill myPossibleMoves, that // means that the local player cannot move, so the game // is over: if(myPossibleMoves.size() == 0) { myCommunicator.endGame(); } } else { // if the user has already selected a piece to move, // we give the options of where the piece can move to: for(byte i = 0; i < myPossibleMoves.size(); i++) { byte[] coordinates = (byte[])myPossibleMoves.elementAt(i); if((coordinates[0] == myDestinationX) && (coordinates[1] == myDestinationY)) { i++; i = (new Integer(i % myPossibleMoves.size())).byteValue(); coordinates = (byte[])myPossibleMoves.elementAt(i); myDestinationX = coordinates[0]; myDestinationY = coordinates[1]; break; } } } } /** * if the left button is pressed, this method takes * the correct course of action depending on the situation. */ void rightPressed() { // in the first case the user has not yet selected a // piece to move: if(myDestinationX == -1) { // find the next possible piece that can // move: selectNext(); // if selectNext fails to fill myPossibleMoves, that // means that the local player cannot move, so the game // is over: if(myPossibleMoves.size() == 0) { myCommunicator.endGame(); } } else { // if the user has already selected a piece to move, // we give the options of where the piece can move to: for(byte i = 0; i < myPossibleMoves.size(); i++) { byte[] coordinates = (byte[])myPossibleMoves.elementAt(i); if((coordinates[0] == myDestinationX) && (coordinates[1] == myDestinationY)) { i++; i = (new Integer(i % myPossibleMoves.size())).byteValue(); coordinates = (byte[])myPossibleMoves.elementAt(i); myDestinationX = coordinates[0]; myDestinationY = coordinates[1]; break; } } } } /** * If no piece is selected, we select one. If a piece * is selected, we move it. */ void upPressed() { // in the first case the user has not yet selected a // piece to move: if(myDestinationX == -1) { fixSelection(); } else { // if the source square and destination square // have been chosen, we move the piece: move(); } } /** * If the user decided not to move the selected piece * (and instead wants to select again), this undoes * the selection. This corresponds to pressing the * DOWN key. */ void deselect() { // if the player has just completed a jump and // could possibly jump again but decides not to // (i.e. deselects), then the turn ends: if(myIsJumping) { mySelectedX = -1; mySelectedY = -1; myDestinationX = -1; myDestinationY = -1; myIsJumping = false; myTurn = false; myCommunicator.endTurn(); } else { // setting the destination coordinates to -1 // is the signal that the the choice of which // piece to move can be modified: myDestinationX = -1; myDestinationY = -1; } } //------------------------------------------------------- // internal square selection methods /** * When the player has decided that the currently selected * square contains the piece he really wants to move, this * is called. This method switches to the mode where * the player selects the destination square of the move. */ private void fixSelection() { byte[] destination = (byte[])myPossibleMoves.elementAt(0); // setting the destination coordinates to valid // coordinates is the signal that the user is done // selecting the piece to move and now is choosing // the destination square: myDestinationX = destination[0]; myDestinationY = destination[1]; } /** * This method starts from the currently selected square * and finds the next square that contains a piece that * the player can move. */ private void selectNext() { // Test the squares one by one (starting from the // currently selected square) until we find a square // that contains one of the local player"s pieces // that can move: byte testX = mySelectedX; byte testY = mySelectedY; while(true) { testX++; if(testX >= X_LENGTH) { testX = 0; testY++; testY = (new Integer(testY % Y_LENGTH)).byteValue(); } getMoves(testX, testY, myPossibleMoves, false); if((myPossibleMoves.size() != 0) || ((testX == mySelectedX) && (testY == mySelectedY))) { mySelectedX = testX; mySelectedY = testY; break; } } } /** * This method starts from the currently selected square * and finds the next square (to the left) that contains * a piece that the player can move. */ private void selectPrevious() { // Test the squares one by one (starting from the // currently selected square) until we find a square // that contains one of the local player"s pieces // that can move: byte testX = mySelectedX; byte testY = mySelectedY; while(true) { testX--; if(testX < 0) { testX += X_LENGTH; testY--; if(testY < 0) { testY += Y_LENGTH; } } getMoves(testX, testY, myPossibleMoves, false); if((myPossibleMoves.size() != 0) || ((testX == mySelectedX) && (testY == mySelectedY))) { mySelectedX = testX; mySelectedY = testY; break; } } } //------------------------------------------------------- // internal utilities /** * Once the user has selected the move to make, this * updates the data accordingly. */ private void move() { // the piece that was on the source square is // now on the destination square: myGrid[myDestinationX][myDestinationY] = myGrid[mySelectedX][mySelectedY]; // the source square is emptied: myGrid[mySelectedX][mySelectedY] = 0; if(myDestinationY == 0) { myGrid[myDestinationX][myDestinationY] = 2; } // tell the communicator to inform the other player // of this move: myCommunicator.move(mySelectedX, mySelectedY, myDestinationX, myDestinationY); // deal with the special rules for jumps:: if((mySelectedY - myDestinationY > 1) || (myDestinationY - mySelectedY > 1)) { int jumpedY = (mySelectedY + myDestinationY)/2; int jumpedX = mySelectedX; int parity = mySelectedY % 2; // the coordinates of the jumped square depend on // what row we"re in: if((parity > 0) && (myDestinationX > mySelectedX)) { jumpedX++; } else if((parity == 0) && (mySelectedX > myDestinationX)) { jumpedX--; } // remove the piece that was jumped over: myGrid[jumpedX][jumpedY] = 0; // now get ready to jump again if possible: mySelectedX = myDestinationX; mySelectedY = myDestinationY; myDestinationX = -1; myDestinationY = -1; // see if another jump is possible. // The "true" argument tells the program to return // only jumps because the player can go again ONLY // if there"s a jump: getMoves(mySelectedX, mySelectedY, myPossibleMoves, true); // if there"s another jump possible with the same piece, // allow the player to continue jumping: if(myPossibleMoves.size() != 0) { myIsJumping = true; byte[] landing = (byte[])myPossibleMoves.elementAt(0); myDestinationX = landing[0]; myDestinationY = landing[1]; } else { myTurn = false; myCommunicator.endTurn(); } } else { // since it"s not a jump, we just end the turn // by deselecting everything. mySelectedX = -1; mySelectedY = -1; myDestinationX = -1; myDestinationY = -1; myPossibleMoves.removeAllElements(); myTurn = false; // tell the other player we"re done: myCommunicator.endTurn(); } } /** * Given a square on the grid, get the coordinates * of one of the adjoining (diagonal) squares. * 0 = top left * 1 = top right * 2 = bottom left * 3 = bottom right. * @return the coordinates or null if the desired corner * is off the board. */ private byte[] getCornerCoordinates(byte x, byte y, byte corner) { byte[] retArray = null; if(corner < 2) { y--; } else { y++; } // Where the corner is on the grid depends on // whether this is an odd row or an even row: if((corner % 2 == 0) && (y % 2 != 0)) { x--; } else if((corner % 2 != 0) && (y % 2 == 0)) { x++; } try { if(myGrid[x][y] > -15) { // we don"t really care about the value, this // if statement is just there to get it to // throw if the coordinates aren"t on the board. retArray = new byte[2]; retArray[0] = x; retArray[1] = y; } } catch(ArrayIndexOutOfBoundsException e) { // this throws if the coordinates do not correspond // to a square on the board. It"s not a problem, // so we do nothing--we just return null instead // of returning coordinates since no valid // coordinates correspond to the desired corner. } return(retArray); } /** * Determines where the piece in the given * grid location can move. Clears the Vector * and fills it with the locations that * the piece can move to. * @param jumpsOnly if we should return only moves that * are jumps. */ private void getMoves(byte x, byte y, Vector toFill, boolean jumpsOnly) { toFill.removeAllElements(); // if the square does not contain one of the local player"s // pieces, then there are no corresponding moves and we just // return an empty vector. if(myGrid[x][y] <= 0) { return; } // check each of the four corners to see if the // piece can move there: for(byte i = 0; i < 4; i++) { byte[] coordinates = getCornerCoordinates(x, y, i); // if the coordinate array is null, then the corresponding // corner is off the board and we don"t deal with it. // The later two conditions in the following if statement // ensure that either the move is a forward move or the // current piece is a king: if((coordinates != null) && ((myGrid[x][y] > 1) || (i < 2))) { // if the corner is empty (and we"re not looking // for just jumps), then this is a possible move // so we add it to the vector of moves: if((myGrid[coordinates[0]][coordinates[1]] == 0) && (! jumpsOnly)) { toFill.addElement(coordinates); // if the space is occupied by an opponent, see if we can jump it: } else if(myGrid[coordinates[0]][coordinates[1]] < 0) { byte[] jumpLanding = getCornerCoordinates(coordinates[0], coordinates[1], i); // if the space on the far side of the opponent"s piece // is on the board and is unoccupied, then a jump // is possible, so we add it to the vector of moves: if((jumpLanding != null) && (myGrid[jumpLanding[0]][jumpLanding[1]] == 0)) { toFill.addElement(jumpLanding); } } } } // end for loop }
}
</source>
Dungeon game
<source lang="java">
/*
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); } }
}
</source>
Game Action Example
<source lang="java">
/* J2ME: The Complete Reference James Keogh Publisher: McGraw-Hill ISBN 0072227109
- /
//jad file (please verify the jar size) /* MIDlet-Name: GameActionExample MIDlet-Version: 1.0 MIDlet-Vendor: MyCompany MIDlet-Jar-URL: GameActionExample.jar MIDlet-1: GameActionExample, , GameActionExample MicroEdition-Configuration: CLDC-1.0 MicroEdition-Profile: MIDP-1.0 MIDlet-JAR-SIZE: 100
- /
import javax.microedition.midlet.*; import javax.microedition.lcdui.*; public class GameActionExample extends MIDlet {
private Display display; private MyCanvas canvas; public GameActionExample() { display = Display.getDisplay(this); canvas = new MyCanvas (this); } protected void startApp() { display.setCurrent(canvas); } protected void pauseApp() { } protected void destroyApp( boolean unconditional ) { } public void exitMIDlet() { destroyApp(true); notifyDestroyed(); }
} class MyCanvas extends Canvas implements CommandListener {
private Command exit; private String message; private GameActionExample gameActionExample; private int x, y; public MyCanvas (GameActionExample gameActionExample) { x = 5; y = 5; message = "Use Game Keys"; this.gameActionExample = gameActionExample; exit = new Command("Exit", Command.EXIT, 1); addCommand(exit); setCommandListener(this); } protected void paint(Graphics graphics) { graphics.setColor(255,255,255); graphics.fillRect(0, 0, getWidth(), getHeight()); graphics.setColor(255, 0, 0); graphics.drawString(message, x, y, Graphics.TOP | Graphics.LEFT); } public void commandAction(Command command, Displayable displayable) { if (command == exit) { gameActionExample.exitMIDlet(); } } protected void keyPressed(int key) { switch ( getGameAction(key) ){ case Canvas.UP: message = "up"; y--; break; case Canvas.DOWN: message = "down"; y++; break; case Canvas.LEFT: message = "left"; x--; break; case Canvas.RIGHT: message = "right"; x++; break; case Canvas.FIRE: message = "FIRE"; break; } repaint(); }
}
</source>
Maze game
<source lang="java">
/* Title: J2ME Games With MIDP2 Authors: Carol Hamer Publisher: Apress ISBN: 1590593820
- /
import java.util.Random; import java.util.Vector; import javax.microedition.midlet.*; import javax.microedition.lcdui.*; /**
* This is the main class of the maze game. * * @author Carol Hamer */
public class Maze extends MIDlet implements CommandListener {
//---------------------------------------------------------------- // game object fields /** * The canvas that the maze is drawn on. */ private MazeCanvas myCanvas; /** * The screen that allows the user to alter the size parameters * of the maze. */ private SelectScreen mySelectScreen; //---------------------------------------------------------------- // command fields /** * The button to exit the game. */ private Command myExitCommand = new Command("Exit", Command.EXIT, 99); /** * The command to create a new maze. (This command may appear in a menu) */ private Command myNewCommand = new Command("New Maze", Command.SCREEN, 1); /** * The command to dismiss an alert error message. In MIDP 2.0 * an Alert set to Alert.FOREVER automatically has a default * dismiss command. This program does not use it in order to * allow backwards com */ private Command myAlertDoneCommand = new Command("Done", Command.EXIT, 1); /** * The command to go to the screen that allows the user * to alter the size parameters. (This command may appear in a menu) */ private Command myPrefsCommand = new Command("Size Preferences", Command.SCREEN, 1); //---------------------------------------------------------------- // initialization /** * Initialize the canvas and the commands. */ public Maze() { try { myCanvas = new MazeCanvas(Display.getDisplay(this)); myCanvas.addCommand(myExitCommand); myCanvas.addCommand(myNewCommand); myCanvas.addCommand(myPrefsCommand); myCanvas.setCommandListener(this); } catch(Exception e) { // if there"s an error during creation, display it as an alert. Alert errorAlert = new Alert("error", e.getMessage(), null, AlertType.ERROR); errorAlert.setCommandListener(this); errorAlert.setTimeout(Alert.FOREVER); errorAlert.addCommand(myAlertDoneCommand); Display.getDisplay(this).setCurrent(errorAlert); } } //---------------------------------------------------------------- // implementation of MIDlet /** * Start the application. */ public void startApp() throws MIDletStateChangeException { if(myCanvas != null) { myCanvas.start(); } } /** * Clean up. */ public void destroyApp(boolean unconditional) throws MIDletStateChangeException { myCanvas = null; System.gc(); } /** * Does nothing since this program occupies no shared resources * and little memory. */ public void pauseApp() { } //---------------------------------------------------------------- // implementation of CommandListener /* * Respond to a command issued on the Canvas. * (reset, exit, or change size prefs). */ public void commandAction(Command c, Displayable s) { if(c == myNewCommand) { myCanvas.newMaze(); } else if(c == myAlertDoneCommand) { try { destroyApp(false); notifyDestroyed(); } catch (MIDletStateChangeException ex) { } } else if(c == myPrefsCommand) { if(mySelectScreen == null) { mySelectScreen = new SelectScreen(myCanvas); } Display.getDisplay(this).setCurrent(mySelectScreen); } else if(c == myExitCommand) { try { destroyApp(false); notifyDestroyed(); } catch (MIDletStateChangeException ex) { } } }
}
/**
* This class is the display of the game. * * @author Carol Hamer */
class MazeCanvas extends javax.microedition.lcdui.Canvas {
//--------------------------------------------------------- // static fields /** * color constant */ public static final int BLACK = 0; /** * color constant */ public static final int WHITE = 0xffffff; //--------------------------------------------------------- // instance fields /** * a handle to the display. */ private Display myDisplay; /** * The data object that describes the maze configuration. */ private Grid myGrid; /** * Whether or not the currently displayed maze has * been completed. */ private boolean myGameOver = false; /** * maze dimension: the width of the maze walls. */ private int mySquareSize; /** * maze dimension: the maximum width possible for the maze walls. */ private int myMaxSquareSize; /** * maze dimension: the minimum width possible for the maze walls. */ private int myMinSquareSize; /** * top corner of the display: x-coordiate */ private int myStartX = 0; /** * top corner of the display: y-coordinate */ private int myStartY = 0; /** * how many rows the display is divided into. */ private int myGridHeight; /** * how many columns the display is divided into. */ private int myGridWidth; /** * the maximum number columns the display can be divided into. */ private int myMaxGridWidth; /** * the minimum number columns the display can be divided into. */ private int myMinGridWidth; /** * previous location of the player in the maze: x-coordiate * (in terms of the coordinates of the maze grid, NOT in terms * of the coordinate system of the Canvas.) */ private int myOldX = 1; /** * previous location of the player in the maze: y-coordinate * (in terms of the coordinates of the maze grid, NOT in terms * of the coordinate system of the Canvas.) */ private int myOldY = 1; /** * current location of the player in the maze: x-coordiate * (in terms of the coordinates of the maze grid, NOT in terms * of the coordinate system of the Canvas.) */ private int myPlayerX = 1; /** * current location of the player in the maze: y-coordinate * (in terms of the coordinates of the maze grid, NOT in terms * of the coordinate system of the Canvas.) */ private int myPlayerY = 1; //----------------------------------------------------- // gets / sets /** * Changes the width of the maze walls and calculates how * this change affects the number of rows and columns * the maze can have. * @return the number of columns now that the the * width of the columns has been updated. */ int setColWidth(int colWidth) { if(colWidth < 2) { mySquareSize = 2; } else { mySquareSize = colWidth; } myGridWidth = getWidth() / mySquareSize; if(myGridWidth % 2 == 0) { myGridWidth -= 1; } myGridHeight = getHeight() / mySquareSize; if(myGridHeight % 2 == 0) { myGridHeight -= 1; } myGrid = null; return(myGridWidth); } /** * @return the minimum width possible for the maze walls. */ int getMinColWidth() { return(myMinSquareSize); } /** * @return the maximum width possible for the maze walls. */ int getMaxColWidth() { return(myMaxSquareSize); } /** * @return the maximum number of columns the display can be divided into. */ int getMaxNumCols() { return(myMaxGridWidth); } /** * @return the width of the maze walls. */ int getColWidth() { return(mySquareSize); } /** * @return the number of maze columns the display is divided into. */ int getNumCols() { return(myGridWidth); } //----------------------------------------------------- // initialization and game state changes /** * Constructor performs size calculations. * @throws Exception if the display size is too * small to make a maze. */ public MazeCanvas(Display d) throws Exception { myDisplay = d; // a few calculations to make the right maze // for the current display. int width = getWidth(); int height = getHeight(); // tests indicate that 5 is a good default square size, // but the user can change it... mySquareSize = 5; myMinSquareSize = 3; myMaxGridWidth = width / myMinSquareSize; if(myMaxGridWidth % 2 == 0) { myMaxGridWidth -= 1; } myGridWidth = width / mySquareSize; if(myGridWidth % 2 == 0) { myGridWidth -= 1; } myGridHeight = height / mySquareSize; if(myGridHeight % 2 == 0) { myGridHeight -= 1; } myMinGridWidth = 15; myMaxSquareSize = width / myMinGridWidth; if(myMaxSquareSize > height / myMinGridWidth) { myMaxSquareSize = height / myMinGridWidth; } // if the display is too small to make a reasonable maze, // then we throw an Exception if(myMaxSquareSize < mySquareSize) { throw(new Exception("Display too small")); } } /** * This is called as soon as the application begins. */ void start() { myDisplay.setCurrent(this); repaint(); } /** * discard the current maze and draw a new one. */ void newMaze() { myGameOver = false; // throw away the current maze. myGrid = null; // set the player back to the beginning of the maze. myPlayerX = 1; myPlayerY = 1; myOldX = 1; myOldY = 1; myDisplay.setCurrent(this); // paint the new maze repaint(); } //------------------------------------------------------- // graphics methods /** * Create and display a maze if necessary, otherwise just * move the player. Since the motion in this game is * very simple, it is not necessary to repaint the whole * maze each time, just the player + erase the square * that the player just left.. */ protected void paint(Graphics g) { // If there is no current maze, create one and draw it. if(myGrid == null) { int width = getWidth(); int height = getHeight(); // create the underlying data of the maze. myGrid = new Grid(myGridWidth, myGridHeight); // draw the maze: // loop through the grid data and color each square the // right color for(int i = 0; i < myGridWidth; i++) { for(int j = 0; j < myGridHeight; j++) { if(myGrid.mySquares[i][j] == 0) { g.setColor(BLACK); } else { g.setColor(WHITE); } // fill the square with the appropriate color g.fillRect(myStartX + (i*mySquareSize), myStartY + (j*mySquareSize), mySquareSize, mySquareSize); } } // fill the extra space outside of the maze g.setColor(BLACK); g.fillRect(myStartX + ((myGridWidth-1) * mySquareSize), myStartY, width, height); // erase the exit path: g.setColor(WHITE); g.fillRect(myStartX + ((myGridWidth-1) * mySquareSize), myStartY + ((myGridHeight-2) * mySquareSize), width, height); // fill the extra space outside of the maze g.setColor(BLACK); g.fillRect(myStartX, myStartY + ((myGridHeight-1) * mySquareSize), width, height); } // draw the player (red): g.setColor(255, 0, 0); g.fillRoundRect(myStartX + (mySquareSize)*myPlayerX, myStartY + (mySquareSize)*myPlayerY, mySquareSize, mySquareSize, mySquareSize, mySquareSize); // erase the previous location if((myOldX != myPlayerX) || (myOldY != myPlayerY)) { g.setColor(WHITE); g.fillRect(myStartX + (mySquareSize)*myOldX, myStartY + (mySquareSize)*myOldY, mySquareSize, mySquareSize); } // if the player has reached the end of the maze, // we display the end message. if(myGameOver) { // perform some calculations to place the text correctly: int width = getWidth(); int height = getHeight(); Font font = g.getFont(); int fontHeight = font.getHeight(); int fontWidth = font.stringWidth("Maze Completed"); g.setColor(WHITE); g.fillRect((width - fontWidth)/2, (height - fontHeight)/2, fontWidth + 2, fontHeight); // write in red g.setColor(255, 0, 0); g.setFont(font); g.drawString("Maze Completed", (width - fontWidth)/2, (height - fontHeight)/2, g.TOP|g.LEFT); } } /** * Move the player. */ public void keyPressed(int keyCode) { if(! myGameOver) { int action = getGameAction(keyCode); switch (action) { case LEFT: if((myGrid.mySquares[myPlayerX-1][myPlayerY] == 1) && (myPlayerX != 1)) { myOldX = myPlayerX; myOldY = myPlayerY; myPlayerX -= 2; repaint(); } break; case RIGHT: if(myGrid.mySquares[myPlayerX+1][myPlayerY] == 1) { myOldX = myPlayerX; myOldY = myPlayerY; myPlayerX += 2; repaint(); } else if((myPlayerX == myGrid.mySquares.length - 2) && (myPlayerY == myGrid.mySquares[0].length - 2)) { myOldX = myPlayerX; myOldY = myPlayerY; myPlayerX += 2; myGameOver = true; repaint(); } break; case UP: if(myGrid.mySquares[myPlayerX][myPlayerY-1] == 1) { myOldX = myPlayerX; myOldY = myPlayerY; myPlayerY -= 2; repaint(); } break; case DOWN: if(myGrid.mySquares[myPlayerX][myPlayerY+1] == 1) { myOldX = myPlayerX; myOldY = myPlayerY; myPlayerY += 2; repaint(); } break; } } }
} /**
* This is the screen that allows the user to modify the * width of the maze walls.. * * @author Carol Hamer */
class SelectScreen extends Form
implements ItemStateListener, CommandListener { //---------------------------------------------------------------- // fields /** * The "Done" button to exit this screen and return to the maze. */ private Command myExitCommand = new Command("Done", Command.EXIT, 1); /** * The gague that modifies the width of the maze walls. */ private Gauge myWidthGauge; /** * The gague that displays the number of columns of the maze. */ private Gauge myColumnsGauge; /** * A handle to the main game canvas. */ private MazeCanvas myCanvas; //---------------------------------------------------------------- // initialization /** * Create the gagues and place them on the screen. */ public SelectScreen(MazeCanvas canvas) { super("Size Preferences"); addCommand(myExitCommand); setCommandListener(this); myCanvas = canvas; setItemStateListener(this); myWidthGauge = new Gauge("Column Width", true, myCanvas.getMaxColWidth(), myCanvas.getColWidth()); myColumnsGauge = new Gauge("Number of Columns", false, myCanvas.getMaxNumCols(), myCanvas.getNumCols()); // Warning: the setLayout method does not exist in // MIDP 1.4. If there is any chance that a target // device will be using MIDP 1.4, comment out the // following two lines: //myWidthGauge.setLayout(Item.LAYOUT_CENTER); //myColumnsGauge.setLayout(Item.LAYOUT_CENTER); append(myWidthGauge); append(myColumnsGauge); } //---------------------------------------------------------------- // implementation of ItemStateListener /** * Respond to the user changing the width. */ public void itemStateChanged(Item item) { if(item == myWidthGauge) { int val = myWidthGauge.getValue(); if(val < myCanvas.getMinColWidth()) { myWidthGauge.setValue(myCanvas.getMinColWidth()); } else { int numCols = myCanvas.setColWidth(val); myColumnsGauge.setValue(numCols); } } } //---------------------------------------------------------------- // implementation of CommandListener /* * Respond to a command issued on this screen. * (either reset or exit). */ public void commandAction(Command c, Displayable s) { if(c == myExitCommand) { myCanvas.newMaze(); } }
}
/**
* This class contains the data necessary to draw the maze. * * @author Carol Hamer */
class Grid {
/** * Random number generator to create a random maze. */ private Random myRandom = new Random(); /** * data for which squares are filled and which are blank. * 0 = black * 1 = white * values higher than 1 are used during the maze creation * algorithm. * 2 = the square could possibly be appended to the maze this round. * 3 = the square"s color is not yet decided, and the square is * not close enough to be appended to the maze this round. */ int[][] mySquares; //-------------------------------------------------------- // maze generation methods /** * Create a new maze. */ public Grid(int width, int height) { mySquares = new int[width][height]; // initialize all of the squares to white except a lattice // framework of black squares. for(int i = 1; i < width - 1; i++) { for(int j = 1; j < height - 1; j++) { if((i % 2 == 1) || (j % 2 == 1)) { mySquares[i][j] = 1; } } } // the entrance to the maze is at (0,1). mySquares[0][1] = 1; createMaze(); } /** * This method randomly generates the maze. */ private void createMaze() { // create an initial framework of black squares. for(int i = 1; i < mySquares.length - 1; i++) { for(int j = 1; j < mySquares[i].length - 1; j++) { if((i + j) % 2 == 1) { mySquares[i][j] = 0; } } } // initialize the squares that can be either black or white // depending on the maze. // first we set the value to 3 which means undecided. for(int i = 1; i < mySquares.length - 1; i+=2) { for(int j = 1; j < mySquares[i].length - 1; j+=2) { mySquares[i][j] = 3; } } // Then those squares that can be selected to be open // (white) paths are given the value of 2. // We randomly select the square where the tree of maze // paths will begin. The maze is generated starting from // this initial square and branches out from here in all // directions to fill the maze grid. Vector possibleSquares = new Vector(mySquares.length * mySquares[0].length); int[] startSquare = new int[2]; startSquare[0] = getRandomInt(mySquares.length / 2)*2 + 1; startSquare[1] = getRandomInt(mySquares[0].length / 2)*2 + 1; mySquares[startSquare[0]][startSquare[1]] = 2; possibleSquares.addElement(startSquare); // Here we loop to select squares one by one to append to // the maze pathway tree. while(possibleSquares.size() > 0) { // the next square to be joined on is selected randomly. int chosenIndex = getRandomInt(possibleSquares.size()); int[] chosenSquare = (int[])possibleSquares.elementAt(chosenIndex); // we set the chosen square to white and then // remove it from the list of possibleSquares (i.e. squares // that can possibly be added to the maze), and we link // the new square to the maze. mySquares[chosenSquare[0]][chosenSquare[1]] = 1; possibleSquares.removeElementAt(chosenIndex); link(chosenSquare, possibleSquares); } // now that the maze has been completely generated, we // throw away the objects that were created during the // maze creation algorithm and reclaim the memory. possibleSquares = null; System.gc(); } /** * internal to createMaze. Checks the four squares surrounding * the chosen square. Of those that are already connected to * the maze, one is randomly selected to be joined to the * current square (to attach the current square to the * growing maze). Those squares that were not previously in * a position to be joined to the maze are added to the list * of "possible" squares (that could be chosen to be attached * to the maze in the next round). */ private void link(int[] chosenSquare, Vector possibleSquares) { int linkCount = 0; int i = chosenSquare[0]; int j = chosenSquare[1]; int[] links = new int[8]; if(i >= 3) { if(mySquares[i - 2][j] == 1) { links[2*linkCount] = i - 1; links[2*linkCount + 1] = j; linkCount++; } else if(mySquares[i - 2][j] == 3) { mySquares[i - 2][j] = 2; int[] newSquare = new int[2]; newSquare[0] = i - 2; newSquare[1] = j; possibleSquares.addElement(newSquare); } } if(j + 3 <= mySquares[i].length) { if(mySquares[i][j + 2] == 3) { mySquares[i][j + 2] = 2; int[] newSquare = new int[2]; newSquare[0] = i; newSquare[1] = j + 2; possibleSquares.addElement(newSquare); } else if(mySquares[i][j + 2] == 1) { links[2*linkCount] = i; links[2*linkCount + 1] = j + 1; linkCount++; } } if(j >= 3) { if(mySquares[i][j - 2] == 3) { mySquares[i][j - 2] = 2; int[] newSquare = new int[2]; newSquare[0] = i; newSquare[1] = j - 2; possibleSquares.addElement(newSquare); } else if(mySquares[i][j - 2] == 1) { links[2*linkCount] = i; links[2*linkCount + 1] = j - 1; linkCount++; } } if(i + 3 <= mySquares.length) { if(mySquares[i + 2][j] == 3) { mySquares[i + 2][j] = 2; int[] newSquare = new int[2]; newSquare[0] = i + 2; newSquare[1] = j; possibleSquares.addElement(newSquare); } else if(mySquares[i + 2][j] == 1) { links[2*linkCount] = i + 1; links[2*linkCount + 1] = j; linkCount++; } } if(linkCount > 0) { int linkChoice = getRandomInt(linkCount); int linkX = links[2*linkChoice]; int linkY = links[2*linkChoice + 1]; mySquares[linkX][linkY] = 1; int[] removeSquare = new int[2]; removeSquare[0] = linkX; removeSquare[1] = linkY; possibleSquares.removeElement(removeSquare); } } /** * a randomization utility. * @param upper the upper bound for the random int. * @return a random non-negative int less than the bound upper. */ public int getRandomInt(int upper) { int retVal = myRandom.nextInt() % upper; if(retVal < 0) { retVal += upper; } return(retVal); }
}
</source>
Sweep
<source lang="java">
/* Wireless Java 2nd edition Jonathan Knudsen Publisher: Apress ISBN: 1590590775
- /
import javax.microedition.lcdui.*; import javax.microedition.midlet.*; public class Sweep extends MIDlet {
public void startApp() { final SweepCanvas sweeper = new SweepCanvas(); sweeper.start(); sweeper.addCommand(new Command("Exit", Command.EXIT, 0)); sweeper.setCommandListener(new CommandListener() { public void commandAction(Command c, Displayable s) { sweeper.stop(); notifyDestroyed(); } }); Display.getDisplay(this).setCurrent(sweeper); } public void pauseApp() {} public void destroyApp(boolean unconditional) {}
} class SweepCanvas extends Canvas implements Runnable {
private boolean mTrucking; private int mTheta; private int mBorder; private int mDelay; public SweepCanvas() { mTheta = 0; mBorder = 10; mDelay = 50; } public void start() { mTrucking = true; Thread t = new Thread(this); t.start(); } public void stop() { mTrucking = false; } public void paint(Graphics g) { int width = getWidth(); int height = getHeight(); // Clear the Canvas. g.setGrayScale(255); g.fillRect(0, 0, width - 1, height - 1); int x = mBorder; int y = mBorder; int w = width - mBorder * 2; int h = height - mBorder * 2; for (int i = 0; i < 8; i++) { g.setGrayScale((8 - i) * 32 - 16); g.fillArc(x, y, w, h, mTheta + i * 10, 10); g.fillArc(x, y, w, h, (mTheta + 180) % 360 + i * 10, 10); } } public void run() { while (mTrucking) { mTheta = (mTheta + 1) % 360; repaint(); try { Thread.sleep(mDelay); } catch (InterruptedException ie) {} } }
}
</source>
Sweep Game
<source lang="java">
/* Wireless Java 2nd edition Jonathan Knudsen Publisher: Apress ISBN: 1590590775
- /
import javax.microedition.lcdui.*; import javax.microedition.lcdui.game.*; import javax.microedition.midlet.*; public class SweepGame extends MIDlet {
public void startApp() { final SweepGameCanvas sweeper = new SweepGameCanvas(); sweeper.start(); sweeper.addCommand(new Command("Exit", Command.EXIT, 0)); sweeper.setCommandListener(new CommandListener() { public void commandAction(Command c, Displayable s) { sweeper.stop(); notifyDestroyed(); } }); Display.getDisplay(this).setCurrent(sweeper); } public void pauseApp() {} public void destroyApp(boolean unconditional) {}
} class SweepGameCanvas extends GameCanvas implements Runnable {
private boolean mTrucking; private int mTheta; private int mBorder; private int mDelay; public SweepGameCanvas() { super(true); mTheta = 0; mBorder = 10; mDelay = 50; } public void start() { mTrucking = true; Thread t = new Thread(this); t.start(); } public void stop() { mTrucking = false; } public void render(Graphics g) { int width = getWidth(); int height = getHeight(); // Clear the Canvas. g.setGrayScale(255); g.fillRect(0, 0, width - 1, height - 1); int x = mBorder; int y = mBorder; int w = width - mBorder * 2; int h = height - mBorder * 2; for (int i = 0; i < 8; i++) { g.setGrayScale((8 - i) * 32 - 16); g.fillArc(x, y, w, h, mTheta + i * 10, 10); g.fillArc(x, y, w, h, (mTheta + 180) % 360 + i * 10, 10); } } public void run() { Graphics g = getGraphics(); while (mTrucking) { mTheta = (mTheta + 1) % 360; render(g); flushGraphics(); try { Thread.sleep(mDelay); } catch (InterruptedException ie) {} } }
}
</source>
Tumbleweed game
<source lang="java">
/*
Title: J2ME Games With MIDP2 Authors: Carol Hamer Publisher: Apress ISBN: 1590593820 */
import javax.microedition.media.*; import javax.microedition.media.control.*; import java.util.Random; import javax.microedition.lcdui.game.*; import javax.microedition.midlet.*; import javax.microedition.lcdui.*; /**
* This is the main class of the tumbleweed game. * * @author Carol Hamer */
public class Jump extends MIDlet implements CommandListener {
//--------------------------------------------------------- // commands /** * the command to end the game. */ private Command myExitCommand = new Command("Exit", Command.EXIT, 99); /** * 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("Play Again", Command.SCREEN, 1); /** * The command to start/pause the music. (This command may appear in a menu) */ private Command myMusicCommand = new Command("Music", Command.SCREEN, 2); //--------------------------------------------------------- // game object fields /** * the the canvas that all of the game will be drawn on. */ private JumpCanvas myCanvas; //--------------------------------------------------------- // thread fields /** * the thread that advances the cowboy. */ private GameThread myGameThread; /** * The class that plays music if the user wants. */ //private MusicMaker myMusicMaker; private ToneControlMusicMaker myMusicMaker; /** * The thread tha sets tumbleweeds in motion at random intervals. */ private TumbleweedThread myTumbleweedThread; /** * if the user has paused the game. */ private boolean myGamePause; /** * if the game is paused because it is hidden. */ private boolean myHiddenPause; //----------------------------------------------------- // initialization and game state changes /** * Initialize the canvas and the commands. */ public Jump() { try { myCanvas = new JumpCanvas(this); myCanvas.addCommand(myExitCommand); myCanvas.addCommand(myMusicCommand); myCanvas.addCommand(myPauseCommand); myCanvas.setCommandListener(this); } catch (Exception e) { errorMsg(e); } } /** * Switch the command to the play again command. */ void setNewCommand() { myCanvas.removeCommand(myPauseCommand); myCanvas.removeCommand(myGoCommand); myCanvas.addCommand(myNewCommand); } /** * Switch the command to the go command. */ private void setGoCommand() { myCanvas.removeCommand(myPauseCommand); myCanvas.removeCommand(myNewCommand); myCanvas.addCommand(myGoCommand); } /** * Switch the command to the pause command. */ private 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 { try { if (myCanvas != null) { myCanvas.start(); myCanvas.flushKeys(); systemStartThreads(); } } catch (Exception e) { errorMsg(e); } } /** * stop and throw out the garbage. */ public void destroyApp(boolean unconditional) throws MIDletStateChangeException { try { stopThreads(); myCanvas = null; System.gc(); } catch (Exception e) { errorMsg(e); } } /** * request the game to pause. This method is called by the application * management software, not in response to a user pausing the game. */ public void pauseApp() { try { if (myCanvas != null) { setGoCommand(); systemPauseThreads(); } } catch (Exception e) { errorMsg(e); } } //---------------------------------------------------------------- // implementation of CommandListener /* * Respond to a command issued on the Canvas. (either reset or exit). */ public void commandAction(Command c, Displayable s) { try { if (c == myGoCommand) { myCanvas.removeCommand(myGoCommand); myCanvas.addCommand(myPauseCommand); myCanvas.flushKeys(); userStartThreads(); } else if (c == myPauseCommand) { myCanvas.removeCommand(myPauseCommand); myCanvas.addCommand(myGoCommand); userPauseThreads(); } else if (c == myNewCommand) { myCanvas.removeCommand(myNewCommand); myCanvas.addCommand(myPauseCommand); System.gc(); myCanvas.reset(); myCanvas.flushKeys(); myHiddenPause = false; myGamePause = false; startThreads(); } else if (c == myMusicCommand) { if (myMusicMaker != null) { myMusicMaker.toggle(); myCanvas.repaint(); myCanvas.serviceRepaints(); } } else if ((c == myExitCommand)/* || (c == Alert.DISMISS_COMMAND)*/) { try { destroyApp(false); notifyDestroyed(); } catch (MIDletStateChangeException ex) { } } } catch (Exception e) { errorMsg(e); } } //------------------------------------------------------- // thread methods /** * start up all of the game"s threads. Creates them if necessary. to be * called when the user hits the go command. */ private synchronized void userStartThreads() throws Exception { myGamePause = false; if (!myHiddenPause) { startThreads(); } } /** * start up all of the game"s threads. Creates them if necessary. used by * showNotify */ synchronized void systemStartThreads() throws Exception { myHiddenPause = false; if (!myGamePause) { startThreads(); } } /** * start up all of the game"s threads. Creates them if necessary. internal * version. note: if this were synchronized, whould it cause deadlock? */ private void startThreads() throws Exception { if (myGameThread == null) { myGameThread = new GameThread(myCanvas); myGameThread.start(); } else { myGameThread.resumeGame(); } if (myTumbleweedThread == null) { myTumbleweedThread = new TumbleweedThread(myCanvas); myTumbleweedThread.start(); } else { myTumbleweedThread.resumeGame(); } if (myMusicMaker == null) { myMusicMaker = new ToneControlMusicMaker(); //myMusicMaker = new MusicMaker(); myMusicMaker.start(); } else { myMusicMaker.resumeGame(); } } /** * Pause all of the threads started by this game. to be called when the user * hits the pause command. */ synchronized void userPauseThreads() { myGamePause = true; pauseThreads(); } /** * Pause all of the threads started by this game. used by hideNotify */ void systemPauseThreads() { myHiddenPause = true; pauseThreads(); } /** * start up all of the game"s threads. Creates them if necessary. internal * version. note: if this were synchronized, whould it cause deadlock? */ private void pauseThreads() { if (myGameThread != null) { myGameThread.pauseGame(); } if (myTumbleweedThread != null) { myTumbleweedThread.pauseGame(); } if (myMusicMaker != null) { myMusicMaker.pauseGame(); } } /** * Stop all of the threads started by this game and delete them as they are * no longer usable. */ private synchronized void stopThreads() { if (myGameThread != null) { myGameThread.requestStop(); } if (myTumbleweedThread != null) { myTumbleweedThread.requestStop(); } if (myMusicMaker != null) { myMusicMaker.requestStop(); } myGameThread = null; myTumbleweedThread = null; myMusicMaker = null; } //------------------------------------------------------- // 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 is the display of the game. * * @author Carol Hamer */
class JumpCanvas extends javax.microedition.lcdui.game.GameCanvas {
//--------------------------------------------------------- // dimension fields // (constant after initialization) /** * the height of the green region below the ground. */ static final int GROUND_HEIGHT = 32; /** * a screen dimension. */ static final int CORNER_X = 0; /** * a screen dimension. */ static final int CORNER_Y = 0; /** * a screen dimension. */ static int DISP_WIDTH; /** * a screen dimension. */ static int DISP_HEIGHT; /** * a font dimension. */ static int FONT_HEIGHT; /** * the default font. */ static Font FONT; /** * a font dimension. */ static int SCORE_WIDTH; /** * The width of the string that displays the time, saved for placement of * time display. */ static int TIME_WIDTH; /** * 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 Jump myJump; /** * the LayerManager that handles the game graphics. */ private JumpManager myManager; /** * whether or not the game has ended. */ private boolean myGameOver; /** * the player"s score. */ private int myScore = 0; /** * How many ticks we start with. */ private int myInitialGameTicks = 950; /** * this is saved to determine if the time string needs to be recomputed. */ private int myOldGameTicks = myInitialGameTicks; /** * the number of game ticks that have passed. */ private int myGameTicks = myOldGameTicks; /** * we save the time string to avoid recreating it unnecessarily. */ private static String myInitialString = "1: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; myJump.userPauseThreads(); } /** * @return a handle to the tumbleweed objects. */ Tumbleweed[] getTumbleweeds() { return (myManager.getTumbleweeds()); } //----------------------------------------------------- // initialization and game state changes /** * Constructor sets the data, performs dimension calculations, and creates * the graphical objects. */ public JumpCanvas(Jump midlet) throws Exception { super(false); myDisplay = Display.getDisplay(midlet); myJump = midlet; // calculate the dimensions DISP_WIDTH = getWidth(); DISP_HEIGHT = getHeight(); Display disp = Display.getDisplay(myJump); if (disp.numColors() < 256) { throw (new Exception("game requires 256 shades")); } 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")); } FONT = getGraphics().getFont(); FONT_HEIGHT = FONT.getHeight(); SCORE_WIDTH = FONT.stringWidth("Score: 000"); TIME_WIDTH = FONT.stringWidth("Time: " + myInitialString); if (myManager == null) { myManager = new JumpManager(CORNER_X, CORNER_Y + FONT_HEIGHT * 2, DISP_WIDTH, DISP_HEIGHT - FONT_HEIGHT * 2 - GROUND_HEIGHT); } } /** * This is called as soon as the application begins. */ void start() { myGameOver = false; myDisplay.setCurrent(this); repaint(); } /** * sets all variables back to their initial positions. */ void reset() { myManager.reset(); myScore = 0; myGameOver = false; myGameTicks = myInitialGameTicks; myOldGameTicks = myInitialGameTicks; repaint(); } /** * clears the key states. */ void flushKeys() { getKeyStates(); } /** * pause the game when it"s hidden. */ protected void hideNotify() { try { myJump.systemPauseThreads(); } catch (Exception oe) { myJump.errorMsg(oe); } } /** * When it comes back into view, unpause it. */ protected void showNotify() { try { myJump.systemStartThreads(); } catch (Exception oe) { myJump.errorMsg(oe); } } //------------------------------------------------------- // graphics methods /** * paint the game graphic on the screen. */ public void paint(Graphics g) { // clear the screen: g.setColor(WHITE); g.fillRect(CORNER_X, CORNER_Y, DISP_WIDTH, DISP_HEIGHT); // color the grass green g.setColor(0, 255, 0); g.fillRect(CORNER_X, CORNER_Y + DISP_HEIGHT - GROUND_HEIGHT, DISP_WIDTH, DISP_HEIGHT); // paint the layer manager: try { myManager.paint(g); } catch (Exception e) { myJump.errorMsg(e); } // draw the time and score g.setColor(BLACK); g.setFont(FONT); g.drawString("Score: " + myScore, (DISP_WIDTH - SCORE_WIDTH) / 2, DISP_HEIGHT + 5 - GROUND_HEIGHT, g.TOP | g.LEFT); g.drawString("Time: " + formatTime(), (DISP_WIDTH - TIME_WIDTH) / 2, CORNER_Y + FONT_HEIGHT, g.TOP | g.LEFT); // write game over if the game is over if (myGameOver) { myJump.setNewCommand(); // clear the top region: g.setColor(WHITE); g.fillRect(CORNER_X, CORNER_Y, DISP_WIDTH, FONT_HEIGHT * 2 + 1); int goWidth = FONT.stringWidth("Game Over"); g.setColor(BLACK); g.setFont(FONT); g.drawString("Game Over", (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) + 1 != 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 /** * Tell the layer manager to advance the layers and then update the display. */ void advance() { myGameTicks--; myScore += myManager.advance(myGameTicks); if (myGameTicks == 0) { setGameOver(); } // paint the display try { paint(getGraphics()); flushGraphics(); } catch (Exception e) { myJump.errorMsg(e); } } /** * Respond to keystrokes. */ public void checkKeys() { if (!myGameOver) { int keyState = getKeyStates(); if ((keyState & LEFT_PRESSED) != 0) { myManager.setLeft(true); } if ((keyState & RIGHT_PRESSED) != 0) { myManager.setLeft(false); } if ((keyState & UP_PRESSED) != 0) { myManager.jump(); } } }
} /**
* This class draws the background grass. * * @author Carol Hamer */
class Grass extends TiledLayer {
//--------------------------------------------------------- // dimension fields // (constant after initialization) /** * The width of the square tiles that make up this layer.. */ static final int TILE_WIDTH = 20; /** * This is the order that the frames should be displayed for the animation. */ static final int[] FRAME_SEQUENCE = { 2, 3, 2, 4 }; /** * This gives the number of squares of grass to put along the bottom of the * screen. */ static int COLUMNS; /** * After how many tiles does the background repeat. */ static final int CYCLE = 5; /** * the fixed Y coordinate of the strip of grass. */ static int TOP_Y; //--------------------------------------------------------- // instance fields /** * Which tile we are currently on in the frame sequence. */ private int mySequenceIndex = 0; /** * The index to use in the static tiles array to get the animated tile.. */ private int myAnimatedTileIndex; //--------------------------------------------------------- // gets / sets /** * Takes the width of the screen and sets my columns to the correct * corresponding number */ static int setColumns(int screenWidth) { COLUMNS = ((screenWidth / 20) + 1) * 3; return (COLUMNS); } //--------------------------------------------------------- // initialization /** * constructor initializes the image and animation. */ public Grass() throws Exception { super(setColumns(JumpCanvas.DISP_WIDTH), 1, Image .createImage("/images/grass.png"), TILE_WIDTH, TILE_WIDTH); TOP_Y = JumpManager.DISP_HEIGHT - TILE_WIDTH; setPosition(0, TOP_Y); myAnimatedTileIndex = createAnimatedTile(2); for (int i = 0; i < COLUMNS; i++) { if ((i % CYCLE == 0) || (i % CYCLE == 2)) { setCell(i, 0, myAnimatedTileIndex); } else { setCell(i, 0, 1); } } } //--------------------------------------------------------- // graphics /** * sets the grass back to its initial position.. */ void reset() { setPosition(-(TILE_WIDTH * CYCLE), TOP_Y); mySequenceIndex = 0; setAnimatedTile(myAnimatedTileIndex, FRAME_SEQUENCE[mySequenceIndex]); } /** * alter the background image appropriately for this frame.. * * @param left * whether or not the player is moving left */ void advance(int tickCount) { if (tickCount % 2 == 0) { // slow the animation down a little mySequenceIndex++; mySequenceIndex %= 4; setAnimatedTile(myAnimatedTileIndex, FRAME_SEQUENCE[mySequenceIndex]); } }
} /**
* 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 boolean myShouldStop; /** * A handle back to the graphical components. */ private JumpCanvas myJumpCanvas; /** * The System.time of the last screen refresh, used to regulate refresh * speed. */ private long myLastRefreshTime; //---------------------------------------------------------- // initialization /** * standard constructor. */ GameThread(JumpCanvas canvas) { myJumpCanvas = 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 pauseGame() { myShouldPause = true; } /** * restart the game after a pause. */ synchronized void resumeGame() { myShouldPause = false; notify(); } /** * stops the game. */ synchronized void requestStop() { myShouldStop = true; notify(); } /** * start the game.. */ public void run() { // flush any keystrokes that occurred before the // game started: myJumpCanvas.flushKeys(); myShouldStop = false; myShouldPause = false; while (true) { myLastRefreshTime = System.currentTimeMillis(); if (myShouldStop) { break; } synchronized (this) { while (myShouldPause) { try { wait(); } catch (Exception e) { } } } myJumpCanvas.checkKeys(); myJumpCanvas.advance(); // 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) { } } } }
} /**
* This class represents the player. * * @author Carol Hamer */
class Cowboy extends Sprite {
//--------------------------------------------------------- // dimension fields /** * The width of the cowboy"s bounding rectangle. */ static final int WIDTH = 32; /** * The height of the cowboy"s bounding rectangle. */ static final int HEIGHT = 48; /** * This is the order that the frames should be displayed for the animation. */ static final int[] FRAME_SEQUENCE = { 3, 2, 1, 2 }; //--------------------------------------------------------- // instance fields /** * the X coordinate of the cowboy where the cowboy starts the game. */ private int myInitialX; /** * the Y coordinate of the cowboy when not jumping. */ private int myInitialY; /** * The jump index that indicates that no jump is currently in progress.. */ private int myNoJumpInt = -6; /** * Where the cowboy is in the jump sequence. */ private int myIsJumping = myNoJumpInt; /** * If the cowboy is currently jumping, this keeps track of how many points * have been scored so far during the jump. This helps the calculation of * bonus points since the points being scored depend on how many tumbleweeds * are jumped in a single jump. */ private int myScoreThisJump = 0; //--------------------------------------------------------- // initialization /** * constructor initializes the image and animation. */ public Cowboy(int initialX, int initialY) throws Exception { super(Image.createImage("/images/cowboy.png"), WIDTH, HEIGHT); myInitialX = initialX; myInitialY = initialY; // we define the reference pixel to be in the middle // of the cowboy image so that when the cowboy turns // from right to left (and vice versa) he does not // appear to move to a different location. defineReferencePixel(WIDTH / 2, 0); setRefPixelPosition(myInitialX, myInitialY); setFrameSequence(FRAME_SEQUENCE); } //--------------------------------------------------------- // game methods /** * If the cowboy has landed on a tumbleweed, we decrease the score. */ int checkCollision(Tumbleweed tumbleweed) { int retVal = 0; if (collidesWith(tumbleweed, true)) { retVal = 1; // once the cowboy has collided with the tumbleweed, // that tumbleweed is done for now, so we call reset // which makes it invisible and ready to be reused. tumbleweed.reset(); } return (retVal); } /** * set the cowboy back to its initial position. */ void reset() { myIsJumping = myNoJumpInt; setRefPixelPosition(myInitialX, myInitialY); setFrameSequence(FRAME_SEQUENCE); myScoreThisJump = 0; // at first the cowboy faces right: setTransform(TRANS_NONE); } //--------------------------------------------------------- // graphics /** * alter the cowboy image appropriately for this frame.. */ void advance(int tickCount, boolean left) { if (left) { // use the mirror image of the cowboy graphic when // the cowboy is going towards the left. setTransform(TRANS_MIRROR); move(-1, 0); } else { // use the (normal, untransformed) image of the cowboy // graphic when the cowboy is going towards the right. setTransform(TRANS_NONE); move(1, 0); } // this section advances the animation: // every third time through the loop, the cowboy // image is changed to the next image in the walking // animation sequence: if (tickCount % 3 == 0) { // slow the animation down a little if (myIsJumping == myNoJumpInt) { // if he"s not jumping, set the image to the next // frame in the walking animation: nextFrame(); } else { // if he"s jumping, advance the jump: // the jump continues for several passes through // the main game loop, and myIsJumping keeps track // of where we are in the jump: myIsJumping++; if (myIsJumping < 0) { // myIsJumping starts negative, and while it"s // still negative, the cowboy is going up. // here we use a shift to make the cowboy go up a // lot in the beginning of the jump, and ascend // more and more slowly as he reaches his highest // position: setRefPixelPosition(getRefPixelX(), getRefPixelY() - (2 << (-myIsJumping))); } else { // once myIsJumping is negative, the cowboy starts // going back down until he reaches the end of the // jump sequence: if (myIsJumping != -myNoJumpInt - 1) { setRefPixelPosition(getRefPixelX(), getRefPixelY() + (2 << myIsJumping)); } else { // once the jump is done, we reset the cowboy to // his non-jumping position: myIsJumping = myNoJumpInt; setRefPixelPosition(getRefPixelX(), myInitialY); // we set the image back to being the walking // animation sequence rather than the jumping image: setFrameSequence(FRAME_SEQUENCE); // myScoreThisJump keeps track of how many points // were scored during the current jump (to keep // track of the bonus points earned for jumping // multiple tumbleweeds). Once the current jump is done, // we set it back to zero. myScoreThisJump = 0; } } } } } /** * makes the cowboy jump. */ void jump() { if (myIsJumping == myNoJumpInt) { myIsJumping++; // switch the cowboy to use the jumping image // rather than the walking animation images: setFrameSequence(null); setFrame(0); } } /** * This is called whenever the cowboy clears a tumbleweed so that more * points are scored when more tumbleweeds are cleared in a single jump. */ int increaseScoreThisJump() { if (myScoreThisJump == 0) { myScoreThisJump++; } else { myScoreThisJump *= 2; } return (myScoreThisJump); }
} /**
* This class contains the loop that keeps the game running. * * @author Carol Hamer */
class TumbleweedThread 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 boolean myShouldStop; /** * A handle back to the graphical components. */ private Tumbleweed[] myTumbleweeds; /** * Random number generator to randomly decide when to appear. */ private Random myRandom = new Random(); //---------------------------------------------------------- // initialization /** * standard constructor, sets data. */ TumbleweedThread(JumpCanvas canvas) throws Exception { myTumbleweeds = canvas.getTumbleweeds(); } //---------------------------------------------------------- // actions /** * pause the thread. */ void pauseGame() { myShouldPause = true; } /** * restart the thread after a pause. */ synchronized void resumeGame() { myShouldPause = false; notify(); } /** * stops the thread. */ synchronized void requestStop() { myShouldStop = true; notify(); } /** * start the thread.. */ public void run() { myShouldStop = false; myShouldPause = false; while (true) { if (myShouldStop) { break; } synchronized (this) { while (myShouldPause) { try { wait(); } catch (Exception e) { } } } // wait a random length of time: int waitTime = (1 + getRandomInt(10)) * 100; synchronized (this) { try { wait(waitTime); } catch (Exception e) { } } if (!myShouldPause) { // randomly select which one to set in motion and // tell it to go. If the chosen tumbleweed is // currently visible, it will not be affected int whichWeed = getRandomInt(myTumbleweeds.length); myTumbleweeds[whichWeed].go(); } } } //---------------------------------------------------------- // randomization utilities /** * Gets a random int between zero and the param upper (exclusive). */ public int getRandomInt(int upper) { int retVal = myRandom.nextInt() % upper; if (retVal < 0) { retVal += upper; } return (retVal); }
} /**
* This class represents the tumbleweeds that the player must jump over. * * @author Carol Hamer */
class Tumbleweed extends Sprite {
//--------------------------------------------------------- // dimension fields /** * The width of the tumbleweed"s bounding square. */ static final int WIDTH = 16; //--------------------------------------------------------- // instance fields /** * whether or not this tumbleweed has been jumped over. This is used to * calculate the score. */ private boolean myJumpedOver; /** * whether or not this tumbleweed enters from the left. */ private boolean myLeft; /** * the Y coordinate of the tumbleweed. */ private int myY; /** * the leftmost visible pixel. */ private int myCurrentLeftBound; /** * the rightmost visible pixel. */ private int myCurrentRightBound; //--------------------------------------------------------- // initialization /** * constructor initializes the image and animation. * * @param left * whether or not this tumbleweed enters from the left. */ public Tumbleweed(boolean left) throws Exception { super(Image.createImage("/images/tumbleweed.png"), WIDTH, WIDTH); myY = JumpManager.DISP_HEIGHT - WIDTH - 2; myLeft = left; if (!myLeft) { setTransform(TRANS_MIRROR); } myJumpedOver = false; setVisible(false); } //--------------------------------------------------------- // game actions /** * Set the tumbleweed in motion if it is not currently visible. */ synchronized boolean go() { boolean retVal = false; if (!isVisible()) { retVal = true; //System.out.println("Tumbleweed.go-->not visible"); myJumpedOver = false; setVisible(true); // set the tumbleweed"s position to the point // where it just barely appears on the screen // to that it can start approaching the cowboy: if (myLeft) { setRefPixelPosition(myCurrentRightBound, myY); move(-1, 0); } else { setRefPixelPosition(myCurrentLeftBound, myY); move(1, 0); } } else { //System.out.println("Tumbleweed.go-->visible"); } return (retVal); } //--------------------------------------------------------- // graphics /** * move the tumbleweed back to its initial (inactive) state. */ void reset() { setVisible(false); myJumpedOver = false; } /** * alter the tumbleweed image appropriately for this frame.. * * @param left * whether or not the player is moving left * @return how much the score should change by after this advance. */ synchronized int advance(Cowboy cowboy, int tickCount, boolean left, int currentLeftBound, int currentRightBound) { int retVal = 0; myCurrentLeftBound = currentLeftBound; myCurrentRightBound = currentRightBound; // if the tumbleweed goes outside of the display // region, set it to invisible since it is // no longer in use. if ((getRefPixelX() - WIDTH >= currentRightBound) && (!myLeft)) { setVisible(false); } if ((getRefPixelX() + WIDTH <= currentLeftBound) && myLeft) { setVisible(false); } if (isVisible()) { // when the tumbleweed is active, we advance the // rolling animation to the next frame and then // move the tumbleweed in the right direction across // the screen. if (tickCount % 2 == 0) { // slow the animation down a little nextFrame(); } if (myLeft) { move(-3, 0); // if the cowboy just passed the tumbleweed // (without colliding with it) we increase the // cowboy"s score and set myJumpedOver to true // so that no further points will be awarded // for this tumbleweed until it goes offscreen // and then is later reactivated: if ((!myJumpedOver) && (getRefPixelX() < cowboy.getRefPixelX())) { myJumpedOver = true; retVal = cowboy.increaseScoreThisJump(); } } else { move(3, 0); if ((!myJumpedOver) && (getRefPixelX() > cowboy.getRefPixelX() + Cowboy.WIDTH)) { myJumpedOver = true; retVal = cowboy.increaseScoreThisJump(); } } } return (retVal); }
} /**
* This is the class that plays a little tune while you play the game. This * version uses the Player and Control interfaces. * * @author Carol Hamer */
class ToneControlMusicMaker implements PlayerListener {
//--------------------------------------------------------- // fields /** * The player object that plays the tune. */ private Player myPlayer; /** * Whether or not the player wants to pause the music. */ private boolean myShouldPause; /** * Whether or not the system wants to pause the music. */ private boolean myGamePause; /** * The tune played by the game, stored as an array of bytes in BNF notation. */ private byte[] myTune = { // first set the version ToneControl.VERSION, 1, // set the tempo ToneControl.TEMPO, 30, // define the first line of the song ToneControl.BLOCK_START, 0, 69, 8, 69, 8, 69, 8, 71, 8, 73, 16, 71, 16, 69, 8, 73, 8, 71, 8, 71, 8, 69, 32, ToneControl.BLOCK_END, 0, // define the other line of the song ToneControl.BLOCK_START, 1, 71, 8, 71, 8, 71, 8, 71, 8, 66, 16, 66, 16, 71, 8, 69, 8, 68, 8, 66, 8, 64, 32, ToneControl.BLOCK_END, 1, // play the song ToneControl.PLAY_BLOCK, 0, ToneControl.PLAY_BLOCK, 0, ToneControl.PLAY_BLOCK, 1, ToneControl.PLAY_BLOCK, 0, }; //---------------------------------------------------------- // actions /** * call this when the game pauses. This method does not affect the field * myShouldPause because this method is called only when the system pauses * the music, not when the player pauses the music. */ void pauseGame() { try { myGamePause = true; myPlayer.stop(); // when the application pauses the game, resources // are supposed to be released, so we close the // player and throw it away. myPlayer.close(); myPlayer = null; } catch (Exception e) { // the music isn"t necessary, so we ignore exceptions. } } /** * call this when the game resumes. This method does not affect the field * myShouldPause because this method is called only when the system reusmes * the music, not when the player pauses the music. */ synchronized void resumeGame() { try { myGamePause = false; if (!myShouldPause) { // if the player is null, we create a new one. if (myPlayer == null) { start(); } // start the music. myPlayer.start(); } } catch (Exception e) { // the music isn"t necessary, so we ignore exceptions. } } /** * toggle the music. (pause it if it"s going, start it again if it"s * paused). */ synchronized void toggle() { try { myShouldPause = !myShouldPause; if (myShouldPause) { if (myPlayer != null) { myPlayer.stop(); } } else if (!myGamePause) { // if the player is null, we create a new one. if (myPlayer == null) { start(); } // start the music. myPlayer.start(); } } catch (Exception e) { // the music isn"t necessary, so we ignore exceptions. } } /** * stops the music. */ synchronized void requestStop() { try { myPlayer.stop(); // this is called when the game is over, to we close // up the player to release the resources. myPlayer.close(); } catch (Exception e) { // the music isn"t necessary, so we ignore exceptions. } } //---------------------------------------------------------- // initialization /** * start the music.. Here the method is "start" instead of "run" because it * is not necessary to create a thread for the Player. the Player runs on * its own thread. */ public void start() { ToneControl control = null; try { myPlayer = Manager.createPlayer(Manager.TONE_DEVICE_LOCATOR); // do the preliminary set-up: myPlayer.realize(); // set a listener to listen for the end of the tune: myPlayer.addPlayerListener(this); // get the ToneControl object in order to set the tune data: control = (ToneControl) myPlayer.getControl("ToneControl"); control.setSequence(myTune); // set the volume to the highest possible volume: VolumeControl vc = (VolumeControl) myPlayer .getControl("VolumeControl"); vc.setLevel(100); } catch (Exception e) { // the music isn"t necessary, so we ignore exceptions. } } //---------------------------------------------------------- // implementation of PlayerListener /** * If we reach the end of the song, play it again... */ public void playerUpdate(Player player, String event, Object eventData) { if (event.equals(PlayerListener.END_OF_MEDIA)) { if ((!myShouldPause) && (!myGamePause)) { try { myPlayer.start(); } catch (Exception e) { // the music isn"t necessary, so we ignore exceptions. } } } }
} /**
* This is the class that plays a little tune while you play the game. * * @author Carol Hamer */
class MusicMaker extends Thread {
//--------------------------------------------------------- // fields /** * Whether or not the main thread would like this thread to stop. */ public static final int NOTE_LENGTH = 250; /** * Whether or not the main thread would like this thread to pause. */ private boolean myShouldPause; /** * If the whole game is paused, we pause the music too.. */ private boolean myGamePause; /** * Whether or not the main thread would like this thread to stop. */ private static boolean myShouldStop; /** * The tune played by the game, stored as an array of notes and durations. * * NOTE: 69 is A. To get other notes, just add or subtract their difference * from A on the keyboard including the black keys in the calculation. See * the scales below for an idea. * */ private byte[][] myTune = { { 69, 1 }, { 69, 1 }, { 69, 1 }, { 71, 1 }, { 73, 2 }, { 71, 2 }, { 69, 1 }, { 73, 1 }, { 71, 1 }, { 71, 1 }, { 69, 4 }, { 69, 1 }, { 69, 1 }, { 69, 1 }, { 71, 1 }, { 73, 2 }, { 71, 2 }, { 69, 1 }, { 73, 1 }, { 71, 1 }, { 71, 1 }, { 69, 4 }, { 71, 1 }, { 71, 1 }, { 71, 1 }, { 71, 1 }, { 66, 2 }, { 66, 2 }, { 71, 1 }, { 69, 1 }, { 68, 1 }, { 66, 1 }, { 64, 4 }, { 69, 1 }, { 69, 1 }, { 69, 1 }, { 71, 1 }, { 73, 2 }, { 71, 2 }, { 69, 1 }, { 73, 1 }, { 71, 1 }, { 71, 1 }, { 69, 4 } }; /** * An example "tune" that is just a scale.. not used. */ private byte[][] myScale = { { 69, 1 }, { 71, 1 }, { 73, 1 }, { 74, 1 }, { 76, 1 }, { 78, 1 }, { 80, 1 }, { 81, 1 } }; /** * An example "tune" that is just a scale.. not used. */ private byte[][] myScale2 = { { 57, 1 }, { 59, 1 }, { 61, 1 }, { 62, 1 }, { 64, 1 }, { 66, 1 }, { 68, 1 }, { 69, 1 } }; //---------------------------------------------------------- // actions /** * call this when the game pauses. */ void pauseGame() { myGamePause = true; } /** * call this when the game resumes. */ synchronized void resumeGame() { myGamePause = false; this.notify(); } /** * toggle the music. (pause it if it"s going, start it again if it"s * paused). */ synchronized void toggle() { myShouldPause = !myShouldPause; this.notify(); } /** * stops the music. */ synchronized void requestStop() { myShouldStop = true; this.notify(); } /** * start the music.. */ public void run() { myShouldStop = false; myShouldPause = true; myGamePause = false; int counter = 0; while (true) { if (myShouldStop) { break; } synchronized (this) { while ((myShouldPause) || (myGamePause)) { try { wait(); } catch (Exception e) { } } } try { Manager.playTone(myTune[counter][0], myTune[counter][1] * NOTE_LENGTH, 50); } catch (Exception e) { // the music isn"t necessary, so we ignore exceptions. } synchronized (this) { try { wait(myTune[counter][1] * NOTE_LENGTH); } catch (Exception e) { } } counter++; if (counter >= myTune.length) { counter = 0; } } }
} /**
* This handles the graphics objects. * * @author Carol Hamer */
class JumpManager extends javax.microedition.lcdui.game.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 graphical region. This is the same as the * height of the visible part because in this game the layer manager"s * visible part scrolls only left and right but not up and down. */ static int DISP_HEIGHT; // game object fields // the player"s object. private Cowboy myCowboy; /** * the tumbleweeds that enter from the left. */ private Tumbleweed[] myLeftTumbleweeds; /** * the tumbleweeds that enter from the right. */ private Tumbleweed[] myRightTumbleweeds; /** * the object representing the grass in the background.. */ private Grass myGrass; /** * Whether or not the player is currently going left. */ private boolean myLeft; /** * The leftmost x-coordinate that should be visible on the screen in terms * of this objects internal coordinates. */ private int myCurrentLeftX; //----------------------------------------------------- // gets/sets /** * This tells the player to turn left or right. * * @param left * whether or not the turn is towards the left.. */ void setLeft(boolean left) { myLeft = left; } /** * @return a handle to the tumbleweed objects. */ Tumbleweed[] getTumbleweeds() { Tumbleweed[] retArray = new Tumbleweed[myLeftTumbleweeds.length + myRightTumbleweeds.length]; for (int i = 0; i < myLeftTumbleweeds.length; i++) { retArray[i] = myLeftTumbleweeds[i]; } for (int i = 0; i < myRightTumbleweeds.length; i++) { retArray[i + myLeftTumbleweeds.length] = myRightTumbleweeds[i]; } return (retArray); } //----------------------------------------------------- // initialization and game state changes /** * Constructor sets the data and constructs the graphical objects.. * * @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. */ public JumpManager(int x, int y, int width, int height) throws Exception { CANVAS_X = x; CANVAS_Y = y; DISP_WIDTH = width; DISP_HEIGHT = height; myCurrentLeftX = Grass.CYCLE * Grass.TILE_WIDTH; setViewWindow(0, 0, DISP_WIDTH, DISP_HEIGHT); // create the player: if (myCowboy == null) { myCowboy = new Cowboy(myCurrentLeftX + DISP_WIDTH / 2, DISP_HEIGHT - Cowboy.HEIGHT - 2); append(myCowboy); } // create the tumbleweeds to jump over: if (myLeftTumbleweeds == null) { myLeftTumbleweeds = new Tumbleweed[2]; for (int i = 0; i < myLeftTumbleweeds.length; i++) { myLeftTumbleweeds[i] = new Tumbleweed(true); append(myLeftTumbleweeds[i]); } } if (myRightTumbleweeds == null) { myRightTumbleweeds = new Tumbleweed[2]; for (int i = 0; i < myRightTumbleweeds.length; i++) { myRightTumbleweeds[i] = new Tumbleweed(false); append(myRightTumbleweeds[i]); } } // create the background object: if (myGrass == null) { myGrass = new Grass(); append(myGrass); } } /** * sets all variables back to their initial positions. */ void reset() { if (myGrass != null) { myGrass.reset(); } if (myCowboy != null) { myCowboy.reset(); } if (myLeftTumbleweeds != null) { for (int i = 0; i < myLeftTumbleweeds.length; i++) { myLeftTumbleweeds[i].reset(); } } if (myRightTumbleweeds != null) { for (int i = 0; i < myRightTumbleweeds.length; i++) { myRightTumbleweeds[i].reset(); } } myLeft = false; myCurrentLeftX = Grass.CYCLE * Grass.TILE_WIDTH; } //------------------------------------------------------- // graphics methods /** * paint the game graphic on the screen. */ public void paint(Graphics g) { setViewWindow(myCurrentLeftX, 0, DISP_WIDTH, DISP_HEIGHT); paint(g, CANVAS_X, CANVAS_Y); } /** * If the cowboy gets to the end of the graphical region, move all of the * pieces so that the screen appears to wrap. */ private void wrap() { if (myCurrentLeftX % (Grass.TILE_WIDTH * Grass.CYCLE) == 0) { if (myLeft) { myCowboy.move(Grass.TILE_WIDTH * Grass.CYCLE, 0); myCurrentLeftX += (Grass.TILE_WIDTH * Grass.CYCLE); for (int i = 0; i < myLeftTumbleweeds.length; i++) { myLeftTumbleweeds[i] .move(Grass.TILE_WIDTH * Grass.CYCLE, 0); } for (int i = 0; i < myRightTumbleweeds.length; i++) { myRightTumbleweeds[i].move(Grass.TILE_WIDTH * Grass.CYCLE, 0); } } else { myCowboy.move(-(Grass.TILE_WIDTH * Grass.CYCLE), 0); myCurrentLeftX -= (Grass.TILE_WIDTH * Grass.CYCLE); for (int i = 0; i < myLeftTumbleweeds.length; i++) { myLeftTumbleweeds[i].move(-Grass.TILE_WIDTH * Grass.CYCLE, 0); } for (int i = 0; i < myRightTumbleweeds.length; i++) { myRightTumbleweeds[i].move(-Grass.TILE_WIDTH * Grass.CYCLE, 0); } } } } //------------------------------------------------------- // game movements /** * Tell all of the moving components to advance. * * @param gameTicks * the remainaing number of times that the main loop of the game * will be executed before the game ends. * @return the change in the score after the pieces have advanced. */ int advance(int gameTicks) { int retVal = 0; // first we move the view window // (so we are showing a slightly different view of // the manager"s graphical area.) if (myLeft) { myCurrentLeftX--; } else { myCurrentLeftX++; } // now we tell the game objects to move accordingly. myGrass.advance(gameTicks); myCowboy.advance(gameTicks, myLeft); for (int i = 0; i < myLeftTumbleweeds.length; i++) { retVal += myLeftTumbleweeds[i].advance(myCowboy, gameTicks, myLeft, myCurrentLeftX, myCurrentLeftX + DISP_WIDTH); retVal -= myCowboy.checkCollision(myLeftTumbleweeds[i]); } for (int i = 0; i < myLeftTumbleweeds.length; i++) { retVal += myRightTumbleweeds[i].advance(myCowboy, gameTicks, myLeft, myCurrentLeftX, myCurrentLeftX + DISP_WIDTH); retVal -= myCowboy.checkCollision(myRightTumbleweeds[i]); } // now we check if we have reached an edge of the viewable // area, and if so we move the view area and all of the // game objects so that the game appears to wrap. wrap(); return (retVal); } /** * Tell the cowboy to jump.. */ void jump() { myCowboy.jump(); }
}
</source>