Java项目实战—数字猜谜游戏设计与实现
一、游戏概述
这是一个基于 Java 图形化界面(GUI)开发的数字猜谜游戏。玩家需要在游戏界面中输入数字,程序会根据与目标数字的比较给出提示,直到玩家猜对为止。游戏还具备设置难度、重置游戏以及记录和展示最佳成绩的功能。
二、运行环境
本游戏需要在安装了 Java 运行时环境(JRE)的计算机上运行。
三、主要功能
- 游戏核心功能:
- 程序会基于设定的难度范围生成一个随机的目标数字,玩家通过图形化界面中的输入框输入猜测数字,程序能判断玩家猜测数字与目标数字的大小关系,并给出相应提示(猜大了、猜小了或者猜对了)。
- 若玩家猜对了数字,程序会记录此次游戏所用的时间,将其作为成绩与历史最佳成绩进行比较,进而更新并保存最佳成绩列表(最多保留5个最佳成绩),同时在界面上展示相应的提示及成绩信息。
- 游戏控制功能:
- 设置难度:玩家可通过点击“设置难度”按钮,在弹出的对话框中输入整数来调整游戏难度,难度值决定了目标数字的生成范围,之后程序会根据新难度重新生成目标数字开启新一轮游戏。
- 重置游戏:点击“重置游戏”按钮,程序会重新生成目标数字,同时将界面上显示的猜测次数、游戏时间等相关信息重置为初始状态,方便玩家重新开始游戏。
- 成绩记录与展示功能:
最佳成绩(游戏用时)会被保存在本地文件“best_scores.txt”中,程序启动时会读取该文件中的数据加载到内存中的最佳成绩列表,每次游戏结束后会根据情况更新这个列表,并实时在界面上展示当前的最佳成绩(用时最短的成绩),若暂无有效成绩则显示“最佳成绩: 无”。
四、游戏界面介绍
- 标题栏:显示游戏的标题“数字猜谜游戏”。
- 输入区域:位于窗口顶部,包含一个文本框和一个“猜测”按钮。玩家在文本框中输入猜测的数字,然后点击“猜测”按钮提交猜测。
- 信息展示区域:在窗口中间,有四个标签分别显示不同信息。
- “结果”标签:初始显示“请输入你的猜测”,在玩家猜测后,会根据猜测结果显示“猜大了,再试试”“猜小了,再试试”或“恭喜你,猜对了!”等提示信息。
- “猜测次数”标签:游戏开始时显示“猜测次数: 0”,每猜测一次,次数会相应增加并在此标签更新显示。
- “游戏时间”标签:初始为“游戏时间: 0 秒”,当玩家猜对数字后,会显示本次游戏所花费的时间。
- “最佳成绩”标签:开始游戏时,如果没有历史最佳成绩数据,显示“最佳成绩: 无”;若有,则显示历史最佳成绩,即最快猜对数字所用的时间。
- 控制按钮区域:在窗口底部,有“重置游戏”和“设置难度”两个按钮。
- “重置游戏”按钮:点击后,游戏会重新生成目标数字,猜测次数和游戏时间重置为 0,结果标签恢复为初始提示。
- “设置难度”按钮:点击后会弹出一个输入对话框,玩家在此输入新的难度范围(整数),确定后游戏将根据新难度重新开始。
五、游戏操作步骤
- 开始游戏:
- 启动游戏后,程序会自动生成一个在默认难度范围(0 - 100)内的目标数字,并显示游戏界面。玩家可以直接在输入框中输入猜测的数字,然后点击“猜测”按钮开始第一轮猜测。
- 猜测数字:
- 在输入框中输入一个整数后,点击“猜测”按钮。程序会将输入的数字与目标数字进行比较,并在“结果”标签显示相应提示。如果猜测错误,玩家可根据提示继续在输入框输入新的数字并点击“猜测”按钮,直到猜对为止。
- 查看游戏进度信息:
- 在游戏过程中,玩家可以随时查看“猜测次数”标签了解已猜测的次数,以及在猜对数字后查看“游戏时间”标签知晓本次游戏所花费的时间。同时,“最佳成绩”标签会一直显示历史最佳成绩信息,玩家可借此对比自己本次游戏的表现。
- 设置游戏难度:
- 若玩家想要调整游戏难度,点击“设置难度”按钮。在弹出的输入对话框中,输入一个整数作为新的难度范围,然后点击“确定”。程序会根据新输入的难度重新生成目标数字,玩家可开始新一轮的猜测。请注意,输入的难度范围必须是有效的整数,否则程序会弹出错误提示对话框,要求重新输入。
- 重置游戏:
- 无论游戏进行到任何阶段,玩家点击“重置游戏”按钮,游戏都会立即重新开始。目标数字会重新生成,猜测次数和游戏时间重置为 0,“结果”标签恢复为“请输入你的猜测”,玩家可重新开始猜测数字。
六、游戏成绩记录
- 最佳成绩记录与更新:
- 每次玩家猜对数字后,游戏会记录本次游戏所用的时间,并将其与历史最佳成绩进行比较。历史最佳成绩最多记录 5 个,且是按照时间从小到大排序。如果本次游戏时间小于或等于历史最佳成绩列表中的最差成绩(即用时最长的成绩),本次成绩将被纳入最佳成绩列表,并在必要时替换掉原有的最差成绩。然后,最佳成绩列表会被保存到本地文件“best_scores.txt”中,以便下次游戏启动时读取。
- 最佳成绩展示:
- 在游戏界面的“最佳成绩”标签处,会实时展示当前的最佳成绩信息。如果没有历史最佳成绩数据,该标签显示“最佳成绩: 无”;若有有效成绩,则显示最佳成绩所用的时间(单位为秒)。
七、注意事项
- 输入猜测数字时,请确保输入的是有效的整数,否则点击“猜测”按钮后程序会弹出错误提示对话框,要求重新输入。
- 设置难度时,同样需要输入有效的整数作为难度范围,否则也会收到错误提示并需要重新输入。
- 若“best_scores.txt”文件因某些原因(如首次运行游戏、文件被误删除等)不存在,游戏在启动时加载最佳成绩数据会出现异常,但程序会忽略该异常并正常运行,只是此时最佳成绩显示为“无”,后续游戏过程中会正常记录和更新最佳成绩数据并创建该文件。
八、完整代码
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
// 数字猜谜游戏的图形界面类,继承自JFrame,用于构建整个游戏的窗口及相关功能
public class NumberGuessingGameGUI extends JFrame {
// 本次游戏要猜测的目标数字
private int targetNumber;
// 玩家猜测的次数
private int attempts;
// 游戏的难度,决定目标数字的生成范围
private int difficulty;
// 游戏开始的时间,用于后续计算游戏用时,单位为毫秒
private long startTime;
// 用于存储历史最佳成绩(游戏用时,单位为秒)的列表
private List<Integer> bestScores = new ArrayList<>();
// 用于玩家输入猜测数字的文本框
private JTextField inputField;
// 用于显示游戏结果提示信息的标签
private JLabel resultLabel;
// 用于显示玩家猜测次数的标签
private JLabel attemptsLabel;
// 用于显示游戏时间的标签
private JLabel timeLabel;
// 用于显示最佳成绩的标签
private JLabel bestScoreLabel;
// 点击进行猜测的按钮
private JButton guessButton;
// 用于重置游戏的按钮
private JButton resetButton;
// 用于设置游戏难度的按钮
private JButton setDifficultyButton;
// 构造函数,用于初始化游戏窗口及相关组件
public NumberGuessingGameGUI() {
// 设置窗口的标题为“数字猜谜游戏”
setTitle("数字猜谜游戏");
// 设置窗口的大小为宽400像素,高300像素
setSize(400, 300);
// 设置窗口关闭时的操作,这里是直接退出程序
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 设置窗口的布局为边界布局(BorderLayout),方便划分不同区域放置组件
setLayout(new BorderLayout());
// 初始化游戏界面的各个组件
initComponents();
// 将窗口设置为可见状态,这样用户就能看到并操作游戏界面了
setVisible(true);
}
// 初始化游戏界面各个组件的方法
private void initComponents() {
// 顶部面板,用于放置输入猜测数字相关的组件
JPanel inputPanel = new JPanel();
// 创建一个文本框,宽度可容纳10个字符左右,用于玩家输入猜测的数字
inputField = new JTextField(10);
// 创建“猜测”按钮,点击该按钮可提交玩家的猜测数字
guessButton = new JButton("猜测");
// 为“猜测”按钮添加动作监听器,当按钮被点击时会执行监听器中的逻辑
guessButton.addActionListener(new GuessButtonListener());
// 将文本框和“猜测”按钮添加到顶部面板中
inputPanel.add(inputField);
inputPanel.add(guessButton);
// 中间面板,采用网格布局(GridLayout),4行1列,用于显示游戏结果、猜测次数、游戏时间和最佳成绩等信息
JPanel infoPanel = new JPanel(new GridLayout(4, 1));
// 初始化结果提示标签,显示默认提示信息“请输入你的猜测”
resultLabel = new JLabel("请输入你的猜测");
// 初始化猜测次数标签,显示初始猜测次数为0
attemptsLabel = new JLabel("猜测次数: 0");
// 初始化游戏时间标签,显示初始游戏时间为0秒
timeLabel = new JLabel("游戏时间: 0秒");
// 初始化最佳成绩标签,显示初始最佳成绩为“无”,表示还没有有效成绩记录
bestScoreLabel = new JLabel("最佳成绩: 无");
// 将上述各个信息标签依次添加到中间面板中
infoPanel.add(resultLabel);
infoPanel.add(attemptsLabel);
infoPanel.add(timeLabel);
infoPanel.add(bestScoreLabel);
// 底部面板,用于放置重置游戏和设置难度的按钮
JPanel controlPanel = new JPanel();
// 创建“重置游戏”按钮,点击它可重置游戏相关状态
resetButton = new JButton("重置游戏");
// 为“重置游戏”按钮添加动作监听器,当按钮被点击时执行相应逻辑
resetButton.addActionListener(new ResetButtonListener());
// 创建“设置难度”按钮,点击它可设置游戏的难度范围
setDifficultyButton = new JButton("设置难度");
// 为“设置难度”按钮添加动作监听器,当按钮被点击时执行对应的操作逻辑
setDifficultyButton.addActionListener(new SetDifficultyButtonListener());
// 将“重置游戏”按钮和“设置难度”按钮添加到底部面板中
controlPanel.add(resetButton);
controlPanel.add(setDifficultyButton);
// 将顶部的输入面板添加到窗口的北部(上方)区域
add(inputPanel, BorderLayout.NORTH);
// 将中间的信息面板添加到窗口的中部(中间)区域
add(infoPanel, BorderLayout.CENTER);
// 将底部的控制面板添加到窗口的南部(下方)区域
add(controlPanel, BorderLayout.SOUTH);
// 初始化最佳成绩列表(先清空列表),并设置默认难度,这里默认难度范围是0 - 100,可以后续通过按钮进行调整
// 同时尝试从本地文件“best_scores.txt”中读取历史最佳成绩数据
loadBestScores();
difficulty = 100;
}
// 生成随机数的方法,根据当前设置的难度范围生成本次游戏要猜测的目标数字,并记录游戏开始时间,同时将猜测次数重置为0
private void generateRandomNumber() {
// 通过Math.random()生成一个在[0, difficulty)范围内的随机整数,作为目标数字
targetNumber = (int) (Math.random() * difficulty);
// 获取当前系统时间(以毫秒为单位),作为游戏开始的时间戳记录下来
startTime = System.currentTimeMillis();
// 将猜测次数初始化为0,表示新一轮游戏开始
attempts = 0;
}
// 比较玩家猜测结果并给出相应提示的方法,同时根据猜测情况更新相关界面显示信息
private void checkGuess(int guess) {
// 猜测次数加1,因为玩家进行了一次猜测操作
attempts++;
// 更新界面上显示的猜测次数标签内容,显示当前猜测次数
attemptsLabel.setText("猜测次数: " + attempts);
if (guess == targetNumber) {
// 如果猜测的数字与目标数字相等,说明猜对了
long endTime = System.currentTimeMillis();
// 计算游戏所用时间,用当前时间减去游戏开始时间,再换算成秒(除以1000,因为时间戳单位是毫秒)
long elapsedTime = (endTime - startTime) / 1000;
// 更新界面上显示的游戏时间标签内容,显示本次游戏所用时间
timeLabel.setText("游戏时间: " + elapsedTime + "秒");
// 根据本次游戏用时更新最佳成绩列表,并保存到文件等相关操作
updateBestScores(elapsedTime);
// 更新界面上的结果提示标签内容,显示猜对的提示信息
resultLabel.setText("恭喜你,猜对了!");
} else if (guess < targetNumber) {
// 如果猜测的数字小于目标数字,说明猜小了,更新界面提示信息
resultLabel.setText("猜小了,再试试");
} else {
// 如果猜测的数字大于目标数字,说明猜大了,更新界面提示信息
resultLabel.setText("猜大了,再试试");
}
}
// 更新最佳成绩列表的方法,将本次游戏用时添加到列表中,进行排序等操作,然后保存到文件,并更新界面显示的最佳成绩信息
private void updateBestScores(long currentTime) {
// 将本次游戏用时(转换为整数)添加到最佳成绩列表中
bestScores.add((int) currentTime);
// 对最佳成绩列表进行排序,从小到大排序,方便后续获取最佳成绩(用时最短的)
Collections.sort(bestScores);
// 如果最佳成绩列表中的元素个数超过5个,说明成绩记录过多,移除用时最长的(即最后一个元素)
if (bestScores.size() > 5) {
bestScores.remove(bestScores.size() - 1);
}
// 将更新后的最佳成绩列表保存到本地文件“best_scores.txt”中
saveBestScores();
// 更新界面上显示的最佳成绩标签内容,显示最新的最佳成绩信息
displayBestScore();
}
// 将最佳成绩列表保存到本地文件“best_scores.txt”的方法
private void saveBestScores() {
try (BufferedWriter writer = new BufferedWriter(new FileWriter("best_scores.txt"))) {
// 遍历最佳成绩列表,将每个成绩(整数)转换为字符串后写入文件,每个成绩占一行
for (Integer score : bestScores) {
writer.write(score.toString());
writer.newLine();
}
} catch (IOException e) {
// 如果在写入文件过程中出现IO异常(比如文件不存在无法写入、没有写入权限等情况),打印堆栈信息方便排查问题
e.printStackTrace();
}
}
// 从本地文件“best_scores.txt”中加载最佳成绩数据到列表中的方法
private void loadBestScores() {
// 先清空最佳成绩列表,避免之前的数据影响本次加载的结果
bestScores.clear();
try (BufferedReader reader = new BufferedReader(new FileReader("best_scores.txt"))) {
String line;
// 逐行读取文件内容,直到文件末尾(读取到的行为null)
while ((line = reader.readLine())!= null) {
// 将读取到的每行字符串解析为整数,并添加到最佳成绩列表中
bestScores.add(Integer.parseInt(line));
}
} catch (IOException e) {
// 如果在读取文件过程中出现异常(比如文件不存在等情况),暂时先忽略,后续可以优化处理,比如提示用户文件不存在等信息
}
}
// 更新并显示界面上最佳成绩标签内容的方法,根据最佳成绩列表的情况来显示相应的文本信息
private void displayBestScore() {
if (bestScores.isEmpty()) {
// 如果最佳成绩列表为空,说明还没有有效成绩记录,显示“最佳成绩: 无”
bestScoreLabel.setText("最佳成绩: 无");
} else {
// 如果列表不为空,显示最佳成绩(取列表中第一个元素,因为已经排序,第一个就是用时最短的)以及对应的单位“秒”
bestScoreLabel.setText("最佳成绩: " + bestScores.get(0) + "秒");
}
}
// “设置难度”按钮的动作监听器内部类,实现ActionListener接口,处理点击“设置难度”按钮后的操作逻辑
private class SetDifficultyButtonListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
// 弹出一个输入对话框,提示用户输入难度范围(整数),获取用户输入的字符串内容
String input = JOptionPane.showInputDialog("请输入难度范围(整数):");
if (input!= null &&!input.isEmpty()) {
try {
// 将用户输入的字符串解析为整数,作为新的难度值
difficulty = Integer.parseInt(input);
// 根据新的难度重新生成目标数字,开启新一轮游戏
generateRandomNumber();
// 更新界面上的结果提示标签内容,显示默认提示信息,让玩家可以开始猜测
resultLabel.setText("请输入你的猜测");
} catch (NumberFormatException ex) {
// 如果用户输入的不是有效的整数,弹出错误提示对话框,告知用户输入有误,需要重新输入
JOptionPane.showMessageDialog(null, "输入的不是有效的整数,请重新输入", "错误", JOptionPane.ERROR_MESSAGE);
}
}
}
}
// “猜测”按钮的动作监听器内部类,实现ActionListener接口,处理点击“猜测”按钮后的操作逻辑
private class GuessButtonListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
// 获取玩家在输入文本框中输入的字符串内容
String input = inputField.getText();
if (input!= null &&!input.isEmpty()) {
try {
// 将玩家输入的字符串解析为整数,作为猜测的数字
int guess = Integer.parseInt(input);
// 调用checkGuess方法,比较猜测数字与目标数字的关系,并根据结果更新界面相关信息等操作
checkGuess(guess);
// 清空输入文本框内容,方便玩家进行下一次猜测
inputField.setText("");
} catch (NumberFormatException ex) {
// 如果用户输入的不是有效的整数,弹出错误提示对话框,告知用户需要输入有效的整数
JOptionPane.showMessageDialog(null, "请输入有效的整数", "错误", JOptionPane.ERROR_MESSAGE);
}
}
}
}
// “重置游戏”按钮的动作监听器内部类,实现ActionListener接口,处理点击“重置游戏”按钮后的操作逻辑
private class ResetButtonListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
// 重新生成目标数字,开启新一轮游戏
generateRandomNumber();
// 更新界面上的结果提示标签内容,显示默认提示信息,让玩家可以开始猜测
resultLabel.setText("请输入你的猜测");
// 更新界面上的猜测次数标签内容,将猜测次数重置为0
attemptsLabel.setText("猜测次数: 0");
// 更新界面上的游戏时间标签内容,将游戏时间重置为0秒
timeLabel.setText("游戏时间: 0秒");
}
}
// 主函数,程序入口点,通过SwingUtilities.invokeLater方法在事件调度线程(EDT)中创建NumberGuessingGameGUI类的实例,确保图形界面相关操作在正确的线程中执行
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new NumberGuessingGameGUI());
}
}