|
/*
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[
|