Checkers game : Game « J2ME « Java






Checkers game

Checkers game
/*
Title:  J2ME Games With MIDP2
Authors:  Carol Hamer
Publisher:  Apress
ISBN:   1590593820
*/
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.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;
      } 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
  }
  
}


           
       








Related examples in the same category

1.Maze gameMaze game
2.Game Action ExampleGame Action Example
3.Game Key EventGame Key Event
4.Sweep GameSweep Game
5.SweepSweep
6.Dungeon game
7.Tumbleweed game
8.Canvas for processing game actionsCanvas for processing game actions