心血来潮,想试试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);
}
}