/*
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();
}
}
|