一个J2ME地牢游戏的源码
作者:未知 例子来源:本站原创 点击数: 更新时间:2006-5-30
import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.*;
import javax.microedition.midlet.*;
import javax.microedition.rms.*;
/**
* This is the main class of the dungeon game.
*
* @author Carol Hamer
*/
public class Dungeon extends MIDlet implements CommandListener {
//-----------------------------------------------------
// game object fields
/**
* The canvas that the dungeon is drawn on.
*/
private DungeonCanvas myCanvas;
/**
* the thread that advances the game clock.
*/
private GameThread myGameThread;
//-----------------------------------------------------
// command fields
/**
* The button to exit the game.
*/
private Command myExitCommand = new Command("Exit", Command.EXIT, 99);
/**
* The command to save the game in progress.
*/
private Command mySaveCommand = new Command("Save Game", Command.SCREEN, 2);
/**
* The command to restore a previously saved game.
*/
private Command myRestoreCommand = new Command("Restore Game",
Command.SCREEN, 2);
/**
* the command to start moving when the game is paused.
*/
private Command myGoCommand = new Command("Go", Command.SCREEN, 1);
/**
* the command to pause the game.
*/
private Command myPauseCommand = new Command("Pause", Command.SCREEN, 1);
/**
* the command to start a new game.
*/
private Command myNewCommand = new Command("Next Board", Command.SCREEN, 1);
//-----------------------------------------------------
// initialization and game state changes
/**
* Initialize the canvas and the commands.
*/
public Dungeon() {
try {
// create the canvas and set up the commands:
myCanvas = new DungeonCanvas(this);
myCanvas.addCommand(myExitCommand);
myCanvas.addCommand(mySaveCommand);
myCanvas.addCommand(myRestoreCommand);
myCanvas.addCommand(myPauseCommand);
myCanvas.setCommandListener(this);
} catch (Exception e) {
// if there's an error during creation, display it as an alert.
errorMsg(e);
}
}
/**
* Switch the command to the play again command. (removing other commands
* that are no longer relevant)
*/
void setNewCommand() {
myCanvas.removeCommand(myPauseCommand);
myCanvas.removeCommand(myGoCommand);
myCanvas.addCommand(myNewCommand);
}
/**
* Switch the command to the go command. (removing other commands that are
* no longer relevant)
*/
void setGoCommand() {
myCanvas.removeCommand(myPauseCommand);
myCanvas.removeCommand(myNewCommand);
myCanvas.addCommand(myGoCommand);
}
/**
* Switch the command to the pause command. (removing other commands that
* are no longer relevant)
*/
void setPauseCommand() {
myCanvas.removeCommand(myNewCommand);
myCanvas.removeCommand(myGoCommand);
myCanvas.addCommand(myPauseCommand);
}
//----------------------------------------------------------------
// implementation of MIDlet
// these methods may be called by the application management
// software at any time, so we always check fields for null
// before calling methods on them.
/**
* Start the application.
*/
public void startApp() throws MIDletStateChangeException {
if (myCanvas != null) {
if (myGameThread == null) {
// create the thread and start the game:
myGameThread = new GameThread(myCanvas);
myCanvas.start();
myGameThread.start();
} else {
// in case this gets called again after
// the application has been started once:
myCanvas.removeCommand(myGoCommand);
myCanvas.addCommand(myPauseCommand);
myCanvas.flushKeys();
myGameThread.resumeGame();
}
}
}
/**
* Stop the threads and throw out the garbage.
*/
public void destroyApp(boolean unconditional)
throws MIDletStateChangeException {
myCanvas = null;
if (myGameThread != null) {
myGameThread.requestStop();
}
myGameThread = null;
System.gc();
}
/**
* Pause the game.
*/
public void pauseApp() {
if (myCanvas != null) {
setGoCommand();
}
if (myGameThread != null) {
myGameThread.pause();
}
}
//----------------------------------------------------------------
// implementation of CommandListener
/*
* Respond to a command issued on the Canvas. (reset, exit, or change size
* prefs).
*/
public void commandAction(Command c, Displayable s) {
try {
if (c == myGoCommand) {
myCanvas.setNeedsRepaint();
myCanvas.removeCommand(myGoCommand);
myCanvas.addCommand(myPauseCommand);
myCanvas.flushKeys();
myGameThread.resumeGame();
} else if (c == myPauseCommand) {
myCanvas.setNeedsRepaint();
myCanvas.removeCommand(myPauseCommand);
myCanvas.addCommand(myGoCommand);
myGameThread.pause();
} else if (c == myNewCommand) {
myCanvas.setNeedsRepaint();
// go to the next board and restart the game
myCanvas.removeCommand(myNewCommand);
myCanvas.addCommand(myPauseCommand);
myCanvas.reset();
myGameThread.resumeGame();
/*} else if (c == Alert.DISMISS_COMMAND) {
// if there was a serious enough error to
// cause an alert, then we end the game
// when the user is done reading the alert:
// (Alert.DISMISS_COMMAND is the default
// command that is placed on an Alert
// whose timeout is FOREVER)
destroyApp(false);
notifyDestroyed();*/
} else if (c == mySaveCommand) {
myCanvas.setNeedsRepaint();
myCanvas.saveGame();
} else if (c == myRestoreCommand) {
myCanvas.setNeedsRepaint();
myCanvas.removeCommand(myNewCommand);
myCanvas.removeCommand(myGoCommand);
myCanvas.addCommand(myPauseCommand);
myCanvas.revertToSaved();
} else if (c == myExitCommand) {
destroyApp(false);
notifyDestroyed();
}
} catch (Exception e) {
errorMsg(e);
}
}
//-------------------------------------------------------
// error methods
/**
* Converts an exception to a message and displays the message..
*/
void errorMsg(Exception e) {
if (e.getMessage() == null) {
errorMsg(e.getClass().getName());
} else {
errorMsg(e.getClass().getName() + ":" + e.getMessage());
}
}
/**
* Displays an error message alert if something goes wrong.
*/
void errorMsg(String msg) {
Alert errorAlert = new Alert("error", msg, null, AlertType.ERROR);
errorAlert.setCommandListener(this);
errorAlert.setTimeout(Alert.FOREVER);
Display.getDisplay(this).setCurrent(errorAlert);
}
}
/**
* This class represents doors and keys.
*
* @author Carol Hamer
*/
class DoorKey extends Sprite {
//---------------------------------------------------------
// fields
/**
* The image file shared by all doors and keys.
*/
public static Image myImage;
/**
* A code int that indicates the door or key's color.
*/
private int myColor;
//---------------------------------------------------------
// get/set data
/**
* @return the door or key's color.
*/
public int getColor() {
return (myColor);
}
//---------------------------------------------------------
// constructor and initializer
static {
try {
myImage = Image.createImage("/images/keys.png");
} catch (Exception e) {
throw (new RuntimeException(
"DoorKey.<init>-->failed to load image, caught "
+ e.getClass() + ": " + e.getMessage()));
}
}
/**
* Standard constructor sets the image to the correct frame (according to
* whether this is a door or a key and what color it should be) and then
* puts it in the correct location.
*/
public DoorKey(int color, boolean isKey, int[] gridCoordinates) {
super(myImage, DungeonManager.SQUARE_WIDTH, DungeonManager.SQUARE_WIDTH);
myColor = color;
int imageIndex = color * 2;
if (isKey) {
imageIndex++;
}
setFrame(imageIndex);
setPosition(gridCoordinates[0] * DungeonManager.SQUARE_WIDTH,
gridCoordinates[1] * DungeonManager.SQUARE_WIDTH);
}
}
/**
* This class is a set of simple utility functions that can be used to convert
* standard data types to bytes and back again. It is used especially for data
* storage, but also for sending and receiving data.
*
* @author Carol Hamer
*/
class DataConverter {
//--------------------------------------------------------
// utilities to encode small, compactly-stored small ints.
/**
* Encodes a coordinate pair into a byte.
*
* @param coordPair
* a pair of integers to be compacted into a single byte for
* storage. WARNING: each of the two values MUST BE between 0 and
* 15 (inclusive). This method does not verify the length of the
* array (which must be 2!) nor does it verify that the ints are
* of the right size.
*/
public static byte encodeCoords(int[] coordPair) {
// get the byte value of the first coordinate:
byte retVal = (new Integer(coordPair[0])).byteValue();
// move the first coordinate's value up to the top
// half of the storage byte:
retVal = (new Integer(retVal << 4)).byteValue();
// store the second coordinate in the lower half
// of the byte:
retVal += (new Integer(coordPair[1])).byteValue();
return (retVal);
}
/**
* Encodes eight ints into a byte. This could be easily modified to encode
* eight booleans.
*
* @param eight
* an array of at least eight ints. WARNING: all values must be 0
* or 1! This method does not verify that the values are in the
* correct range nor does it verify that the array is long
* enough.
* @param offset
* the index in the array eight to start reading data from.
* (should usually be 0)
*/
public static byte encode8(int[] eight, int offset) {
// get the byte value of the first int:
byte retVal = (new Integer(eight[offset])).byteValue();
// progressively move the data up one bit in the
// storage byte and then record the next int in
// the lowest spot in the storage byte:
for (int i = offset + 1; i < 8 + offset; i++) {
retVal = (new Integer(retVal << 1)).byteValue();
retVal += (new Integer(eight[i])).byteValue();
}
return (retVal);
}
//--------------------------------------------------------
// utilities to decode small, compactly-stored small ints.
/**
* Turns a byte into a pair of coordinates.
*/
public static int[] decodeCoords(byte coordByte) {
int[] retArray = new int[2];
// we perform a bitwise and with the value 15
// in order to just get the bits of the lower
// half of the byte:
retArray[1] = coordByte & 15;
// To get the bits of the upper half of the
// byte, we perform a shift to move them down:
retArray[0] = coordByte >> 4;
// bytes in Java are generally assumed to be
// signed, but in this coding algorithm we
// would like to treat them as unsigned:
if (retArray[0] < 0) {
retArray[0] += 16;
}
return (retArray);
}
/**
* Turns a byte into eight ints.
*/
public static int[] decode8(byte data) {
int[] retArray = new int[8];
// The flag allows us to look at each bit individually
// to determine if it is 1 or 0. The number 128
// corresponds to the highest bit of a byte, so we
// start with that one.
int flag = 128;
// We use a loop that checks
// the data bit by bit by performing a bitwise
// and (&) between the data byte and a flag:
for (int i = 0; i < 8; i++) {
if ((flag & data) != 0) {
retArray[i] = 1;
} else {
retArray[i] = 0;
}
// move the flag down one bit so that we can
// check the next bit of data on the next pass
// through the loop:
flag = flag >> 1;
}
return (retArray);
}
//--------------------------------------------------------
// standard integer interpretation
/**
* Uses an input stream to convert an array of bytes to an int.
*/
public static int parseInt(byte[] data) throws IOException {
DataInputStream stream = new DataInputStream(new ByteArrayInputStream(
data));
int retVal = stream.readInt();
stream.close();
return (retVal);
}
/**
* Uses an output stream to convert an int to four bytes.
*/
public static byte[] intToFourBytes(int i) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream(4);
DataOutputStream dos = new DataOutputStream(baos);
dos.writeInt(i);
baos.close();
dos.close();
byte[] retArray = baos.toByteArray();
return (retArray);
}
//--------------------------------------------------------
// integer interpretation illustrated
/**
* Java appears to treat a byte as being signed when returning it as an
* int--this function converts from the signed value to the corresponding
* unsigned value. This method is used by nostreamParseInt.
*/
public static int unsign(int signed) {
int retVal = signed;
if (retVal < 0) {
retVal += 256;
}
return (retVal);
}
/**
* Takes an array of bytes and returns an int. This version will return the
* same value as the method parseInt above. This version is included in
* order to illustrate how Java encodes int values in terms of bytes.
*
* @param data
* an array of 1, 2, or 4 bytes.
*/
public static int nostreamParseInt(byte[] data) {
// byte 0 is the high byte which is assumed
// to be signed. As we add the lower bytes
// one by one, we unsign them because because
// a single byte alone is interpreted as signed,
// but in an int only the top byte should be signed.
// (note that the high byte is the first one in the array)
int retVal = data[0];
for (int i = 1; i < data.length; i++) {
retVal = retVal << 8;
retVal += unsign(data[i]);
}
return (retVal);
}
/**
* Takes an arbitrary int and returns an array of four bytes. This version
* will return the same byte array as the method intToFourBytes above. This
* version is included in order to illustrate how Java encodes int values in
* terms of bytes.
*/
public static byte[] nostreamIntToFourBytes(int i) {
byte[] fourBytes = new byte[4];
// when you take the byte value of an int, it
// only gives you the lowest byte. So we
// get all four bytes by taking the lowest
// byte four times and moving the whole int
// down by one byte between each one.
// (note that the high byte is the first one in the array)
fourBytes[3] = (new Integer(i)).byteValue();
i = i >> 8;
fourBytes[2] = (new Integer(i)).byteValue();
i = i >> 8;
fourBytes[1] = (new Integer(i)).byteValue();
i = i >> 8;
fourBytes[0] = (new Integer(i)).byteValue();
return (fourBytes);
}
/**
* Takes an int between -32768 and 32767 and returns an array of two bytes.
* This does not verify that the argument is of the right size. If the
* absolute value of i is too high, it will not be encoded correctly.
*/
public static byte[] nostreamIntToTwoBytes(int i) {
byte[] twoBytes = new byte[2];
// when you take the byte value of an int, it
// only gives you the lowest byte. So we
// get the lower two bytes by taking the lowest
// byte twice and moving the whole int
// down by one byte between each one.
twoBytes[1] = (new Integer(i)).byteValue();
i = i >> 8;
twoBytes[0] = (new Integer(i)).byteValue();
return (twoBytes);
}
}
/**
* This class contains the data for the map of the dungeon..
*
* @author Carol Hamer
*/
class BoardDecoder {
//--------------------------------------------------------
// fields
/**
* The coordinates of where the player starts on the map in terms of the
* array indices.
*/
private int[] myPlayerSquare;
/**
* The coordinates of the goal (crown).
*/
private int[] myGoalSquare;
/**
* The coordinates of the doors. the there should be two in a row of each
* color, following the same sequence as the keys.
*/
private int[][] myDoors;
/**
* The coordinates of the Keys. the there should be of each color, following
* the same sequence as the doors.
*/
private int[][] myKeys;
/**
* The coordinates of the stone walls of the maze, encoded bit by bit.
*/
private TiledLayer myLayer;
/**
* The data in bytes that gives the various boards. This was created using
* EncodingUtils... This is a two-dimensional array: Each of the four main
* sections corresponds to one of the four possible boards.
*/
private static byte[][] myData = {
{ 0, 0, -108, -100, -24, 65, 21, 58, 53, -54, -116, -58, -56, -84,
115, -118, -1, -1, -128, 1, -103, -15, -128, 25, -97, -127,
-128, 79, -14, 1, -126, 121, -122, 1, -113, -49, -116, 1,
-100, -3, -124, 5, -25, -27, -128, 1, -1, -1 },
{ 0, 1, 122, 90, -62, 34, -43, 72, -59, -29, 56, -55, 98, 126, -79,
61, -1, -1, -125, 1, -128, 17, -26, 29, -31, 57, -72, 1,
-128, -51, -100, 65, -124, 57, -2, 1, -126, 13, -113, 1,
-97, 25, -127, -99, -8, 1, -1, -1 },
{ 0, 2, 108, -24, 18, -26, 102, 30, -58, 46, -28, -88, 34, -98, 97,
-41, -1, -1, -96, 1, -126, 57, -9, 97, -127, 69, -119, 73,
-127, 1, -109, 59, -126, 1, -26, 103, -127, 65, -103, 115,
-127, 65, -25, 73, -128, 1, -1, -1 },
{ 0, 3, -114, 18, -34, 27, -39, -60, -76, -50, 118, 90, 82, -88,
34, -74, -1, -1, -66, 1, -128, 121, -26, 125, -128, -123,
-103, 29, -112, 1, -109, 49, -112, 1, -116, -31, -128, 5,
-122, 5, -32, 13, -127, -51, -125, 1, -1, -1 }, };
//--------------------------------------------------------
// initialization
/**
* Constructor fills data fields by interpreting the data bytes.
*/
public BoardDecoder(int boardNum) throws Exception {
// we start by selecting the two dimensional
// array corresponding to the desired board:
byte[] data = myData[boardNum];
// The first two bytes give the version number and
// the board number, but we ignore them because
// they are assumed to be correct.
// The third byte of the first array is the first one
// we read: it gives the player's starting coordinates:
myPlayerSquare = DataConverter.decodeCoords(data[2]);
// the next byte gives the coordinates of the crown:
myGoalSquare = DataConverter.decodeCoords(data[3]);
// the next four bytes give the coordinates of the keys:
myKeys = new int[4][];
for (int i = 0; i < myKeys.length; i++) {
myKeys[i] = DataConverter.decodeCoords(data[i + 4]);
}
// the next eight bytes give the coordinates of the doors:
myDoors = new int[8][];
for (int i = 0; i < myDoors.length; i++) {
myDoors[i] = DataConverter.decodeCoords(data[i + 8]);
}
// now we create the TiledLayer object that is the
// background dungeon map:
myLayer = new TiledLayer(16, 16,
Image.createImage("/images/stone.png"),
DungeonManager.SQUARE_WIDTH, DungeonManager.SQUARE_WIDTH);
// now we call an internal utility that reads the array
// of data that gives the positions of the blocks in the
// walls of this dungeon:
decodeDungeon(data, myLayer, 16);
}
//--------------------------------------------------------
// get/set data
/**
* @return the number of boards currently stored in this class.
*/
public static int getNumBoards() {
return (myData.length);
}
/**
* get the coordinates of where the player starts on the map in terms of the
* array indices.
*/
public int[] getPlayerSquare() {
return (myPlayerSquare);
}
/**
* get the coordinates of the goal crown in terms of the array indices.
*/
public int[] getGoalSquare() {
return (myGoalSquare);
}
/**
* get the tiled layer that gives the map of the dungeon.
*/
public TiledLayer getLayer() {
return (myLayer);
}
/**
* Creates the array of door sprites. (call this only once to avoid creating
* redundant sprites).
*/
DoorKey[] createDoors() {
DoorKey[] retArray = new DoorKey[8];
for (int i = 0; i < 4; i++) {
retArray[2 * i] = new DoorKey(i, false, myDoors[2 * i]);
retArray[2 * i + 1] = new DoorKey(i, false, myDoors[2 * i + 1]);
}
return (retArray);
}
/**
* Creates the array of key sprites. (call this only once to avoid creating
* redundant sprites.)
*/
DoorKey[] createKeys() {
DoorKey[] retArray = new DoorKey[4];
for (int i = 0; i < 4; i++) {
retArray[i] = new DoorKey(i, true, myKeys[i]);
}
return (retArray);
}
//--------------------------------------------------------
// decoding utilities
/**
* Takes a dungeon given as a byte array and uses it to set the tiles of a
* tiled layer.
*
* The TiledLayer in this case is a 16 x 16 grid in which each square can be
* either blank (value of 0) or can be filled with a stone block (value of
* 1). Therefore each square requires only one bit of information. Each byte
* of data in the array called "data" records the frame indices of eight
* squares in the grid.
*/
private static void decodeDungeon(byte[] data, TiledLayer dungeon,
int offset) throws Exception {
if (data.length + offset < 32) {
throw (new Exception(
"BoardDecoder.decodeDungeon-->not enough data!!!"

您现在的位置: