SnakeMIDlet.java — MIDlet 入口,建立並顯示 SnakeCanvasSnakeCanvas.java — 繼承 Canvas 實作 Runnable,包含完整遊戲邏輯// 蛇身節點座標陣列,最大 200 節
int[][] snake = new int[200][2]; // [節點索引][0=x, 1=y]
int snakeLen = 7; // 當前長度
// 移動方向常數
final int UP = 0, DOWN = 1, LEFT = 2, RIGHT = 3;
int dir = RIGHT; // 初始向右
// 遊戲區尺寸
int width, height;
final byte UNIT = 4; // 每格 4 像素
// 食物
int foodX, foodY;
boolean foodVisible = true; // 閃爍控制
Random rnd = new Random();
// 狀態旗標
boolean paused = false;
boolean running = true;
final int SLEEP = 300; // 幀延遲毫秒
private void init() {
snakeLen = 7;
for (int i = 0; i < snakeLen; i++) {
snake[i][0] = 100 - UNIT * i; // 水平排列
snake[i][1] = 40;
}
dir = RIGHT;
foodX = 100; foodY = 100;
new Thread(this).start(); // 啟動遊戲迴路
}
protected void paint(Graphics g) {
// 清背景
g.setColor(0xFFFFFF); g.fillRect(0, 0, width, height);
g.setColor(0); // 黑色蛇身
// 繪製蛇身每節
for (int i = 0; i < snakeLen; i++)
g.fillRect(snake[i][0], snake[i][1], UNIT, UNIT);
// 繪製閃爍食物
if (foodVisible)
g.fillRect(foodX, foodY, UNIT, UNIT);
}
private void move(int dir) {
// 1. 身體跟隨:從尾部向前複製前一節座標
for (int i = snakeLen - 1; i > 0; i--) {
snake[i][0] = snake[i-1][0];
snake[i][1] = snake[i-1][1];
}
// 2. 頭部根據方向移動一格
switch (dir) {
case UP: snake[0][1] -= UNIT; break;
case DOWN: snake[0][1] += UNIT; break;
case LEFT: snake[0][0] -= UNIT; break;
case RIGHT: snake[0][0] += UNIT; break;
}
}
private void eatFood() {
if (snake[0][0] == foodX && snake[0][1] == foodY) {
snakeLen++; // 增長
generateFood();
}
}
private void generateFood() {
while (true) {
// 對齊網格:隨機數取模後乘回 UNIT
foodX = Math.abs(rnd.nextInt() % (width - UNIT + 1)) / UNIT * UNIT;
foodY = Math.abs(rnd.nextInt() % (height - UNIT + 1)) / UNIT * UNIT;
// 檢查不與蛇身重疊
boolean ok = true;
for (int i = 0; i < snakeLen; i++)
if (foodX == snake[i][0] && foodY == snake[i][1]) { ok = false; break; }
if (ok) break;
}
}
private boolean isGameOver() {
// 1. 撞牆
if (snake[0][0] < 0 || snake[0][0] > width - UNIT ||
snake[0][1] < 0 || snake[0][1] > height - UNIT)
return true;
// 2. 撞自己 (從第 4 節開始檢查,前 3 段不可能自撞)
for (int i = 4; i < snakeLen; i++)
if (snake[0][0] == snake[i][0] && snake[0][1] == snake[i][1])
return true;
return false;
}
public void keyPressed(int keyCode) {
int action = getGameAction(keyCode); // 映射為邏輯鍵 UP/DOWN/LEFT/RIGHT/FIRE
switch (action) {
case UP: if (dir != DOWN) dir = UP; break;
case DOWN: if (dir != UP) dir = DOWN; break;
case LEFT: if (dir != RIGHT) dir = LEFT; break;
case RIGHT: if (dir != LEFT) dir = RIGHT; break;
case FIRE: paused = !paused; break; // 暫停/繼續
}
}
使用 getGameAction() 吸收鍵碼差異,防止 180 度反向導致瞬間自殺。
public void run() {
try {
while (running) {
long start = System.currentTimeMillis();
if (!paused) {
eatFood();
move(dir);
if (isGameOver()) break; // 結束迴路
foodVisible = !foodVisible; // 食物閃爍
}
repaint();
long elapsed = System.currentTimeMillis() - start;
int sleep = SLEEP - (int)elapsed;
if (sleep > 0) Thread.sleep(sleep);
}
} catch (Exception e) {}
// 遊戲結束處理可在這裡加入 Game Over 畫面
}
採用 時間補償法:測量邏輯耗時,動態睡眠剩餘時間,維持穩定 ~3.3 FPS (300ms/幀)。
UNIT 對齊,邏輯清晰。getGameAction() 屏蔽硬體差異。此代碼為 J2ME 遊戲入門最佳範例,涵蓋迴路、輸入、繪圖、邏輯、狀態管理所有核心要素。