Tumbleweed game
/* 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(); } }
1. | Maze game | ||
2. | Checkers game | ||
3. | Game Action Example | ||
4. | Game Key Event | ||
5. | Sweep Game | ||
6. | Sweep | ||
7. | Dungeon game | ||
8. | Canvas for processing game actions |