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

Java Swing 實作貪吃蛇遊戲:完整原始碼與架構解析

     編輯:Java 相關知識

前言

貪吃蛇是經典程式設計練習題,涵蓋 GUI 事件處理、遊戲迴圈、資料結構(佇列/鏈表)、碰撞偵測等核心概念。本文提供基於 Java Swing 的完整實作,並解析四大核心類別職責與關鍵演算法。

一、專案結構

snake/
├── Frame.java      // 主視窗、鍵盤監聽、選單列
├── Launch.java     // 遊戲主迴圈 (TimerTask)、邏輯判斷
├── Panel.java      // 繪圖面板 (JPanel paintComponent)
└── SnakeBody.java  // 蛇身/食物節點資料模型

二、核心類別詳解

1. SnakeBody.java - 不可變節點模型

package snake;

public class SnakeBody {
    int x, y; // 格子座標

    public SnakeBody() { this(0, 0); }
    public SnakeBody(int x, int y) { this.x = x; this.y = y; }
    public SnakeBody(SnakeBody other) { this(other.x, other.y); }

    // 方向移動:直接修改座標 (可改為回傳新實例以符合不可變原則)
    public void turnUp()    { y--; }
    public void turnDown()  { y++; }
    public void turnLeft()  { x--; }
    public void turnRight() { x++; }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (!(obj instanceof SnakeBody)) return false;
        SnakeBody o = (SnakeBody) obj;
        return x == o.x && y == o.y;
    }
}

2. Panel.java - 純渲染元件

package snake;

import javax.swing.*;
import java.awt.*;
import java.util.LinkedList;

public class Panel extends JPanel {
    static final int GRID_SIZE = 10; // 10x10 格子
    LinkedList<SnakeBody> snake = new LinkedList<>();
    SnakeBody food = new SnakeBody(-99, -99); // 初始隱藏

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        int w = getWidth(), h = getHeight();
        int cw = w / GRID_SIZE, ch = h / GRID_SIZE;

        // 繪製蛇身
        for (SnakeBody b : snake) {
            g.setColor(Color.ORANGE);
            g.fillRect(b.x * cw, b.y * ch, cw, ch);
            g.setColor(Color.BLACK);
            g.drawRect(b.x * cw, b.y * ch, cw, ch);
        }
        // 繪製食物
        g.setColor(Color.RED);
        g.fillRect(food.x * cw, food.y * ch, cw, ch);
    }

    public void reset() { snake.clear(); }
}

3. Frame.java - 視窗與輸入處理

package snake;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class Frame extends JFrame implements KeyListener {
    Panel panel = new Panel();
    volatile char direction = 'd'; // 初始向右
    volatile boolean alive = true, paused = false;

    public Frame() {
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setSize(400, 400);
        setTitle("貪吃蛇 - Swing 版");
        add(panel);
        addKeyListener(this);
        setJMenuBar(createMenuBar());
        setLocationRelativeTo(null);
        setVisible(true);
    }

    private JMenuBar createMenuBar() {
        JMenuBar mb = new JMenuBar();
        JMenu menu = new JMenu("遊戲");
        JMenuItem newGame = new JMenuItem("新遊戲 (R)");
        JMenuItem pause = new JMenuItem("暫停/繼續 (P)");
        newGame.addActionListener(e -> resetGame());
        pause.addActionListener(e -> paused = !paused);
        menu.add(newGame); menu.add(pause);
        mb.add(menu);
        return mb;
    }

    public void resetGame() {
        panel.reset();
        alive = true;
        direction = 'd';
        // 重置邏輯由 Launch 初始化
    }

    @Override public void keyTyped(KeyEvent e) {}
    @Override public void keyReleased(KeyEvent e) {}

    @Override
    public void keyPressed(KeyEvent e) {
        // 防止 180 度反向導致瞬間自殺
        switch (e.getKeyCode()) {
            case KeyEvent.VK_UP:    if (direction != 's') direction = 'w'; break;
            case KeyEvent.VK_DOWN:  if (direction != 'w') direction = 's'; break;
            case KeyEvent.VK_LEFT:  if (direction != 'd') direction = 'a'; break;
            case KeyEvent.VK_RIGHT: if (direction != 'a') direction = 'd'; break;
            case KeyEvent.VK_P: paused = !paused; break;
            case KeyEvent.VK_R: resetGame(); break;
        }
    }
}

4. Launch.java - 遊戲邏輯核心 (TimerTask)

package snake;

import java.util.*;

public class Launch extends TimerTask {
    Frame frame;
    static final int DELAY_MS = 200; // 速度

    public Launch(Frame frame) { this.frame = frame; }

    @Override
    public void run() {
        if (frame.panel.snake.isEmpty()) initGame();

        if (frame.alive && !frame.paused) {
            boolean dead = moveSnake();
            frame.alive = !dead;
            frame.repaint(); // 觸發 Panel 重繪
        }
        if (checkWallCrash()) frame.alive = false;
    }

    private void initGame() {
        frame.panel.snake.add(new SnakeBody(5, 5));
        spawnFood();
    }

    private void spawnFood() {
        Random r = new Random();
        SnakeBody food;
        do {
            food = new SnakeBody(r.nextInt(Panel.GRID_SIZE - 2) + 1,
                                 r.nextInt(Panel.GRID_SIZE - 2) + 1);
        } while (frame.panel.snake.contains(food)); // 避免生成在蛇身上
        frame.panel.food = food;
    }

    private boolean moveSnake() {
        SnakeBody head = new SnakeBody(frame.panel.snake.getFirst());
        frame.panel.snake.removeLast(); // 尾巴縮回 (未吃食物時)

        // 依方向移動頭部
        switch (frame.direction) {
            case 'w': head.turnUp(); break;
            case 's': head.turnDown(); break;
            case 'a': head.turnLeft(); break;
            case 'd': head.turnRight(); break;
        }

        // 自撞檢測
        if (frame.panel.snake.contains(head)) return true;

        frame.panel.snake.addFirst(head);

        // 吃食物
        if (head.equals(frame.panel.food)) {
            frame.panel.snake.addFirst(new SnakeBody(head)); // 身體增長
            spawnFood();
        }
        return false;
    }

    private boolean checkWallCrash() {
        SnakeBody h = frame.panel.snake.getFirst();
        return h.x < 0 || h.x >= Panel.GRID_SIZE || h.y < 0 || h.y >= Panel.GRID_SIZE;
    }

    public static void main(String[] args) {
        Frame f = new Frame();
        Timer timer = new Timer("GameLoop");
        timer.schedule(new Launch(f), 0, DELAY_MS);
    }
}

三、架構亮點與改進方向

面向現狀建議改進
執行緒安全Swing 元件在 TimerTask 執行緒修改資料使用 SwingUtilities.invokeLater 或改用 javax.swing.Timer
狀態管理方向、生死旗標為 volatile封裝為 GameState 物件,集中管理
擴充性硬編碼 10x10 格子支援動態格子大小、關卡、障礙物
渲染優化全域重繪僅重繪變動區域 (repaint(x,y,w,h))

四、總結

此實作展示了 MVC 變體:Frame (Controller/View)、Launch (Model/Controller)、Panel (View)、SnakeBody (Model)。理解職責分離與遊戲迴圈設計,是進階開發 2D 遊戲的基石。歡迎 Fork 擴充計分、關卡、AI 對戰等功能!

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