本文提供 GameScreen 完整實現,涵蓋棋盤自適應、雙緩衝繪圖優化、光標閃爍、玩家/AI 走子、勝負判斷、戰績統計、命令處理等核心模塊,是 MIDP 遊戲開發的參考範本。
class GameScreen extends Canvas implements CommandListener {
// 顏色常量
private static final int BLACK = 0x000000, WHITE = 0x00FFFFFF, RED = 0x00FF0000, BLUE = 0x000000FF;
// 遊戲狀態
private final TicTacToeMIDlet midlet;
private final Game game; // 邏輯封裝類
private final Random random = new Random();
// 棋盤幾何
private int screenWidth, screenHeight, boardCellSize, boardSize, boardTop, boardLeft;
// 棋子屬性
private boolean playerIsCircle, computerIsCircle;
// 光標與走子
private int preCursorPosition, cursorPosition;
private int computerMove = NO_MOVE, playerMove = NO_MOVE;
// 戰績
private int computerGamesWonTally = 0, playerGamesWonTally = 0;
private boolean isRestart;
// 命令
private final Command exitCommand, newGameCommand;
}
public GameScreen(TicTacToeMIDlet midlet, boolean playerIsCircle) {
this.midlet = midlet; this.playerIsCircle = playerIsCircle;
computerIsCircle = !playerIsCircle;
game = new Game(random);
initializeBoard(); // 動態計算幾何參數
exitCommand = new Command("Exit", Command.EXIT, 1);
newGameCommand = new Command("New", Command.SCREEN, 2);
addCommand(exitCommand); addCommand(newGameCommand);
setCommandListener(this);
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;
}
}
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) { // 首幀全繪
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 = game.isComputerWinner() ? "I win !" :
game.isPlayerWinner() ? "You win" : "Stalemate";
if (game.isComputerWinner()) computerGamesWonTally++;
else if (game.isPlayerWinner()) playerGamesWonTally++;
String tallyMsg = "You:" + playerGamesWonTally + " Me:" + computerGamesWonTally;
// 居中繪製結果文本
Font font = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_MEDIUM);
int strHeight = font.getHeight();
int x = (screenWidth - Math.max(font.stringWidth(statusMsg), font.stringWidth(tallyMsg))) / 2;
x = x < 0 ? 0 : x;
int y = (screenHeight - 2 * strHeight) / 2; y = y < 0 ? 0 : y;
g.setColor(WHITE); g.fillRect(0, 0, screenWidth, screenHeight); // 清屏
g.setColor(BLACK);
g.drawString(statusMsg, x, y, Graphics.TOP | Graphics.LEFT);
g.drawString(tallyMsg, x, y + 1 + strHeight, Graphics.TOP | Graphics.LEFT);
}
protected void keyPressed(int keyCode) {
if (game.isGameOver()) return;
int action = getGameAction(keyCode);
switch (action) {
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;
}
}
private void doPlayerMove() {
if (game.isFree(cursorPosition)) {
playerMove = cursorPosition; // 記錄玩家落子位置供繪圖
// AI 走子
if (!game.isGameOver()) computerMove = game.makeComputerMove();
repaint();
}
}
private void doMoveCursor(int dx, int dy) {
int newPos = cursorPosition + dx + 3 * dy;
if (newPos >= 0 && newPos < 9) {
preCursorPosition = cursorPosition;
cursorPosition = newPos;
repaint();
}
}
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);
}
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 - 12, boardCellSize - 12, 0, 360);
}
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 - 8 + i + boardLeft, y + boardCellSize - 5 + boardTop);
g.drawLine(x + 1 + i + boardLeft, y + boardCellSize - 5 + boardTop,
x + boardCellSize - 8 + i + boardLeft, y + boardTop);
}
}
private void drawCursor(Graphics g) {
// 擦除舊光標
g.setColor(WHITE);
g.drawRect((preCursorPosition%3)*boardCellSize+2+boardLeft,
(preCursorPosition/3)*boardCellSize+2+boardTop,
boardCellSize-3, boardCellSize-3);
// 繪製新光標
g.setColor(BLACK);
g.drawRect((cursorPosition%3)*boardCellSize+2+boardLeft,
(cursorPosition/3)*boardCellSize+2+boardTop,
boardCellSize-3, boardCellSize-3);
}
private void drawBoard(Graphics g) {
g.setColor(WHITE); g.fillRect(0, 0, screenWidth, screenHeight);
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); // 豎線
}
}
public void commandAction(Command c, Displayable d) {
if (c == exitCommand) midlet.quit();
else if (c == newGameCommand) initialize();
}
class Game {
private final Random random;
private byte[] board = new byte[9]; // 0=空, 1=玩家, 2=電腦
public Game(Random random) { this.random = random; }
public void initialize() { Arrays.fill(board, (byte)0); }
public boolean isFree(int pos) { return board[pos] == 0; }
public int makeComputerMove() { /* Minimax 或啟發式算法 */ }
public boolean isGameOver() { /* 檢查八條勝負線 + 平局 */ }
public boolean isComputerWinner() { ... }
public boolean isPlayerWinner() { ... }
}
paintGame 僅在 isRestart 時全屏清屏,平時只繪製光標與新落子Random、Font、Command 僅創建一次Runtime.getRuntime().freeMemory() 打印,便於調試| 項目 | 處理方式 |
|---|---|
| 屏幕尺寸 | getWidth/Height 動態佈局 |
| 鍵盤差異 | getGameAction 統一映射 |
| 顏色深度 | 使用系統默認調色盤,避免依賴真彩色 |
| 字體大小 | Font.getHeight() 計算文本佈局 |
| 繪製性能 | 最小化重繪區域,避免浮點運算 |
GameScreen 展示了 MIDP 遊戲開發的最佳實踐:邏輯與視圖分離、動態適配、事件驅動、部分重繪、標準命令映射。此代碼可直接作為其他回合制遊戲(五子棋、翻棋、數獨)的框架基礎。下一階段可擴展:網絡對戰、AI 難度選擇、主題皮膚、觸屏支持(MIDP 3.0/LCDUI 扩展)。