本案例實現人機對戰「九宮格」(井字棋),展示 MIDP 核心技術:多屏幕流程控制、高級列表選擇、Canvas 遊戲繪圖、鍵盤事件映射、動態屏幕適配。
屏幕切換由 TicTacToeMIDlet 統一管理,各屏通過回調方法通知 MIDlet 切換。
public class ChoosePieceScreen extends List implements CommandListener {
private TicTacToeMIDlet midlet;
public ChoosePieceScreen(TicTacToeMIDlet midlet) {
super("Choose your piece", List.IMPLICIT);
this.midlet = midlet;
append("Circle (O)", null);
append("Cross (X)", null);
addCommand(new Command("OK", Command.OK, 1));
setCommandListener(this);
}
public void commandAction(Command c, Displayable d) {
if (c.getCommandType() == Command.OK) {
boolean isCircle = (getSelectedIndex() == 0);
midlet.choosePieceScreenDone(isCircle); // 回調 MIDlet
}
}
}
使用 IMPLICIT 模式,選中項自動高亮,按「OK」觸發回調傳遞玩家選擇。
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) {
if (game.isGameOver()) return;
int action = getGameAction(keyCode); // 映射為標準遊戲動作
switch (action) {
case FIRE: doPlayerMove(); break;
case RIGHT: moveCursor(1, 0); break;
case LEFT: moveCursor(-1, 0); break;
case DOWN: moveCursor(0, 1); break;
case UP: moveCursor(0, -1); break;
}
}
private void moveCursor(int dx, int dy) {
int newPos = cursorPosition + dx + 3 * dy;
if (newPos >= 0 && newPos < 9) {
preCursorPosition = cursorPosition;
cursorPosition = newPos;
repaint();
}
}
getGameAction() 關鍵作用:屏蔽設備鍵盤差異,將數字鍵、方向鍵、軟鍵統一映射為 UP/DOWN/LEFT/RIGHT/FIRE。
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);
}
優化點:避免全屏重繪,僅重繪變化區域;isRestart 標記控制棋盤背景只畫一次。
private void drawCircle(Graphics g, int x, int y) {
g.setColor(BLUE);
g.fillArc(x + boardLeft, y + boardTop, cellSize-4, cellSize-4, 0, 360);
g.setColor(WHITE);
g.fillArc(x+4+boardLeft, y+4+boardTop, cellSize-12, cellSize-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+cellSize-8+i+boardLeft, y+cellSize-5+boardTop);
g.drawLine(x+1+i+boardLeft, y+cellSize-5+boardTop, x+cellSize-8+i+boardLeft, y+boardTop);
}
}
使用 fillArc 畫實心圓再挖空實現圓環,drawLine 多次偏移畫粗十字。
核心算法分離:Game 類獨立封裝勝負判斷、AI 走位、棋盤狀態管理,GameScreen 僅負責視圖與輸入。這符合 MVC 模式,便於單元測試與邏輯複用。
exitCommand = new Command("Exit", Command.EXIT, 1);
newGameCommand = new Command("New", Command.SCREEN, 2);
addCommand(exitCommand); addCommand(newGameCommand);
setCommandListener(this);
public void commandAction(Command c, Displayable d) {
if (c == exitCommand) midlet.quit();
else if (c == newGameCommand) initialize();
}
使用標準 Command 類型(EXIT, SCREEN, OK, BACK 等),設備自動映射到對應物理鍵(如掛機鍵、菜單鍵)。
| 技術點 | 實現方式 | MIDP 特性體現 |
|---|---|---|
| 多屏導航 | MIDlet 回調 + setCurrent | 單顯示模型、生命週期管理 |
| 高級 UI | List 選單 | 設備自適應渲染、隱式選擇模式 |
| 低級繪圖 | Canvas + Graphics | 像素級控制、雙緩衝隱式支持 |
| 屏幕適配 | getWidth/Height 動態佈局 | 跨設備可移植性核心 |
| 鍵盤統一 | getGameAction 映射 | 解決鍵盤碎片化 |
| 遊戲循環 | 事件驅動 + repaint | 無主循環線程、系統回調模型 |
下篇將深入分析 Game 類 AI 算法(Minimax/啟發式)、持久化存儲(RecordStore 保存戰績)、聲音振動反饋(MIDP 2.0 MMAPI)以及 OTA 部署發布流程。