WEB开发网
开发学院软件开发Java Checkers游戏源码 阅读

Checkers游戏源码

 2007-12-23 12:36:07 来源:WEB开发网   
核心提示:import java.util.Vector;import java.io.*;import javax.microedition.io.*;import javax.microedition.rms.*;import javax.microedition.midlet.*;import javax.microedi
import java.util.Vector;
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.TOPg.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.VCENTERGraphics.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.VCENTERGraphics.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.TOPg.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;
   } 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 takes care of the underlying logic and data of 
 * the checkers game being played. That includes where 
 * all of the pieces are on the board and where it is okay 
 * for them to move to. 
 *
 * @author Carol Hamer
 */
class CheckersGame {

 //-------------------------------------------------------
 //  static fields

 /**
  * The length of the checkerboard in the x-direction.
  */
 public static final byte X_LENGTH = 4;

 /**
  * The length of the checkerboard in the y-direction.
  */
 public static final byte Y_LENGTH = 8;

 //-------------------------------------------------------
 //  instance fields

 /**
  * a handle to the communications class that exchanges
  * data with the server.
  */
 private Communicator myCommunicator;

 /**
  * This array represents the black squares of the 
  * checkerboard. The two dimensions of the array 
  * represent the two dimensions of the checkerboard.
  * The value represents what type of piece is on 
  * the square.
  * 0 = empty
  * 1 = local player's piece
  * 2 = local player's king
  * -1 = remote player's piece
  * -2 = remote player's king
  */
 private byte[][] myGrid;

 /**
  * If the user has currently selected a piece to move, 
  * this is its X grid coordinate. (-1 if none selected)
  */
 private byte mySelectedX = -1;

 /**
  * If the user has currently selected a piece to move, 
  * this is its Y grid coordinate.(-1 if none selected)
  */
 private byte mySelectedY = -1;

 /**
  * If the user has currently selected a possible 
  * destination square for a move, this is its X coordinate..
  * (-1 if none selected)
  */
 private byte myDestinationX = -1;

 /**
  * If the user has currently selected a possible 
  * destination square for a move, this is its Y coordinate..
  * (-1 if none selected)
  */
 private byte myDestinationY = -1;

 /**
  * This Vector contains the coordinates of all of the 
  * squares that the player could currently move to.
  */
 private Vector myPossibleMoves = new Vector(4);

 /**
  * Whether or not the currently displayed checkers has 
  * been completed.
  */
 private boolean myGameOver = false;

 /**
  * Whether or not it is currently this player's turn.
  */
 private boolean myTurn = false;

 /**
  * This is true if the player has just jumped and can 
  * jump again.
  */
 private boolean myIsJumping = false;

 //-------------------------------------------------------
 //  get/set data
 /**
  * get the piece on the given grid square.
  */
 byte getPiece(byte x, byte y) {
  return(myGrid[x][y]);
 }

 /**
  * This is callsed by CheckersCanvas to determine if 
  * the square is currently selected (as containing 
  * a piece to move or a destination square).
  */
 boolean isSelected(byte x, byte y) {
  boolean retVal = false;
  if((x == mySelectedX) && (y == mySelectedY)) {
   retVal = true;
  } else if((x == myDestinationX) && (y == myDestinationY)) {
   retVal = true;
  }
  return(retVal);
 }

 /**
  * This tells whether or not the keystrokes should currently
  * be taken into account.
  */
 boolean isMyTurn() {
  boolean retVal = false;
  if((!myGameOver) && ((myTurn) (myIsJumping))) {
   retVal = true;
  }
  return(retVal);
 }

 /**
  * This tells whether or not the game has ended.
  */
 boolean getGameOver() {
  boolean retVal = false;
  if(myGameOver) {
   retVal = true;
  }
  return(retVal);
 }

 /**
  * tell the CheckersGame that the other player has ended the game.
  */
 void setGameOver() {
  myGameOver = true;
 }

 /**
  * set the communicator object.
  */
 void setCommunicator(Communicator comm) {
  myCommunicator = comm;
 }

 //-------------------------------------------------------
 //  initialization

 /**
  * Constructor puts the pieces in their initial positions:
  */
 CheckersGame() {
  myGrid = new byte[X_LENGTH][];
  for(byte i = 0; i < myGrid.length; i++) {
   myGrid[i] = new byte[Y_LENGTH];
   for(byte j = 0; j < myGrid[i].length; j++) {
 if(j < 3) {
  // fill the top of the board with remote players
  myGrid[i][j] = -1;
 } else if(j > 4) {
  // fill the bottom of the board with local players
  myGrid[i][j] = 1;
 }
   }
  }
 }

 /**
  * This is called just before the player makes the 
  * first move.
  */
 void start() {
  mySelectedX = 0;
  mySelectedY = 5;
  myTurn = true;
  getMoves(mySelectedX, mySelectedY, myPossibleMoves, false);
 }

 //-------------------------------------------------------
 //  move the opponent
 // to be called by Communicator

 /**
  * This is called when the opponent wants to move
  * its piece.
  * @param moveData an array of four bytes:
  * moveData[0] = opponent's initial X coordinate
  * moveData[1] = opponent's initial Y coordinate
  * moveData[2] = opponent's destination X coordinate
  * moveData[3] = opponent's destination Y coordinate
  */
 void moveOpponent(byte[] moveData) {
  // since both players appear on their own screens 
  // as the red side (bottom of the screen), we need 
  // to invert the opponent's move:
  moveData[0] = (new Integer(X_LENGTH - moveData[0] - 1)).byteValue();
  moveData[2] = (new Integer(X_LENGTH - moveData[2] - 1)).byteValue();
  moveData[1] = (new Integer(Y_LENGTH - moveData[1] - 1)).byteValue();
  moveData[3] = (new Integer(Y_LENGTH - moveData[3] - 1)).byteValue();
  myGrid[moveData[2]][moveData[3]]
   = myGrid[moveData[0]][moveData[1]];
  myGrid[moveData[0]][moveData[1]] = 0;
  // deal with an opponent's jump:
  if((moveData[1] - moveData[3] > 1) 
    (moveData[3] - moveData[1] > 1)) {
   int jumpedY = (moveData[1] + moveData[3])/2;
   int jumpedX = moveData[0];
   int parity = moveData[1] % 2;
   if((parity > 0) && (moveData[2] > moveData[0])) {
 jumpedX++;
   } else if((parity == 0) && (moveData[0] > moveData[2])) {
 jumpedX--;
   }
   myGrid[jumpedX][jumpedY] = 0;
  }
  // if the opponent reaches the far side, 
  // make him a king:
  if(moveData[3] == Y_LENGTH - 1) {
   myGrid[moveData[2]][moveData[3]] = -2;
  }
 }

 /**
  * This is called when the opponent's turn is over.
  * Note that the turn doesn't automatically end after 
  * the opponent moves because the opponent may make 
  * a double or triple jump.
  */
 void endOpponentTurn() {
  myTurn = true;
  // Now begin the local player's turn: 
  // First select the first local piece that can be 
  // moved. (rightPressed will select an appropriate 
  // piece or end the game if the local player has 
  // no possible moves to make)
  mySelectedX = 0;
  mySelectedY = 0;
  myDestinationX = -1;
  myDestinationY = -1;
  rightPressed();
  // the local player's thread has been waiting 
  // for the opponent's turn to end. 
  synchronized(this) {
   notify();
  }
 }

 //-------------------------------------------------------
 //  handle keystrokes
 // to be called by CheckersCanvas

 /**
  * if the left button is pressed, this method takes 
  * the correct course of action depending on the situation.
  */
 void leftPressed() {
  // in the first case the user has not yet selected a 
  // piece to move:
  if(myDestinationX == -1) {
   // find the next possible piece (to the left) 
   // that can move:
   selectPrevious();
   // if selectPrevious fails to fill myPossibleMoves, that 
   // means that the local player cannot move, so the game
   // is over:
   if(myPossibleMoves.size() == 0) {
 myCommunicator.endGame();
   }
  } else {
   // if the user has already selected a piece to move, 
   // we give the options of where the piece can move to:
   for(byte i = 0; i < myPossibleMoves.size(); i++) {
 byte[] coordinates = (byte[])myPossibleMoves.elementAt(i);
 if((coordinates[0] == myDestinationX) && 
   (coordinates[1] == myDestinationY)) {
  i++;
  i = (new Integer(i % myPossibleMoves.size())).byteValue();
  coordinates = (byte[])myPossibleMoves.elementAt(i);
  myDestinationX = coordinates[0];
  myDestinationY = coordinates[1];
  break;
 }
   }
  }
 }

 /**
  * if the left button is pressed, this method takes 
  * the correct course of action depending on the situation.
  */
 void rightPressed() {
  // in the first case the user has not yet selected a 
  // piece to move:
  if(myDestinationX == -1) {
   // find the next possible piece that can 
   // move:
   selectNext();
   // if selectNext fails to fill myPossibleMoves, that 
   // means that the local player cannot move, so the game
   // is over:
   if(myPossibleMoves.size() == 0) {
 myCommunicator.endGame();
   }
  } else {
   // if the user has already selected a piece to move, 
   // we give the options of where the piece can move to:
   for(byte i = 0; i < myPossibleMoves.size(); i++) {
 byte[] coordinates = (byte[])myPossibleMoves.elementAt(i);
 if((coordinates[0] == myDestinationX) && 
   (coordinates[1] == myDestinationY)) {
  i++;
  i = (new Integer(i % myPossibleMoves.size())).byteValue();
  coordinates = (byte[])myPossibleMoves.elementAt(i);
  myDestinationX = coordinates[0];
  myDestinationY = coordinates[1];
  break;
 }
   }
  }
 }

 /**
  * If no piece is selected, we select one. If a piece * is selected, we move it.
  */
 void upPressed() {
  // in the first case the user has not yet selected a 
  // piece to move:
  if(myDestinationX == -1) {
   fixSelection();
  } else {
   // if the source square and destination square 
   // have been chosen, we move the piece:
   move();
  }
 }

 /**
  * If the user decided not to move the selected piece 
  * (and instead wants to select again), this undoes 
  * the selection. This corresponds to pressing the 
  * DOWN key.
  */
 void deselect() {
  // if the player has just completed a jump and 
  // could possibly jump again but decides not to 
  // (i.e. deselects), then the turn ends:
  if(myIsJumping) {
   mySelectedX = -1;
   mySelectedY = -1;
   myDestinationX = -1;
   myDestinationY = -1;
   myIsJumping = false;
   myTurn = false;
   myCommunicator.endTurn();
  } else {
   // setting the destination coordinates to -1 
   // is the signal that the the choice of which 
   // piece to move can be modified:
   myDestinationX = -1;
   myDestinationY = -1;
  }
 }

 //-------------------------------------------------------
 //  internal square selection methods

 /**
  * When the player has decided that the currently selected
  * square contains the piece he really wants to move, this 
  * is called. This method switches to the mode where 
  * the player selects the destination square of the move.
  */
 private void fixSelection() {
  byte[] destination = (byte[])myPossibleMoves.elementAt(0);
  // setting the destination coordinates to valid 
  // coordinates is the signal that the user is done 
  // selecting the piece to move and now is choosing 
  // the destination square:
  myDestinationX = destination[0];
  myDestinationY = destination[1];
 }

 /**
  * This method starts from the currently selected square 
  * and finds the next square that contains a piece that 
  * the player can move.
  */
 private void selectNext() {
  // Test the squares one by one (starting from the 
  // currently selected square) until we find a square 
  // that contains one of the local player's pieces 
  // that can move:
  byte testX = mySelectedX;
  byte testY = mySelectedY;
  while(true) {
   testX++;
   if(testX >= X_LENGTH) {
 testX = 0;
 testY++;
 testY = (new Integer(testY % Y_LENGTH)).byteValue();
   }
   getMoves(testX, testY, myPossibleMoves, false);
   if((myPossibleMoves.size() != 0) 
   ((testX == mySelectedX) && (testY == mySelectedY))) {
 mySelectedX = testX;
 mySelectedY = testY;
 break;
   }
  }
 }

 /**
  * This method starts from the currently selected square 
  * and finds the next square (to the left) that contains 
  * a piece that the player can move.
  */
 private void selectPrevious() {
  // Test the squares one by one (starting from the 
  // currently selected square) until we find a square 
  // that contains one of the local player's pieces 
  // that can move:
  byte testX = mySelectedX;
  byte testY = mySelectedY;
  while(true) {
   testX--;
   if(testX < 0) {
 testX += X_LENGTH;
 testY--;
 if(testY < 0) {
  testY += Y_LENGTH;
 }
   }
   getMoves(testX, testY, myPossibleMoves, false);
   if((myPossibleMoves.size() != 0) 
  ((testX == mySelectedX) && (testY == mySelectedY))) {
 mySelectedX = testX;
 mySelectedY = testY;
 break;
   }
  }
 }

 //-------------------------------------------------------
 //  internal utilities

 /**
  * Once the user has selected the move to make, this 
  * updates the data accordingly.
  */
 private void move() {
  // the piece that was on the source square is 
  // now on the destination square:
  myGrid[myDestinationX][myDestinationY] 
   = myGrid[mySelectedX][mySelectedY];
  // the source square is emptied:
  myGrid[mySelectedX][mySelectedY] = 0;
  if(myDestinationY == 0) {
   myGrid[myDestinationX][myDestinationY] = 2;
  }
  // tell the communicator to inform the other player 
  // of this move:
  myCommunicator.move(mySelectedX, mySelectedY, 
   myDestinationX, myDestinationY);
  // deal with the special rules for jumps::
  if((mySelectedY - myDestinationY > 1) 
    (myDestinationY - mySelectedY > 1)) {
   int jumpedY = (mySelectedY + myDestinationY)/2;
   int jumpedX = mySelectedX;
   int parity = mySelectedY % 2;
   // the coordinates of the jumped square depend on 
   // what row we're in:
   if((parity > 0) && (myDestinationX > mySelectedX)) {
     jumpedX++;
   } else if((parity == 0) && (mySelectedX > myDestinationX)) {
     jumpedX--;
   }
   // remove the piece that was jumped over:
   myGrid[jumpedX][jumpedY] = 0;
   // now get ready to jump again if possible:
   mySelectedX = myDestinationX;
   mySelectedY = myDestinationY;
   myDestinationX = -1;
   myDestinationY = -1;
   // see if another jump is possible.
   // The "true" argument tells the program to return 
   // only jumps because the player can go again ONLY 
   // if there's a jump:
   getMoves(mySelectedX, mySelectedY, myPossibleMoves, true);
   // if there's another jump possible with the same piece, 
   // allow the player to continue jumping:
   if(myPossibleMoves.size() != 0) {
 myIsJumping = true;
 byte[] landing = (byte[])myPossibleMoves.elementAt(0);
 myDestinationX = landing[0];
 myDestinationY = landing[1];
   } else {
 myTurn = false;
 myCommunicator.endTurn();
   }
  } else {
   // since it's not a jump, we just end the turn 
   // by deselecting everything.
   mySelectedX = -1;
   mySelectedY = -1;
   myDestinationX = -1;
   myDestinationY = -1;
   myPossibleMoves.removeAllElements();
   myTurn = false;
   // tell the other player we're done:
   myCommunicator.endTurn();
  }
 }
 /**
  * Given a square on the grid, get the coordinates 
  * of one of the adjoining (diagonal) squares.
  * 0 = top left
  * 1 = top right
  * 2 = bottom left
  * 3 = bottom right.
  * @return the coordinates or null if the desired corner 
  * is off the board.
  */
 private byte[] getCornerCoordinates(byte x, byte y, byte corner) {
  byte[] retArray = null;
  if(corner < 2) {
   y--;
  } else {
   y++;
  }
  // Where the corner is on the grid depends on 
  // whether this is an odd row or an even row:
  if((corner % 2 == 0) && (y % 2 != 0)) {
   x--;
  } else if((corner % 2 != 0) && (y % 2 == 0)) {
   x++;
  }
  try {
   if(myGrid[x][y] > -15) {
 // we don't really care about the value, this
 // if statement is just there to get it to 
 // throw if the coordinates aren't on the board.
 retArray = new byte[2];
 retArray[0] = x;
 retArray[1] = y;
   }
  } catch(ArrayIndexOutOfBoundsException e) {
   // this throws if the coordinates do not correspond 
   // to a square on the board. It's not a problem, 
   // so we do nothing--we just return null instead 
   // of returning coordinates since no valid 
   // coordinates correspond to the desired corner.
  }
  return(retArray);
 }
 /**
  * Determines where the piece in the given 
  * grid location can move. Clears the Vector
  * and fills it with the locations that 
  * the piece can move to.
  * @param jumpsOnly if we should return only moves that 
  *    are jumps.
  */
 private void getMoves(byte x, byte y, Vector toFill, boolean jumpsOnly) {
  toFill.removeAllElements();
  // if the square does not contain one of the local player's 
  // pieces, then there are no corresponding moves and we just
  // return an empty vector.
  if(myGrid[x][y] <= 0) {
   return;
  }
  // check each of the four corners to see if the 
  // piece can move there:
  for(byte i = 0; i < 4; i++) {
   byte[] coordinates = getCornerCoordinates(x, y, i);
   // if the coordinate array is null, then the corresponding 
   // corner is off the board and we don't deal with it.
   // The later two conditions in the following if statement
   // ensure that either the move is a forward move or the 
   // current piece is a king:
   if((coordinates != null) &&
  ((myGrid[x][y] > 1) (i < 2))) {
 // if the corner is empty (and we're not looking 
 // for just jumps), then this is a possible move
 // so we add it to the vector of moves:
 if((myGrid[coordinates[0]][coordinates[1]] == 0) && (! jumpsOnly)) {
  toFill.addElement(coordinates);
  // if the space is occupied by an opponent, see if we can jump it:
 } else if(myGrid[coordinates[0]][coordinates[1]] < 0) {
  byte[] jumpLanding = getCornerCoordinates(coordinates[0], 
       coordinates[1], i);
  // if the space on the far side of the opponent's piece
  // is on the board and is unoccupied, then a jump 
  // is possible, so we add it to the vector of moves:
  if((jumpLanding != null) && 
    (myGrid[jumpLanding[0]][jumpLanding[1]] == 0)) {
   toFill.addElement(jumpLanding);
  }
 }
   }
  } // end for loop
 }
}

(出处:http://www.cncms.com)


Tags:Checkers 游戏 源码

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接