java中使用swing开发的一个贪吃蛇的窗体小游戏

        心血来潮,想试试ChatGPT写代码的能力,于是让它写了一个贪吃蛇的小游戏,结果太粗糙了,还有一些bug,体验非常差。

        ChatGPT生成的初始版本的代码只有200行左右,且第一个版本是没有难度选择功能的,于是自己动手简单改了改,解决了一些已知的bug,完善了一下操作,得到下面这个400多行的,勉强可以玩的版本(虽然代码还是很混乱)。先贴几张界面截图:

(这是主界面,有4个难度等级可选)

(这是游戏中的界面,顶部有得分,还有按Q结束游戏的提示,游戏过程中可以按空格暂停)

(这是游戏结束的界面,显示最终得分,有重新开始、关闭程序、返回主界面3个按钮)

        

        下面贴出代码,直接运行main方法就可以启动游戏:

package com.zqm.demo;

import javax.swing.Timer;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.List;
import java.util.*;

/**
 * 贪吃蛇游戏类,继承自JPanel并实现ActionListener接口
 */
public class SnakeGame extends JPanel implements ActionListener {
    public static final String GAME_NAME = "Snake Game"; // 游戏名称
    public static final int WINDOW_WIDTH = 350; // 窗口宽度
    public static final int WINDOW_HEIGHT = 350; // 窗口高度
    public static final int BUTTON_WIDTH = 100; // 按钮宽度
    public static final int BUTTON_HEIGHT = 30; // 按钮高度
    private final int UNIT_SIZE = 10; // 蛇移动的步伐
    private final int GAME_UNITS = (WINDOW_WIDTH * WINDOW_HEIGHT) / UNIT_SIZE;
    private int bodyParts; // 蛇身长度,初始为6,每吃一个苹果加1
    private int score = 0; // 得分,即吃到的苹果数量
    private final int snakeX[] = new int[GAME_UNITS]; // 蛇头和各个身体部分的X坐标
    private final int snakeY[] = new int[GAME_UNITS]; // 蛇头和各个身体部分的Y坐标
    private int appleX; // 苹果的X坐标
    private int appleY; // 苹果的Y坐标
    private char direction = 'R'; // 初始方向设为向右
    private boolean isRunning = false; // 当前运行状态,是否在运行中、已结束
    private boolean isPaused = false; // 当前游戏状态,是否暂停中
    private boolean continueFlag = false; // 继续状态
    private Timer timer;
    private Random random = new Random();
    private JButton restartButton; // 重新开始按钮
    private JButton closeButton; // 关闭按钮
    private JButton backButton; // 返回按钮
    private int gameSpeed; // 蛇移动的速度,根据所选难度级别来定

    // 游戏中可以使用的按键,上、下、左、右、空格(用于暂停/继续)、Q(用于退出)
    private List<Integer> acceptKeys = Arrays.asList(KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT, KeyEvent.VK_UP, KeyEvent.VK_DOWN, KeyEvent.VK_SPACE, KeyEvent.VK_Q);

    // 游戏难度级别定义,Map中key为游戏难度名称,value为难度等级对应的移动速度,毫秒
    public static final Map<String, Integer> difficultyMap = new LinkedHashMap<String, Integer>() {{
        put("Easy", 250);
        put("Medium", 150);
        put("Hard", 100);
        put("Crazy", 50);
    }};

    // 蛇移动的方向定义,分别是上、下、左、右
    private static final char[] directions = {'U', 'D', 'L', 'R'};

    /**
     * 构造方法,初始化游戏界面并启动游戏
     */
    public SnakeGame(String difficulty) {
        // 根据难度参数设置蛇移动的速度,毫秒,数值越大速度越慢
        gameSpeed = difficultyMap.get(difficulty);

        this.setPreferredSize(new Dimension(WINDOW_WIDTH, WINDOW_HEIGHT));
        this.setBackground(Color.black);
        this.setFocusable(true);
        this.addKeyListener(new MyKeyAdapter());
        startGame();
    }

    /**
     * 启动游戏的方法
     */
    public void startGame() {
        isRunning = true; // 运行中
        isPaused = false; // 未暂停
        score = 0;
        bodyParts = 6;
        resetSnakeAndApplePosition();

        spawnApple();
        timer = new Timer(gameSpeed, this);
        timer.start();
    }

    /**
     * 生成苹果的方法
     */
    public void spawnApple() {
        appleX = random.nextInt(WINDOW_WIDTH / UNIT_SIZE) * UNIT_SIZE;
        appleY = random.nextInt(WINDOW_HEIGHT / UNIT_SIZE) * UNIT_SIZE;

        // 苹果的Y轴不能在Score区域以及分界线以上,防止苹果在Score区域以及分界线以上位置
        if (appleY - UNIT_SIZE < this.getFont().getSize() + 20) {
            appleY = UNIT_SIZE + this.getFont().getSize() + 20;
        }
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if (isRunning) {
            move();
            checkApple(); // 检查是否吃到苹果
            checkCollisions(); // 检查是否撞墙
        }
        repaint();
    }

    /**
     * 绘制组件的方法
     */
    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        draw(g);
    }

    /**
     * 绘制元素的方法
     */
    public void draw(Graphics g) {
        if (isRunning) {
            // 绘制苹果
            g.setColor(Color.red);
            g.fillOval(appleX, appleY, UNIT_SIZE, UNIT_SIZE); // 苹果,圆形
//            g.fillRect(appleX, appleY, UNIT_SIZE, UNIT_SIZE); // 苹果,矩形

            // 绘制蛇
            for (int i = 0; i < bodyParts; i++) {
                if (i == 0) {
                    g.setColor(Color.green); // 蛇头显示为绿色
                } else {
                    g.setColor(new Color(45, 180, 0)); // 蛇身显示为不同绿色
                }
                g.fillRect(snakeX[i], snakeY[i], UNIT_SIZE, UNIT_SIZE);
            }

            // 显示分数
            g.setColor(Color.red);
            g.setFont(new Font("Arial", Font.BOLD, 20));
            FontMetrics metrics = getFontMetrics(g.getFont());
            String scoreText = "Score: " + score + "【按”Q“结束游戏】";
            int scoreX = (WINDOW_WIDTH - metrics.stringWidth(scoreText)) / 2;
            int scoreY = g.getFont().getSize() + 10; // 将"Score"文本向下移动一定距离
            g.drawString(scoreText, scoreX, scoreY);

            // 绘制分界线
            g.setColor(Color.white); // 设置分界线颜色为白色
            g.drawLine(0, scoreY + 10, WINDOW_WIDTH, scoreY + 10); // 绘制分界线,位置在Score文本下方5个像素
        } else {
            gameOver(g);
        }
    }

    /**
     * 移动蛇的方法
     */
    public void move() {
        for (int i = bodyParts; i > 0; i--) {
            snakeX[i] = snakeX[i - 1];
            snakeY[i] = snakeY[i - 1];
        }
        switch (direction) {
            case 'U':
                if (snakeY[0] - UNIT_SIZE < this.getFont().getSize() + 20) { // 判断蛇头位置是否在Score区域以及分界线以上,防止蛇头向上走到Score区域以及分界线以上位置
                    isRunning = false; // 已结束
                } else {
                    snakeY[0] = snakeY[0] - UNIT_SIZE;
                }
                break;
            case 'D':
                snakeY[0] = snakeY[0] + UNIT_SIZE;
                break;
            case 'L':
                snakeX[0] = snakeX[0] - UNIT_SIZE;
                break;
            case 'R':
                snakeX[0] = snakeX[0] + UNIT_SIZE;
                break;
        }
    }

    /**
     * 检查是否吃到苹果的方法
     */
    public void checkApple() {
        if (snakeX[0] == appleX && snakeY[0] == appleY) {
            bodyParts++;
            score++;
            spawnApple();
        }
    }

    /**
     * 检查碰撞的方法(与墙壁或自身)
     */
    public void checkCollisions() {
        // 检查蛇头是否与身体相撞
        for (int i = bodyParts; i > 0; i--) {
            if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]) {
                isRunning = false; // 已结束
                break;
            }
        }
        // 检查蛇头是否触碰边界
        if (isRunning && (snakeX[0] < 0 || snakeX[0] >= WINDOW_WIDTH || snakeY[0] < 0 || snakeY[0] >= WINDOW_HEIGHT)) {
            isRunning = false; // 已结束
        }
        if (!isRunning) {
            timer.stop();
        }
    }

    /**
     * 显示游戏结束信息的方法
     */
    public void gameOver(Graphics g) {
        g.setColor(Color.red);
        g.setFont(new Font("Arial", Font.BOLD, 40));
        FontMetrics metrics = getFontMetrics(g.getFont());
        String gameOver = "Game Over !!!";
        g.drawString(gameOver, (WINDOW_WIDTH - metrics.stringWidth(gameOver)) / 2, WINDOW_HEIGHT / 4);
        String scoreString = "Score: " + score;
        g.drawString(scoreString, (WINDOW_WIDTH - metrics.stringWidth(scoreString)) / 2, WINDOW_HEIGHT / 4 + 50);

        // 创建重新开始按钮
        if (restartButton == null) {
            restartButton = new JButton("Restart");
            restartButton.setBounds((WINDOW_WIDTH - 100) / 2, WINDOW_HEIGHT / 4 + 100, BUTTON_WIDTH, BUTTON_HEIGHT);
            restartButton.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    startGame();
                    resetSnakeAndApplePosition();
                    restartButton.setVisible(false);
                    closeButton.setVisible(false);
                    backButton.setVisible(false);
                }
            });
            add(restartButton);
        } else {
            restartButton.setBounds((WINDOW_WIDTH - 100) / 2, WINDOW_HEIGHT / 4 + 100, BUTTON_WIDTH, BUTTON_HEIGHT); // 重置下按钮位置
            restartButton.setVisible(true);
        }

        // 创建关闭按钮
        if (closeButton == null) {
            closeButton = new JButton("Close");
            closeButton.setBounds((WINDOW_WIDTH - 100) / 2, WINDOW_HEIGHT / 4 + 140, BUTTON_WIDTH, BUTTON_HEIGHT);
            closeButton.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    System.exit(0);
                }
            });
            add(closeButton);
        } else {
            closeButton.setBounds((WINDOW_WIDTH - 100) / 2, WINDOW_HEIGHT / 4 + 140, BUTTON_WIDTH, BUTTON_HEIGHT); // 重置下按钮位置
            closeButton.setVisible(true);
        }

        // 创建返回按钮
        if (backButton == null) {
            backButton = new JButton("Back");
            backButton.setBounds((WINDOW_WIDTH - 100) / 2, WINDOW_HEIGHT / 4 + 180, BUTTON_WIDTH, BUTTON_HEIGHT);
            backButton.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    JFrame difficultyFrame = new JFrame(GAME_NAME);
                    GameDifficultySelection selectionPanel = new GameDifficultySelection();
                    difficultyFrame.add(selectionPanel);
                    difficultyFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    difficultyFrame.setSize(SnakeGame.WINDOW_WIDTH, SnakeGame.WINDOW_HEIGHT); // 设置窗口大小
                    difficultyFrame.setLocationRelativeTo(null);
                    difficultyFrame.setVisible(true);

                    // 关闭当前的 SnakeGame 窗体
                    SwingUtilities.getWindowAncestor(SnakeGame.this).dispose();
                }
            });
            add(backButton);
        } else {
            backButton.setBounds((WINDOW_WIDTH - 100) / 2, WINDOW_HEIGHT / 4 + 180, BUTTON_WIDTH, BUTTON_HEIGHT); // 重置下按钮位置
            backButton.setVisible(true);
        }
    }

    /**
     * 重置蛇和苹果的位置以及蛇移动的方向
     */
    public void resetSnakeAndApplePosition() {
        for (int i = 1; i < bodyParts; i++) {
            snakeX[i] = 0; // 将蛇身位置重置为0,这样它们不会继续显示在界面上
            snakeY[i] = 0;
        }

        // 重新设置蛇的移动方向为随机方向
        direction = directions[random.nextInt(directions.length)];

        spawnApple();

        snakeX[0] = random.nextInt(WINDOW_WIDTH / UNIT_SIZE) * UNIT_SIZE;
        snakeY[0] = random.nextInt(WINDOW_HEIGHT / UNIT_SIZE) * UNIT_SIZE;

        // 蛇头的Y轴不能在Score区域以及分界线以上,防止蛇头向上走到Score区域以及分界线以上位置
        if (snakeY[0] - UNIT_SIZE < this.getFont().getSize() + 20) {
            snakeY[0] = UNIT_SIZE + this.getFont().getSize() + 20;
        }

        // 蛇的位置不要太靠近上下左右边沿,防止一出现就撞墙,距离边界小于1/3就强制重置为1/3
        if (direction == 'U') { // 向上,距离顶部不能小于窗体高度的1/3
            if (snakeY[0] < WINDOW_HEIGHT / 3) {
                snakeY[0] = WINDOW_HEIGHT / 3;
            }
        } else if (direction == 'D') { // 向下,距离底部不能小于窗体高度的1/3,即距离顶部不能大于窗体高度的2/3
            if (snakeY[0] > (WINDOW_HEIGHT / 3) * 2) {
                snakeY[0] = (WINDOW_HEIGHT / 3) * 2;
            }
        } else if (direction == 'L') { // 向左,距离左边不能小于窗体宽度的1/3
            if (snakeX[0] < WINDOW_WIDTH / 3) {
                snakeX[0] = WINDOW_WIDTH / 3;
            }
        } else if (direction == 'R') { // 向右,距离右边不能小于窗体高度的1/3,即离距左部不能大于窗体高度的2/3
            if (snakeX[0] > (WINDOW_WIDTH / 3) * 2) {
                snakeX[0] = (WINDOW_WIDTH / 3) * 2;
            }
        }
    }

    /**
     * 键盘事件处理类
     */
    public class MyKeyAdapter extends KeyAdapter {
        @Override
        public void keyPressed(KeyEvent e) {

            // 只接受acceptKeys指定的按键
            if (!acceptKeys.contains(e.getKeyCode())) {
                return;
            }

            // 如果当前暂停状态,按任意键都要继续开始
            if (isPaused) {
                isPaused = false;
                timer.start();

                // 标识本次是暂停状态下,按任意键继续的操作,标识成true以免在switch中对SPACE键的处理中又把isPaused重新置为true了
                continueFlag = true;
            }

            // R/L/D/U四个方向的按键,不能向反方向移动
            switch (e.getKeyCode()) {
                case KeyEvent.VK_LEFT:
                    if (direction != 'R') {
                        direction = 'L';
                    }
                    break;
                case KeyEvent.VK_RIGHT:
                    if (direction != 'L') {
                        direction = 'R';
                    }
                    break;
                case KeyEvent.VK_UP:
                    if (direction != 'D') {
                        direction = 'U';
                    }
                    break;
                case KeyEvent.VK_DOWN:
                    if (direction != 'U') {
                        direction = 'D';
                    }
                    break;
                case KeyEvent.VK_Q: // Q键退出游戏
                    isRunning = false; // 设置游戏状态为结束
                    timer.stop(); // 停止计时器
                    repaint(); // 更新界面,显示 "Game Over" 界面
                    break;
                case KeyEvent.VK_SPACE: // 空格暂停游戏:此处只需要处理未暂停状态下按空格进行暂停,已经暂停状态下处理已经在最前面任意键继续处理过了
                    if (!isPaused && !continueFlag) {
                        isPaused = true;
                        timer.stop();
                    }
                    break;
            }

            if (continueFlag) { // 将continueFlag重置为false,以便不影响下次执行
                continueFlag = false;
            }
        }
    }

    /**
     * 游戏难度选择窗口
     */
    public static class GameDifficultySelection extends JPanel {
        public GameDifficultySelection() {
            setLayout(null); // 使用绝对布局

            JLabel titleLabel = new JLabel("Select Game Difficulty");
            titleLabel.setBounds(Double.valueOf(1d * WINDOW_WIDTH / 2 - 150 / 2).intValue(), 50, 150, BUTTON_HEIGHT);
            add(titleLabel);

            int yPosition = 100;
            for (Map.Entry<String, Integer> entry : difficultyMap.entrySet()) {
                String difficultyName = entry.getKey();
                JButton button = new JButton(difficultyName);
                button.setBounds(Double.valueOf(1d * WINDOW_WIDTH / 2 - BUTTON_WIDTH / 2).intValue(), yPosition, BUTTON_WIDTH, BUTTON_HEIGHT);

                button.addActionListener(e -> {
                    startGameWithDifficulty(difficultyName);
                    SwingUtilities.getWindowAncestor(this).dispose(); // 关闭GameDifficultySelection窗体
                });

                add(button);
                yPosition += BUTTON_HEIGHT + 15; // 按钮之间的间距为50
            }
        }

        /**
         * 指定游戏难度来启动游戏
         * @param difficulty 游戏难度
         */
        private void startGameWithDifficulty(String difficulty) {
            // 根据选择的难度启动对应难度的贪吃蛇游戏
            SnakeGame game = new SnakeGame(difficulty);
            JFrame frame = new JFrame(GAME_NAME + " - " + difficulty);
            frame.add(game);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setResizable(false); // 禁止调整窗体大小,影响游戏的显示效果
            frame.setVisible(true);
        }
    }

    /**
     * 主方法,创建并显示游戏窗口
     */
    public static void main(String[] args) {
//        JFrame frame = new JFrame("Snake Game");
//        SnakeGame game = new SnakeGame("Easy");
//        frame.add(game);
//        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//        frame.pack();
//        frame.setLocationRelativeTo(null);
//        frame.setVisible(true);

        JFrame frame = new JFrame(GAME_NAME);
        GameDifficultySelection selectionPanel = new GameDifficultySelection();
        frame.add(selectionPanel);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(SnakeGame.WINDOW_WIDTH, SnakeGame.WINDOW_HEIGHT); // 设置窗口大小
        frame.setLocationRelativeTo(null);
        frame.setResizable(false); // 禁止调整窗体大小,影响游戏的显示效果
        frame.setVisible(true);

    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值