貪食蛇是經典資料結構教學案例。本文展示如何使用自訂雙向鏈表管理蛇身節點,搭配 Swing 繪製遊戲畫面,實作完整邏輯:移動、吃食、成長、撞牆/撞身判定、鍵盤控制與遊戲迴圈。程式碼結構清晰,適合資料結構與 GUI 程式設計雙重練習。
package snake;
public class SnakeNode {
private int x, y; // 格子座標
private SnakeNode next; // 指向下一節(尾部方向)
private SnakeNode ahead; // 指向上一節(頭部方向)
public SnakeNode() {}
public SnakeNode(int x, int y) { this.x = x; this.y = y; }
// Getter/Setter 省略...
public int getX() { return x; }
public void setX(int x) { this.x = x; }
public int getY() { return y; }
public void setY(int y) { this.y = y; }
public SnakeNode getNext() { return next; }
public void setNext(SnakeNode next) { this.next = next; }
public SnakeNode getAhead() { return ahead; }
public void setAhead(SnakeNode ahead) { this.ahead = ahead; }
}
設計要點:雙向鏈表使得從尾部向頭部移動(位移)與在尾部新增節點(成長)皆為 O(1) 操作,無需陣列搬移。
private static final int ROWS = 60, COLS = 80;
private static final int UP = 1, RIGHT = 2, DOWN = 3, LEFT = 4;
private static int direction = RIGHT;
private static boolean isEat = false;
private static int tailX, tailY; // 記錄尾部座標供成長用
private static SnakeNode head = new SnakeNode(); // 頭節點
private static SnakeNode tail = head; // 尾節點(初始同頭)
private static SnakeNode food = new SnakeNode();
private static JLabel[] cells = new JLabel[ROWS * COLS]; // 格子映射 UI 元件
public static void main(String[] args) {
// 初始化蛇頭隨機位置
head.setX(new Random().nextInt(ROWS - 2) + 1);
head.setY(new Random().nextInt(COLS - 2) + 1);
new Snake(); // 建構子啟動 UI
food = generateFood();
while (true) {
try {
move(); // 1. 移動蛇身
// 2. 檢查吃食
if (food.getX() == head.getX() && food.getY() == head.getY()) {
grow(); // 在尾部新增節點
isEat = true;
}
if (isEat) food = generateFood(); // 重新生成食物
// 3. 判定結束
if (checkGameOver()) {
JOptionPane.showMessageDialog(null, "遊戲結束!", "Game Over", JOptionPane.ERROR_MESSAGE);
break;
}
// 4. 繪製:蛇身 + 食物
paintSnakeAndFood();
Thread.sleep(100); // 遊戲速度
// 5. 清除舊畫面(重繪背景)
clearSnakeAndFood();
isEat = false;
} catch (InterruptedException e) { e.printStackTrace(); }
}
}
public static void move() {
if (head == null) return;
tailX = tail.getX(); tailY = tail.getY(); // 記錄舊尾座標
// 從尾部向頭部依次位移:每節取代為前一節座標
SnakeNode cur = tail;
while (cur != null) {
if (cur == head) break;
cur.setX(cur.getAhead().getX());
cur.setY(cur.getAhead().getY());
cur = cur.getAhead();
}
// 移動頭節點
switch (direction) {
case RIGHT: head.setY(head.getY() + 1); break;
case LEFT: head.setY(head.getY() - 1); break;
case UP: head.setX(head.getX() - 1); break;
case DOWN: head.setX(head.getX() + 1); break;
}
}
public static void grow() {
SnakeNode newTail = new SnakeNode(tailX, tailY);
tail.setNext(newTail);
newTail.setAhead(tail);
tail = newTail;
}
public static SnakeNode generateFood() {
while (true) {
int x = new Random().nextInt(ROWS);
int y = new Random().nextInt(COLS);
if (x == 0 || x == ROWS - 1 || y == 0 || y == COLS - 1) continue; // 牆壁
// 檢查是否撞到蛇身
boolean onSnake = false;
for (SnakeNode n = head; n != null; n = n.getNext()) {
if (n.getX() == x && n.getY() == y) { onSnake = true; break; }
}
if (!onSnake) return new SnakeNode(x, y);
}
}
public static boolean checkGameOver() {
// 撞牆
if (head.getX() == 0 || head.getX() == ROWS - 1 ||
head.getY() == 0 || head.getY() == COLS - 1) return true;
// 撞身(從第二節開始檢查)
for (SnakeNode n = head.getNext(); n != null; n = n.getNext()) {
if (head.getX() == n.getX() && head.getY() == n.getY()) return true;
}
return false;
}
KeyboardFocusManager.getCurrentKeyboardFocusManager()
.addKeyEventPostProcessor(e -> {
if (e.getID() != KeyEvent.KEY_PRESSED) return false;
int code = e.getKeyCode();
// 防止 180 度反向:檢查頭節點與第二節相對位置
if (code == KeyEvent.VK_UP && canTurn(UP)) direction = UP;
else if (code == KeyEvent.VK_RIGHT && canTurn(RIGHT)) direction = RIGHT;
else if (code == KeyEvent.VK_DOWN && canTurn(DOWN)) direction = DOWN;
else if (code == KeyEvent.VK_LEFT && canTurn(LEFT)) direction = LEFT;
return true;
});
建構子中以 GridLayout(ROWS, COLS) 鋪設 JLabel 陣列,邊框紅色、內部白色,對應 cells[index] 快速繪製/清除。
image/white.jpg、image/red.jpg、image/black.jpg 放在專案根目錄 image/ 資料夾。Snake.main()。Thread.sleep 時間。Preferences 或檔案持久化。volatile boolean paused 控制主迴圈。雙向鏈表天然契合貪食蛇「頭動尾隨、尾部增長」的動態特性,配合 Swing 輕量 UI,僅 300 行左右即可完成完整遊戲。本專案展示了資料結構選型對演算法簡潔度的決定性影響,是學習 Java 集合原理與 GUI 程式設計的優質實戰素材。