DOJA (DoCoMo Java) 是 NTT DoCoMo 定制的 J2ME 配置,針對日本功能機優化。本項目實現經典貪食蛇遊戲,採用標準 MIDP 風格架構:MIDlet 入口 → Canvas 繪製/事件 → 遊戲邏輯分離,包含五大核心類。
GreedSnake (MIDlet/主控制器)
│
├─ Grid (Canvas/繪製視圖)
│ │
│ ├─ Snake (遊戲實體:蛇)
│ │ └─ Node[] (身體節點)
│ │
│ └─ Food (遊戲實體:食物)
│
└─ Node (數據結構:座標點)
public class GreedSnake extends IApplication implements Runnable {
// 遊戲常量
public static final int GRID_WIDTH = 20;
public static final int GRID_HEIGHT = 24;
public static final int NODE_SIZE = 10; // 像素大小
// 組件引用
private Grid grid;
private Food food;
private Snake snake = new Snake();
// 遊戲狀態
private boolean running; // true=遊戲結束
private int score, level;
private int count, countMove, timeInterval = 200; // 速度控制
// 生命週期
public void start() {
reset();
grid = new Grid(GRID_WIDTH, GRID_HEIGHT, this);
Display.setCurrent(grid);
new Thread(this).start();
}
private void reset() { /* 重置所有狀態變量 */ }
// 遊戲主循環
public void run() {
food = grid.addFood();
grid.addSnake(snake);
grid.repaint();
for (;;) {
if (food == null) { food = grid.addFood(); continue; }
try { Thread.sleep(timeInterval); } catch (Exception e) { break; }
if (!running) {
snake.move();
Node head = snake.getHead();
// 碰撞檢測: 牆壁 或 自身
if (grid.overlapsWith(head) || snake.ptInSnake(head)) {
running = true; continue;
}
// 吃食物
if (head.x == food.getX() && head.y == food.getY()) {
int scoreGet = (10000 - 200 * countMove) / timeInterval;
score += Math.max(scoreGet, 0);
countMove = 0;
snake.eatFood(food);
grid.reduceFood(food);
food = null;
if (++count % 5 == 0) level++; // 每5個食物升級
} else {
countMove++;
}
grid.repaint();
} else {
grid.setGameOver();
break;
}
}
grid.repaint(); // 最後一幀顯示 Game Over
}
// 按鍵處理
public void keyPressed(int key) {
switch (key) {
case Display.KEY_SELECT: grid.setGameOver(); break;
case Display.KEY_LEFT: snake.setDirection(Snake.LEFT); break;
case Display.KEY_RIGHT: snake.setDirection(Snake.RIGHT); break;
case Display.KEY_UP: snake.setDirection(Snake.UP); break;
case Display.KEY_DOWN: snake.setDirection(Snake.DOWN); break;
}
}
// Getters...
}
public class Grid extends Canvas {
private int width, height;
private GreedSnake listener;
private Snake snake;
private Food food;
private boolean blDisplay, gameOver;
private Font scoreFont, gameOverFont;
public Grid(int w, int h, GreedSnake l) {
this.width = w; this.height = h; this.listener = l; reset();
setSoftLabel(Frame.SOFT_KEY_1, "New");
setSoftLabel(Frame.SOFT_KEY_2, "Quit");
scoreFont = Font.getFont(Font.FACE_MONOSPACE | Font.SIZE_SMALL);
gameOverFont = Font.getFont(Font.FACE_PROPORTIONAL | Font.SIZE_LARGE | Font.STYLE_BOLD);
}
// 食物生成: 避開蛇身
public Food addFood() {
blDisplay = false;
int gs = GreedSnake.GRID_WIDTH;
if (snake == null) return this.food = Food.createRandomFood(gs, gs);
for (;;) {
this.food = Food.createRandomFood(gs, gs);
if (!snake.ptInSnake(new Node(food.getX(), food.getY()))) break;
}
return this.food;
}
public void reduceFood(Food f) { blDisplay = true; } // 暫停顯示
// 繪製核心
public synchronized void paint(Graphics g) {
g.lock();
g.setColor(Graphics.BLACK);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Graphics.WHITE);
g.drawLine(0, 0, 0, getHeight()); // 左邊框
g.drawLine(201, 0, 201, getHeight()); // 右邊框
g.drawLine(0, getHeight(), 201, getHeight()); // 底邊框
if (food != null && !blDisplay) food.paint(g);
if (snake != null) snake.paint(g);
// 側邊欄分數/等級
g.setColor(Graphics.SILVER);
g.setFont(scoreFont);
g.drawString("LEVEL", 203, 40);
g.drawString(String.valueOf(listener.getLevel()), 220, 70);
g.drawString("SCORE", 202, 100);
g.drawString(format(listener.getScore()), 208, 130);
if (gameOver) {
g.setColor(0x00ffff);
g.setFont(gameOverFont);
g.drawString("GAME", 70, 110);
g.drawString("OVER", 70, 140);
}
g.unlock(true);
}
private String format(int n) { // 4位補零
String s = String.valueOf(n);
return "0000".substring(0, 4 - s.length()) + s;
}
public void processEvent(int type, int param) {
if (type == Display.KEY_PRESSED_EVENT) listener.keyPressed(param);
}
}
public class Snake {
public static final int UP=1, DOWN=3, LEFT=2, RIGHT=4;
private int direction;
private Vector snakeData = new Vector(); // Node 序列
private static Random rnd = new Random();
public Snake() { // 初始 5 節,水平向左
for (int i=4; i>=0; i--) snakeData.addElement(new Node(i, 0));
setDirection(DOWN);
}
public void setDirection(int dir) {
if (direction % 2 != dir % 2) direction = dir; // 禁止 180° 轉向
}
public Node getHead() { return (Node) snakeData.elementAt(0); }
public Node getNextHead() { // 預測下一幀頭部位置
Node n = new Node(getHead().x, getHead().y);
switch (direction) {
case UP: n.y--; break;
case DOWN: n.y++; break;
case LEFT: n.x--; break;
case RIGHT: n.x++; break;
}
return n;
}
public void move() { // 整體位移
Node newHead = getNextHead();
for (int i = snakeData.size()-1; i > 0; i--) {
Node cur = (Node) snakeData.elementAt(i);
Node prev = (Node) snakeData.elementAt(i-1);
cur.x = prev.x; cur.y = prev.y;
}
Node head = (Node) snakeData.elementAt(0);
head.x = newHead.x; head.y = newHead.y;
}
public void addNode() { // 吃食物增長
Node tail = (Node) snakeData.lastElement();
snakeData.addElement(new Node(tail.x, tail.y));
}
public void eatFood(Food f) { addNode(); }
public boolean ptInSnake(Node n) { // 自身碰撞檢測 (排除頭部)
for (int i=1; i
public class Food {
private int x, y;
private static Random rnd = new Random();
private Food(int x, int y) { this.x = x; this.y = y; }
public static Food createRandomFood(int w, int h) {
return new Food(Math.abs(rnd.nextInt() % w), Math.abs(rnd.nextInt() % h));
}
public int getX() { return x; }
public int getY() { return y; }
public synchronized void paint(Graphics g) {
int gn = GreedSnake.NODE_SIZE;
g.setColor(0x0000ff);
g.fillRect(x * gn, y * gn, gn, gn);
}
}
public class Node { public int x, y; public Node(int x, int y) { this.x=x; this.y=y; } }
| 特性 | 實現方式 |
|---|---|
| 繼承體系 | IApplication (DOJA 入口) + Runnable (遊戲循環線程) |
| 畫布模型 | Canvas 繼承,paint(Graphics) 同步繪製,processEvent 事件分發 |
| 軟鍵映射 | setSoftLabel(Frame.SOFT_KEY_1/2, ...) 適配日系手機雙軟鍵 |
| 雙緩衝 | Graphics.lock()/unlock(true) 手動控制,減少閃爍 |
| 隨機數 | java.util.Random 標準 API,DOJA 完整支持 |
| 向量容器 | Vector 存儲蛇身節點,J2ME 標準集合 |
GameState 枚舉 (READY/RUNNING/PAUSED/OVER) 替換布爾標誌RecordStore 存儲歷史最高分Manager.createPlayer() 播放吃食/撞牆/升級音效Display.getWidth()/getHeight() 計算網格大小這個 DOJA 貪食蛇實現展示了 J2ME 遊戲開發的標準模式:主控制器調度、Canvas 視圖渲染、實體類封裝邏輯、輕量數據結構。代碼結構清晰、註釋完整、邊界處理得當 (禁止反向、食物避開蛇身、邊界檢測),是學習 DOJA/J2ME 遊戲開發的優秀範例。