Java手机游戏编程之MIDP图形设计

package example.tictactoe;
import java.util.Random;
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
class GameScreen extends Canvas implements CommandListener {
 private static final int BLACK = 0x00000000;
 private static final int WHITE = 0x00FFFFFF;
 private static final int RED = 0x00FF0000;
 private static final int BLUE = 0x000000FF;
 private static final int NO_MOVE = -1;
 private final TicTacToeMIDlet midlet;
 private final Game game;
 private final Command exitCommand;
 private final Command newGameCommand;
 private final Random random = new Random();
 private int screenWidth, screenHeight;
 private int boardCellSize, boardSize, boardTop, boardLeft;
 private boolean playerIsCircle;
 private boolean computerIsCircle;
 private int preCursorPosition, cursorPosition;
 private int computerMove = NO_MOVE;
 private int playerMove = NO_MOVE;
 private int computerGamesWonTally = 0;
 private int playerGamesWonTally = 0;
 private boolean isRestart;
 public GameScreen(TicTacToeMIDlet midlet, boolean playerIsCircle) {
  this.midlet = midlet;
  this.playerIsCircle = playerIsCircle;
  computerIsCircle = !playerIsCircle;
  game = new Game(random);
  initializeBoard();
//  configure Screen commands
  exitCommand = new Command("Exit", Command.EXIT, 1);
  newGameCommand = new Command("New", Command.SCREEN, 2);
  addCommand(exitCommand);
  addCommand(newGameCommand);
  setCommandListener(this);
//  begin the game play initialize();
 }
// Initialize the Game and Game screen. Also used for game restarts.
 private void initialize() {
  game.initialize();
  preCursorPosition = cursorPosition = 0;
  playerMove = NO_MOVE;
  boolean computerFirst = ((random.nextInt() & 1) == 0);
  if (computerFirst) {
   computerMove = game.makeComputerMove();
  }
  else
  {
   computerMove = NO_MOVE;
  }
  isRestart = true;
  repaint();
 }
 public void paint(Graphics g) {
  if (game.isGameOver()) {
   paintGameOver(g);
  }
  else {
   paintGame(g);
  }
 }
 private void paintGame(Graphics g) {
  if (isRestart) {
//   clean the canvas
   g.setColor(WHITE);
   g.fillRect(0, 0, screenWidth, screenHeight);
   drawBoard(g);
   isRestart = false;
  }
  drawCursor(g);
  if (playerMove != NO_MOVE) {
   drawPiece(g, playerIsCircle, playerMove);
  }
  if (computerMove != NO_MOVE) {
   drawPiece(g, computerIsCircle, computerMove);
  }
 }
 private void paintGameOver(Graphics g)

 {
  String statusMsg = null;
  if(game.isComputerWinner()) {
   statusMsg = "I win !";
   computerGamesWonTally++;
  }
  else if (game.isPlayerWinner()) {
   statusMsg = "You win";
   playerGamesWonTally++;
  }
  else {
   statusMsg = "Stalemate";
  }
  String tallyMsg = "You:" + playerGamesWonTally + " Me:" + computerGamesWonTally;
  Font font = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_MEDIUM);
  int strHeight = font.getHeight();
  int statusMsgWidth = font.stringWidth(statusMsg);
  int tallyMsgWidth = font.stringWidth(tallyMsg);
  int strWidth = tallyMsgWidth;
  if (statusMsgWidth > tallyMsgWidth)
  {
   strWidth = statusMsgWidth;
  }
//  Get the
  {
   x, y
  }
  position for painting the strings. int x = (screenWidth – strWidth) / 2;
  x = x < 0 ? 0 : x;
  int y = (screenHeight – 2 * strHeight) / 2;
  y = y < 0 ? 0 : y;
//  clean the canvas
  g.setColor(WHITE);
  g.fillRect(0, 0, screenWidth, screenHeight);
//  paint the strings’ text
  g.setColor(BLACK);
  g.drawString(statusMsg, x, y, (Graphics.TOP | Graphics.LEFT));
  g.drawString(tallyMsg, x, (y + 1 + strHeight), (Graphics.TOP | Graphics.LEFT));
 }

 public void commandAction(Command c, Displayable d) {
  if (c == exitCommand) {
   midlet.quit();
  }
  else if (c == newGameCommand) {
   initialize();
  }
 }
 private void initializeBoard() {
  screenWidth = getWidth();
  screenHeight = getHeight();
  if (screenWidth > screenHeight) {
   boardCellSize = (screenHeight – 2) / 3;
   boardLeft = (screenWidth – (boardCellSize * 3)) / 2;
   boardTop = 1;
  }
  else {
   boardCellSize = (screenWidth – 2) / 3;
   boardLeft = 1;
   boardTop = (screenHeight – boardCellSize * 3) / 2;
  }
 }
 protected void keyPressed(int keyCode) {
//  can’t continue playing until the player restarts
  if (game.isGameOver()) {
   return;
  }
  int gameAction = getGameAction(keyCode);
  switch (gameAction) {
  case FIRE: doPlayerMove();

  break;
  case RIGHT: doMoveCursor(1, 0);
  break;
  case DOWN: doMoveCursor(0, 1);
  break;
  case LEFT: doMoveCursor(-1, 0);
  break;
  case UP: doMoveCursor(0, -1);
  break;
  default: break;
  }
 }
 private void doPlayerMove() {
  if (game.isFree(cursorPosition)) {
//   player move game.
   makePlayerMove(cursorPosition);
   playerMove = cursorPosition;
//   computer move
   if (!game.isGameOver()) {
    computerMove = game.makeComputerMove();
   }
   repaint();
  }
 }
 private void doMoveCursor(int dx, int dy) {
  int newCursorPosition = cursorPosition + dx + 3 * dy;
  if ((newCursorPosition >= 0) && (newCursorPosition < 9))

  {
   preCursorPosition = cursorPosition;
   cursorPosition = newCursorPosition;
   repaint();
  }
 }
// Draw a CIRCLE or CROSS piece on the board
 private void drawPiece(Graphics g, boolean isCircle, int pos) {
  int x = ((pos % 3) * boardCellSize) + 3;
  int y = ((pos / 3) * boardCellSize) + 3;
  if (isCircle) {
   drawCircle(g, x, y);
  }
  else {
   drawCross(g, x, y);
  }
 }
// Draw blue CIRCLE onto the board image
 private void drawCircle(Graphics g, int x, int y) {
  g.setColor(BLUE);
  g.fillArc(x + boardLeft, y + boardTop, boardCellSize – 4, boardCellSize – 4, 0, 360);
  g.setColor(WHITE);
  g.fillArc(x + 4 + boardLeft, y + 4 + boardTop, boardCellSize – 4 – 8, boardCellSize – 4 – 8, 0, 360);
 }
// Draw red CROSS onto the board image
 private void drawCross(Graphics g, int x, int y) {
  g.setColor(RED);
  for (int i = 0;
  i < 4;
  i++) {
   g.drawLine(x + 1 + i + boardLeft, y + boardTop, x + boardCellSize – 4 – 4 + i + boardLeft, y + boardCellSize – 5 + boardTop);

   g.drawLine(x + 1 + i + boardLeft, y + boardCellSize – 5 + boardTop, x + boardCellSize – 4 – 4 + i + boardLeft, y + boardTop);
  }
 }
// Visually indicates a Player selected square on the board image
 private void drawCursor(Graphics g) {
//  draw cursor at selected Player square.
  g.setColor(WHITE);
  g.drawRect(((preCursorPosition % 3) * boardCellSize) + 2 + boardLeft, ((preCursorPosition/3) * boardCellSize) + 2 + boardTop, boardCellSize – 3, boardCellSize – 3);
//  draw cursor at selected Player square.
  g.setColor(BLACK);
  g.drawRect(((cursorPosition % 3) * boardCellSize) + 2 + boardLeft, ((cursorPosition/3) * boardCellSize) + 2 + boardTop, boardCellSize – 3, boardCellSize – 3);
 }
 private void drawBoard(Graphics g) {
//  clean the board
  g.setColor(WHITE);
  g.fillRect(0, 0, screenWidth, screenHeight);
//  draw the board
  g.setColor(BLACK);
  for (int i = 0;
  i < 4;
  i++) {
   g.fillRect(boardLeft, boardCellSize * i + boardTop, (boardCellSize * 3) + 2, 2);
   g.fillRect(boardCellSize * i + boardLeft, boardTop, 2, boardCellSize * 3);
  }
 }
}

6、Game.java

这个类封装了九宫格游戏的主要的游戏程序逻辑。前面我们也说过,游戏程序逻辑本身并不在本例程重点讨论的范围之内,本文主要是介绍MIDP图形编程的基础知识。游戏程序逻辑的WINS数组部分来自http://java.sun.com/applets/jdk/1.0/demo/TicTacToe/TicTacToe.java 这个经典例程。

 注意游戏程序逻辑是独立于游戏用户界面的(参见类GameScreen),并且可以使用其它实现方法替代。

 package example.tictactoe;
import java.util.Random;
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
//The game logic for TicTacToe
class Game {
 private static final int[] WINS = {
//  horizontals
  bit(0) | bit(1) | bit(2),
  bit(3) | bit(4) | bit(5),
  bit(6) | bit(7) | bit(8),
//  verticals
  bit(0) | bit(3) | bit(6),
  bit(1) | bit(4) | bit(7),
  bit(2) | bit(5) | bit(8),
//  diagonals
  bit(0) | bit(4) | bit(8),
  bit(2) | bit(4) | bit(6) }
 ;
 private static final int DRAWN_GAME = bit(0) | bit(1) | bit(2) | bit(3) | bit(4) | bit(5) | bit(6) | bit(7) | bit(8);
 private int playerState;
 private int computerState;
 private Random random;
 Game(Random random) {
  this.random = random;
  initialize();
 }
 void initialize() {
  playerState = 0;
  computerState = 0;
 }
 boolean isFree(int position) {
  int bit = bit(position);
  return (((playerState & bit) == 0) && ((computerState & bit) == 0));
 }
// The ‘Contract’ is that caller will always make valid moves.
// We don’t check that it’s the player’s turn.
 void makePlayerMove(int position) {
  playerState |= bit(position);
 }
// The ‘Contract’ is that we will be called only when there is still
// at least one free square.
 int makeComputerMove() {
  int move = getWinningComputerMove();
  if (move == -1) {
//   can’t win
   move = getRequiredBlockingComputerMove();
   if (move == -1) {
//    don’t need to block
    move = getRandomComputerMove();
   }
  }
  computerState |= bit(move);
  return move;
 }

 boolean isGameOver() {
  return isPlayerWinner() | isComputerWinner() | isGameDrawn();
 }
 boolean isPlayerWinner() {
  return isWin(playerState);
 }
 boolean isComputerWinner() {
  return isWin(computerState);
 }
 boolean isGameDrawn() {
  return (playerState | computerState) == DRAWN_GAME;
 }
// Return a winning move if there is at least one, otherwise return -1
 private int getWinningComputerMove() {
  int move = -1;
  for (int i = 0;
  i < 9;
  ++i) {
   if (isFree(i) && isWin(computerState | bit(i))) {
    move = i;
    break;
   }
  }
  return move;
 }
// Return a required blocking move if there is at least one (more
// than one and we’ve inevitably lost), otherwise return -1
 private int getRequiredBlockingComputerMove() {
  int move = -1;
  for (int i = 0;
  i < 9;
  ++i) {

   if (isFree(i) && isWin(playerState | bit(i))) {
    move = i;
    break;
   }
  }
  return move;
 }
// Return a random move in a free square, // or return -1 if none are available private int getRandomComputerMove() {
 int move = -1;
// determine how many possible moves there are int numFreeSquares = 0;
 for (int i = 0;
 i < 9;
 ++i) {
  if (isFree(i)) {
   numFreeSquares++;
  }
 }
// if there is at least one possible move, pick randomly
 if (numFreeSquares > 0) {
//  shift twice to get rid of sign bit, then modulo numFreeSquares
  int pick = ((random.nextInt()<<1)>>>1) % numFreeSquares;
//  now find the chosen free square by counting pick down to zero
  for (int i = 0;
  i < 9;
  ++i) {
   if (isFree(i)) {
    if (pick == 0) {
     move = i;
     break;
    }
    pick–;
   }
  }
 }

 return move;
}
private static boolean isWin(int state) {
 boolean isWinner = false;
 for (int i = 0;
 i < WINS.length;
 ++i) {
  if ((state & WINS[i]) == WINS[i]) {
   isWinner = true;
   break;
  }
 }
 return isWinner;
}
private static int bit(int i) {
 return 1 << i;
}
}

7、TicTacToe.jad

  下面是九宫格MIDlet的应用程序描述文件。

MIDlet-Name: TicTacToe
MIDlet-Vendor: Forum Nokia MIDlet-Version: 1.1.1
MIDlet-Jar-Size: 11409
MIDlet-Jar-URL: TicTacToe.jar
MIDlet-1: TicTacToe, /tictactoe.png, example.tictactoe.TicTacToeMIDlet