首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 雙向鏈表實作貪食蛇遊戲完整原始碼教學

     編輯:Java 相關知識

專案簡介

貪食蛇是經典資料結構教學案例。本文展示如何使用自訂雙向鏈表管理蛇身節點,搭配 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) 操作,無需陣列搬移。

主遊戲類別:Snake extends JFrame

關鍵常數與狀態

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;
    });

UI 初始化(網格佈局)

建構子中以 GridLayout(ROWS, COLS) 鋪設 JLabel 陣列,邊框紅色、內部白色,對應 cells[index] 快速繪製/清除。

執行與資源

  1. 準備圖片資源:image/white.jpg、image/red.jpg、image/black.jpg 放在專案根目錄 image/ 資料夾。
  2. 直接執行 Snake.main()。
  3. 方向鍵控制移動,撞牆或撞身結束彈窗。

擴充建議

  • 分數系統:每吃一顆加分,顯示於標題列。
  • 等級/加速:分數達標縮短 Thread.sleep 時間。
  • 高分記錄:使用 Preferences 或檔案持久化。
  • 暫停/繼續:引入 volatile boolean paused 控制主迴圈。
  • 重構為 MVC:分離 Model(蛇/食物/邏輯)、View(繪製)、Controller(輸入/迴圈)。

總結

雙向鏈表天然契合貪食蛇「頭動尾隨、尾部增長」的動態特性,配合 Swing 輕量 UI,僅 300 行左右即可完成完整遊戲。本專案展示了資料結構選型對演算法簡潔度的決定性影響,是學習 Java 集合原理與 GUI 程式設計的優質實戰素材。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved