Checkers游戏源码
作者:未知 例子来源:本站原创 点击数: 更新时间:2006-5-30
import java.io.*;
import javax.microedition.io.*;
import javax.microedition.rms.*;
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
/**
* This is the main class of the checkers game.
*
* @author Carol Hamer
*/
public class Checkers extends MIDlet implements CommandListener {
//-----------------------------------------------------
// game object fields
/**
* The canvas that the checkerboard is drawn on.
*/
private CheckersCanvas myCanvas;
/**
* The class that makes the http connection.
*/
private Communicator myCommunicator;
//-----------------------------------------------------
// command fields
/**
* The button to exit the game.
*/
private Command myExitCommand = new Command("Exit", Command.EXIT, 99);
//-----------------------------------------------------
// initialization and game state changes
/**
* Initialize the canvas and the commands.
*/
public Checkers() {
try {
//create the canvas and set up the commands:
myCanvas = new CheckersCanvas(Display.getDisplay(this));
myCanvas.addCommand(myExitCommand);
myCanvas.setCommandListener(this);
CheckersGame game = myCanvas.getGame();
myCommunicator = new Communicator(this, myCanvas, game);
game.setCommunicator(myCommunicator);
} catch(Exception e) {
// if there's an error during creation, display it as an alert.
errorMsg(e);
}
}
//----------------------------------------------------------------
// implementation of MIDlet
// these methods may be called by the application management
// software at any time, so we always check fields for null
// before calling methods on them.
/**
* Start the application.
*/
public void startApp() throws MIDletStateChangeException {
// tell the canvas to set up the game data and paint the
// checkerboard.
if(myCanvas != null) {
myCanvas.start();
}
// tell the communicator to start its thread and make a
// connection.
if(myCommunicator != null) {
myCommunicator.start();
}
}
/**
* Throw out the garbage.
*/
public void destroyApp(boolean unconditional)
throws MIDletStateChangeException {
// tell the communicator to send the end game
// message to the other player and then disconnect:
if(myCommunicator != null) {
myCommunicator.endGame();
}
// throw the larger game objects in the garbage:
myCommunicator = null;
myCanvas = null;
System.gc();
}
/**
* Pause the game.
* This method merely ends the game because this
* version of the Checkers game does not support
* re-entering a game that is in play. A possible
* improvement to the game would be to allow
* a player to diconeect and leave a game and then
* later return to it, using some sort of session
* token to find the correct game in progress on
* the server side.
*/
public void pauseApp() {
try {
destroyApp(false);
notifyDestroyed();
} catch (MIDletStateChangeException ex) {
}
}
//----------------------------------------------------------------
// implementation of CommandListener
/*
* Respond to a command issued on the Canvas.
*/
public void commandAction(Command c, Displayable s) {
if(c == myExitCommand) {
try {
destroyApp(false);
notifyDestroyed();
} catch (MIDletStateChangeException ex) {
}
}
}
//-------------------------------------------------------
// error methods
/**
* Converts an exception to a message and displays
* the message..
*/
void errorMsg(Exception e) {
e.printStackTrace();
if(e.getMessage() == null) {
errorMsg(e.getClass().getName());
} else {
errorMsg(e.getMessage());
}
}
/**
* Displays an error message alert if something goes wrong.
*/
void errorMsg(String msg) {
Alert errorAlert = new Alert("error",
msg, null, AlertType.ERROR);
errorAlert.setCommandListener(this);
errorAlert.setTimeout(Alert.FOREVER);
Display.getDisplay(this).setCurrent(errorAlert);
}
}
/**
* This class is the display of the game.
*
* @author Carol Hamer
*/
class CheckersCanvas extends Canvas {
//---------------------------------------------------------
// static fields
/**
* color constant
*/
public static final int BLACK = 0;
/**
* color constant
*/
public static final int WHITE = 0xffffff;
/**
* color constant.
* (not quite bright red)
*/
public static final int RED = 0xf96868;
/**
* color constant
*/
public static final int GREY = 0xc6c6c6;
/**
* color constant
*/
public static final int LT_GREY = 0xe5e3e3;
/**
* how many rows and columns the display is divided into.
*/
public static final int GRID_WIDTH = 8;
//---------------------------------------------------------
// instance fields
/**
* The black crown to draw on the red pieces..
*/
private Image myBlackCrown;
/**
* The red crown to draw on the black pieces..
*/
private Image myWhiteCrown;
/**
* a handle to the display.
*/
private Display myDisplay;
/**
* a handle to the object that stores the game logic
* and game data.
*/
private CheckersGame myGame;
/**
* checkers dimension: the width of the squares of the checkerboard.
*/
private int mySquareSize;
/**
* checkers dimension: the minimum width possible for the
* checkerboard squares.
*/
private int myMinSquareSize = 15;
/**
* whether or not we're waiting for another player to join
* the game.
*/
private boolean myIsWaiting;
//-----------------------------------------------------
// gets / sets
/**
* @return a handle to the class that holds the logic of the
* checkers game.
*/
CheckersGame getGame() {
return(myGame);
}
/**
* Display a screen to inform the player that we're
* waiting for another player.
*/
void setWaitScreen(boolean wait) {
myIsWaiting = wait;
}
//-----------------------------------------------------
// initialization and game state changes
/**
* Constructor performs size calculations.
* @throws Exception if the display size is too
* small to make a checkers.
*/
CheckersCanvas(Display d) throws Exception {
myDisplay = d;
myGame = new CheckersGame();
// a few calculations to make the right checkerboard
// for the current display.
int width = getWidth();
int height = getHeight();
// get the smaller dimension fo the two possible
// screen dimensions in order to determine how
// big to make the checkerboard.
int screenSquareWidth = height;
if(width < height) {
screenSquareWidth = width;
}
mySquareSize = screenSquareWidth / GRID_WIDTH;
// if the display is too small to make a reasonable checkerboard,
// then we throw an Exception
if(mySquareSize < myMinSquareSize) {
throw(new Exception("Display too small"));
}
// initialize the crown images:
myBlackCrown = Image.createImage("/blackCrown.png");
myWhiteCrown = Image.createImage("/whiteCrown.png");
}
/**
* This is called as soon as the application begins.
*/
void start() {
myDisplay.setCurrent(this);
// prepare the game data for the first move:
myGame.start();
}
//-------------------------------------------------------
// graphics methods
/**
* Repaint the checkerboard..
*/
protected void paint(Graphics g) {
int width = getWidth();
int height = getHeight();
g.setColor(WHITE);
// clear the board (including the region around
// the board, which can get menu stuff and other
// garbage painted onto it...)
g.fillRect(0, 0, width, height);
// If we need to wait for another player to join the
// game before we can start, this displays the appropriate
// message:
if(myIsWaiting) {
// perform some calculations to place the text correctly:
Font font = g.getFont();
int fontHeight = font.getHeight();
int fontWidth = font.stringWidth("waiting for another player");
g.setColor(WHITE);
g.fillRect((width - fontWidth)/2, (height - fontHeight)/2,
fontWidth + 2, fontHeight);
// write in black
g.setColor(BLACK);
g.setFont(font);
g.drawString("waiting for another player", (width - fontWidth)/2,
(height - fontHeight)/2,
g.TOP|g.LEFT);
return;
}
// now draw the checkerboard:
// first the dark squares:
byte offset = 0;
for(byte i = 0; i < 4; i++) {
for(byte j = 0; j < 8; j++) {
// the offset is used to handle the fact that in every
// other row the dark squares are shifted one place
// to the right.
if(j % 2 != 0) {
offset = 1;
} else {
offset = 0;
}
// now if this is a selected square, we draw it lighter:
if(myGame.isSelected(i, j)) {
g.setColor(LT_GREY);
g.fillRect((2*i + offset)*mySquareSize, j*mySquareSize,
mySquareSize, mySquareSize);
} else {
// if it's not selected, we draw it dark grey:
g.setColor(GREY);
g.fillRect((2*i + offset)*mySquareSize, j*mySquareSize,
mySquareSize, mySquareSize);
}
// now put the pieces in their places:
g.setColor(RED);
int piece = myGame.getPiece(i, j);
int circleOffset = 2;
int circleSize = mySquareSize - 2*circleOffset;
if(piece < 0) {
// color the piece in black
g.setColor(BLACK);
g.fillRoundRect((2*i + offset)*mySquareSize + circleOffset,
j*mySquareSize + circleOffset,
circleSize, circleSize, circleSize, circleSize);
// if the player is a king, draw a crown on:
if(piece < -1) {
g.drawImage(myWhiteCrown,
(2*i + offset)*mySquareSize + mySquareSize/2,
j*mySquareSize + 1 + mySquareSize/2,
Graphics.VCENTER|Graphics.HCENTER);
}
} else if(piece > 0) {
// color the piece in red
g.fillRoundRect((2*i + offset)*mySquareSize + circleOffset,
j*mySquareSize + circleOffset,
circleSize, circleSize, circleSize, circleSize);
// if the player is a king, draw a crown on:
if(piece > 1) {
g.drawImage(myBlackCrown,
(2*i + offset)*mySquareSize + mySquareSize/2,
j*mySquareSize + 1 + mySquareSize/2,
Graphics.VCENTER|Graphics.HCENTER);
}
}
}
}
// now the blank squares:
// actually, this part is probably not necessary...
g.setColor(WHITE);
for(int i = 0; i < 4; i++) {
for(int j = 0; j < 8; j++) {
if(j % 2 == 0) {
offset = 1;
} else {
offset = 0;
}
g.fillRect((2*i + offset)*mySquareSize, j*mySquareSize,
mySquareSize, mySquareSize);
}
}
// if the player has reached the end of the game,
// we display the end message.
if(myGame.getGameOver()) {
// perform some calculations to place the text correctly:
Font font = g.getFont();
int fontHeight = font.getHeight();
int fontWidth = font.stringWidth("Game Over");
g.setColor(WHITE);
g.fillRect((width - fontWidth)/2, (height - fontHeight)/2,
fontWidth + 2, fontHeight);
// write in black
g.setColor(BLACK);
g.setFont(font);
g.drawString("Game Over", (width - fontWidth)/2,
(height - fontHeight)/2,
g.TOP|g.LEFT);
}
}
//-------------------------------------------------------
// handle keystrokes
/**
* Move the player.
*/
public void keyPressed(int keyCode) {
if(myGame.isMyTurn()) {
int action = getGameAction(keyCode);
switch (action) {
case LEFT:
myGame.leftPressed();
break;
case RIGHT:
myGame.rightPressed();
break;
case UP:
myGame.upPressed();
break;
case DOWN:
myGame.deselect();
break;
}
repaint();
serviceRepaints();
}
}
}
/**
* This class contacts a remote server in order to
* play a game of checkers against an opponent..
*
* @author Carol Hamer
*/
class Communicator extends Thread {
//--------------------------------------------------------
// static fields
/**
* This is the URL to contact.
* IMPORTANT: before compiling, the following URL
* must be changed to the correct URL of the
* machine running the server code.
*/
public static final String SERVER_URL
= "socket://malbec:8007";
/**
* The int to signal that the game is to begin.
*/
public static final byte START_GAME_FLAG = -4;
/**
* The byte to signal that the game is to end.
*/
public static final byte END_GAME_FLAG = -3;
/**
* The byte to signal the end of a turn.
*/
public static final byte END_TURN_FLAG = -2;
//--------------------------------------------------------
// game instance fields
/**
* The MIDlet subclass, used to set the Display
* in the case where an error message needs to be sent..
*/
private Checkers myCheckers;
/**
* The Canvas subclass, used to set the Display
* in the case where an error message needs to be sent..
*/
private CheckersCanvas myCanvas;
/**
* The game logic class that we send the opponent's
* moves to..
*/
private CheckersGame myGame;
/**
* Whether or not the MIDlet class has requested the
* game to end.
*/
private boolean myShouldStop;
//--------------------------------------------------------
// data exchange instance fields
/**
* The data from the local player that is to
* be sent to the opponent.
*/
private byte[] myMove;
/**
* Whether or not the current turn is done and
* should be sent.
*/
private boolean myTurnIsDone = true;
//--------------------------------------------------------
// initialization
/**
* Constructor is used only when the program wants
* to spawn a data-fetching thread, not for merely
* reading local data with static methods.
*/
Communicator(Checkers checkers, CheckersCanvas canvas,
CheckersGame game) {
myCheckers = checkers;
myCanvas = canvas;
myGame = game;
}
//--------------------------------------------------------
// methods called by CheckersGame to send move
// information to the opponent.
/**
* Stop the game entirely. Notify the servlet that
* the user is exiting the game.
*/
synchronized void endGame() {
myShouldStop = true;
if(myGame != null) {
myGame.setGameOver();
}
notify();
}
/**
* This is called when the player moves a piece.
*/
synchronized void move(byte sourceX, byte sourceY, byte destinationX,
byte destinationY) {
myMove = new byte[4]; myMove[0] = sourceX;
myMove[1] = sourceY;
myMove[2] = destinationX;
myMove[3] = destinationY;
myTurnIsDone = false;
notify();
}
/**
* This is called when the local player's turn is over.
*/
synchronized void endTurn() {
myTurnIsDone = true;
notify();
}
//--------------------------------------------------------
// main communication method
/**
* Makes a connection to the server and sends and receives
* information about moves.
*/
public void run() {
DataInputStream dis = null;
DataOutputStream dos = null;
SocketConnection conn = null;
byte[] fourBytes = new byte[4];
try {
// tell the user that we're waiting for the other player to join:
myCanvas.setWaitScreen(true);
myCanvas.repaint();
myCanvas.serviceRepaints();
// now make the connection:
conn = (SocketConnection)Connector.open(SERVER_URL);
conn.setSocketOption(SocketConnection.KEEPALIVE, 1);
dos = conn.openDataOutputStream();
dis = conn.openDataInputStream();
// we read four bytes to make sure the connection works...
dis.readFully(fourBytes);
if(fourBytes[0] != START_GAME_FLAG) {
throw(new Exception("server-side error"));
}
// On this line it will block waiting for another
// player to join the game or make a move:
dis.readFully(fourBytes);
// if the server sends the start game flag again,
// that means that we start with the local player's turn.
// Otherwise, we read the other player's first move from the
// stream:
if(fourBytes[0] != START_GAME_FLAG) {
// verify that the other player sent a move
// and not just a message ending the game...
if(fourBytes[0] == END_GAME_FLAG) {
throw(new Exception("other player quit"));
}
// we move the opponent on the local screen.
// then we read from the opponent again,
// in case there's a double-jump:
while(fourBytes[0] != END_TURN_FLAG) {
myGame.moveOpponent(fourBytes);
dis.readFully(fourBytes);
}
}
// now signal the local game that the opponent is done
// so the board must be updated and the local player
// prompted to make a move:
myGame.endOpponentTurn();
myCanvas.setWaitScreen(false);
myCanvas.repaint();
myCanvas.serviceRepaints();
// begin main game loop:
while(! myShouldStop) {
// now it's the local player's turn.
// wait for the player to move a piece:
synchronized(this) {
wait();
}
// after every wait, we check if the game
// ended while we were waiting...
if(myShouldStop) {
break;
}
while(! myTurnIsDone) {
// send the current move:
if(myMove != null) {
dos.write(myMove, 0, myMove.length);
myMove = null;
}
// If the player can continue the move with a double
// jump, we wait for the player to do it:
synchronized(this) {
// make sure the turn isn't done before we start waiting
// (the end turn notify might accidentally be called
// before we start waiting...)
if(! myTurnIsDone) {
wait();
}
}
}
// after every wait, we check if the game
// ended while we were waiting...
if(myShouldStop) {
break;
}
// now we tell the other player the this player's
// turn is over:
fourBytes[0] = END_TURN_FLAG;
dos.write(fourBytes, 0, fourBytes.length);
// now that we've sent the move, we wait for a response:
dis.readFully(fourBytes);
while((fourBytes[0] != END_TURN_FLAG) &&
(fourBytes[0] != END_GAME_FLAG) && (!myShouldStop)) {
// we move the opponent on the local screen.
// then we read from the opponent again,
// in case there's a double-jump:
myGame.moveOpponent(fourBytes);
dis.readFully(fourBytes);
}
// if the other player has left the game, we tell the
// local user that the game is over.
if((fourBytes[0] == END_GAME_FLAG) || (myShouldStop)) {
endGame();
break;
}
myGame.endOpponentTurn();
myCanvas.repaint();
myCanvas.serviceRepaints();
} // end while loop
} catch(Exception e) {
// if there's an error, we display its messsage and
// end the game.
myCheckers.errorMsg(e.getMessage());
} finally {
// now we send the information that we're leaving the game,
// then close up and delete everything.
try {
if(dos != null) {
dos.write(END_GAME_FLAG);
dos.close();
}
if(dis != null) {
dis.close();
}
if(conn != null) {
conn.close();
}
dis = null;
dos = null;
conn = null;
} catch(Exception e) {
// if this throws, at least we made our best effort
// to close everything up....
}
}
// one last paint job to display the "Game Over"
myCanvas.repaint();
myCanvas.serviceRepaints();
}
}
/**
* This class is a set of simple utility functions that
* can be used to convert standard data types to bytes
* and back again. It is used especially for data storage,
* but also for sending and receiving data.
*
* @author Carol Hamer
*/
class DataConverter {
//--------------------------------------------------------
// utilities to encode small, compactly-stored small ints.
/**
* Encodes a coordinate pair into a byte.
* @param coordPair a pair of integers to be compacted into
* a single byte for storage.
* WARNING: each of the two values MUST BE
* between 0 and 15 (inclusive). This method does not
* verify the length of the array (which must be 2!)
* nor does it verify that the ints are of the right size.
*/
public static byte encodeCoords(int[] coordPair) {
// get the byte value of the first coordinate:
byte retVal = (new Integer(coordPair[0])).byteValue();
// move the first coordinate's value up to the top
// half of the storage byte:
retVal = (new Integer(retVal << 4)).byteValue();
// store the second coordinate in the lower half
// of the byte:
retVal += (new Integer(coordPair[1])).byteValue();
return(retVal);
}
/**
* Encodes eight ints into a byte.
* This could be easily modified to encode eight booleans.
* @param eight an array of at least eight ints.
* WARNING: all values must be 0 or 1! This method does
* not verify that the values are in the correct range
* nor does it verify that the array is long enough.
* @param offset the index in the array eight to start
* reading data from. (should usually be 0)
*/
public static byte encode8(int[] eight, int offset) {
// get the byte value of the first int:
byte retVal = (new Integer(eight[offset])).byteValue();
// progressively move the data up one bit in the
// storage byte and then record the next int in
// the lowest spot in the storage byte:
for(int i = offset + 1; i < 8 + offset; i++) {
retVal = (new Integer(retVal << 1)).byteValue();
retVal += (new Integer(eight[i])).byteValue();
}
return(retVal);
}
//--------------------------------------------------------
// utilities to decode small, compactly-stored small ints.
/**
* Turns a byte into a pair of coordinates.
*/
public static int[] decodeCoords(byte coordByte) {
int[] retArray = new int[2];
// we perform a bitwise and with the value 15
// in order to just get the bits of the lower
// half of the byte:
retArray[1] = coordByte & 15;
// To get the bits of the upper half of the
// byte, we perform a shift to move them down:
retArray[0] = coordByte >> 4;
// bytes in Java are generally assumed to be
// signed, but in this coding algorithm we
// would like to treat them as unsigned:
if(retArray[0] < 0) {
retArray[0] += 16;
}
return(retArray);
}
/**
* Turns a byte into eight ints.
*/
public static int[] decode8(byte data) {
int[] retArray = new int[8];
// The flag allows us to look at each bit individually
// to determine if it is 1 or 0. The number 128
// corresponds to the highest bit of a byte, so we
// start with that one.
int flag = 128;
// We use a loop that checks
// the data bit by bit by performing a bitwise
// and (&) between the data byte and a flag:
for(int i = 0; i < 8; i++) {
if((flag & data) != 0) {
retArray[i] = 1;
}

您现在的位置: