基于Java实现扫雷游戏
扫雷游戏是一款经典的智力游戏。具体要求如下:
- 扫雷游戏分为初级、中级和高级3个级别,扫雷英雄榜存储每个级别的最好成绩,即挖出全部的地雷且用时最少者。单击游戏菜单可以选择初级、中级或高级查看英雄榜。
- 选择级别后将出现相应级别的扫雷区域,这时用户单击雷区中的任何一个方块便启动计时器。
- 用户要揭开某个方块,可单击它。若所揭方块是雷,用户便输了这一局程序发出爆炸的声音。若所揭方块不是雷,则显示一个数字,该数字代表和该方块相邻的方块中是雷的方块总数(相邻方块最多可有8个)同时将周围不是雷的方块揭开。
- 如果用户认为某个方块是雷,在方块上右击,可以在方块上标识一个用户认为是雷的图标(再单击一次可取消所做的标记),即给出一个扫雷标记,相当于扫雷期间在怀疑是雷的方块上插个小红旗。用户每标记一个扫雷标记(无论用户的标记是否正确),程序就把“剩余雷数”减少一个,并显示该剩余雷数。扫雷顺利后,如果成绩进入前 3名,程序会弹出保存成绩的对话框
import javax.swing.*; // 导入Swing库,用于创建GUI组件
import javax.swing.Timer; // 导入Timer类,用于计时
import java.awt.*; // 导入AWT库中的所有类,用于布局和绘图
import java.awt.event.*; // 导入AWT事件包,用于处理事件
import java.io.*; // 导入I/O库,用于文件读写
import java.util.*; // 导入实用工具类库,包括随机数生成器等
public class Minesweeper extends JFrame {
// 常量,用于表示难度级别
private static final int EASY = 1, MEDIUM = 2, HARD = 3;
// 不同难度级别下的网格大小
private static final int EASY_SIZE = 9, MEDIUM_SIZE = 16, HARD_SIZE = 24;
// 不同难度级别下的雷数
private static final int EASY_MINES = 10, MEDIUM_MINES = 40, HARD_MINES = 99;
// 游戏网格的行数和列数,及雷数
private int rows, cols, mines;
// 存储按钮的二维数组,每个按钮代表一个格子
private JButton[][] buttons;
// 存储地雷的位置
private boolean[][] mineField;
// 存储格子是否被揭开的状态
private boolean[][] revealed;
// 存储格子是否被标记的状态
private boolean[][] flagged;
// 剩余的雷数
private int remainingMines;
// 计时器,用于记录游戏时间
private Timer timer;
// 已经过的时间
private int timeElapsed;
// 显示剩余雷数和时间的标签
private JLabel mineLabel, timerLabel;
// 标记游戏是否已开始
private boolean gameStarted;
// 存储英雄榜记录
private String[] leaderboard = new String[3];
// 当前难度级别的名称
private String levelName;
// 存储英雄榜文件名
private String leaderboardFileName;
// 构造函数,游戏启动时执行
public Minesweeper() {
chooseDifficulty(); // 游戏启动时选择难度
}
// 选择游戏难度
private void chooseDifficulty() {
// 提供难度选择选项
String[] options = {"初级", "中级", "高级"};
int choice = JOptionPane.showOptionDialog(this, "请选择游戏难度", "扫雷游戏",
JOptionPane.DEFAULT_OPTION, JOptionPane.INFORMATION_MESSAGE, null, options, options[0]);
// 根据选择的难度设置游戏
switch (choice) {
case 0:
setupGame(EASY);
break;
case 1:
setupGame(MEDIUM);
break;
case 2:
setupGame(HARD);
break;
default:
System.exit(0); // 如果未选择有效选项,则退出游戏
}
setupUI(); // 设置游戏界面
}
// 根据选择的难度设置游戏
private void setupGame(int level) {
switch (level) {
case EASY:
rows = cols = EASY_SIZE;
mines = EASY_MINES;
levelName = "初级";
leaderboardFileName = "leaderboard_easy.txt";
break;
case MEDIUM:
rows = cols = MEDIUM_SIZE;
mines = MEDIUM_MINES;
levelName = "中级";
leaderboardFileName = "leaderboard_medium.txt";
break;
case HARD:
rows = cols = HARD_SIZE;
mines = HARD_MINES;
levelName = "高级";
leaderboardFileName = "leaderboard_hard.txt";
break;
}
// 初始化游戏数据结构
buttons = new JButton[rows][cols];
mineField = new boolean[rows][cols];
revealed = new boolean[rows][cols];
flagged = new boolean[rows][cols];
remainingMines = mines; // 设置剩余雷数
gameStarted = false; // 标记游戏尚未开始
placeMines(); // 随机放置地雷
loadLeaderboard(); // 加载英雄榜数据
}
// 随机在网格中放置地雷
private void placeMines() {
Random rand = new Random(); // 创建随机数生成器
int placedMines = 0; // 记录已放置的地雷数
while (placedMines < mines) {
int r = rand.nextInt(rows); // 随机选择行
int c = rand.nextInt(cols); // 随机选择列
if (!mineField[r][c]) { // 如果当前位置没有地雷
mineField[r][c] = true; // 放置地雷
placedMines++; // 已放置的地雷数加1
System.out.println("地雷放置在: 第" + (r + 1) + "行, 第" + (c + 1) + "列"); // 打印地雷位置
}
}
}
// 设置游戏界面
private void setupUI() {
setTitle("扫雷游戏 - " + levelName); // 设置窗口标题
setSize(500, 500); // 设置窗口大小
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 设置关闭操作
setLayout(new BorderLayout()); // 设置布局管理器
// 顶部面板,显示剩余雷数和时间
JPanel topPanel = new JPanel();
mineLabel = new JLabel("剩余雷数: " + remainingMines); // 显示剩余雷数
timerLabel = new JLabel("时间: 0"); // 显示时间
topPanel.add(mineLabel);
topPanel.add(timerLabel);
add(topPanel, BorderLayout.NORTH);
// 中间的网格面板,显示按钮
JPanel gridPanel = new JPanel();
gridPanel.setLayout(new GridLayout(rows, cols)); // 设置网格布局
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
buttons[r][c] = new JButton(""); // 创建每个格子的按钮
buttons[r][c].setFont(new Font("Arial", Font.PLAIN, 18)); // 设置按钮字体
final int row = r; // 保存当前行
final int col = c; // 保存当前列
// 添加鼠标点击事件监听器
buttons[r][c].addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) { // 左键点击
handleLeftClick(row, col); // 处理左键点击
} else if (e.getButton() == MouseEvent.BUTTON3) { // 右键点击
handleRightClick(row, col); // 处理右键点击
}
}
});
gridPanel.add(buttons[r][c]); // 将按钮添加到网格面板
}
}
add(gridPanel, BorderLayout.CENTER);
// 菜单栏设置
JMenuBar menuBar = new JMenuBar();
JMenu gameMenu = new JMenu("游戏");
JMenuItem newGameItem = new JMenuItem("新游戏");
JMenuItem leaderboardItem = new JMenuItem("查看英雄榜");
JMenuItem exitItem = new JMenuItem("退出");
newGameItem.addActionListener(e -> startNewGame()); // 新游戏菜单项的操作
leaderboardItem.addActionListener(e -> showLeaderboard()); // 查看英雄榜菜单项的操作
exitItem.addActionListener(e -> System.exit(0)); // 退出菜单项的操作
gameMenu.add(newGameItem);
gameMenu.add(leaderboardItem);
gameMenu.addSeparator();
gameMenu.add(exitItem);
menuBar.add(gameMenu);
setJMenuBar(menuBar);
resetTimer(); // 初始化计时器标签
setVisible(true); // 显示窗口
}
// 开始新游戏,重新选择难度
private void startNewGame() {
getContentPane().removeAll(); // 移除当前界面内容
chooseDifficulty(); // 重新选择难度
revalidate(); // 刷新界面
repaint(); // 重绘界面
}
// 处理左键点击事件
private void handleLeftClick(int row, int col) {
if (!gameStarted) {
startTimer(); // 如果游戏未开始,启动计时器
gameStarted = true; // 标记游戏已开始
}
if (revealed[row][col] || flagged[row][col]) return; // 如果该格子已被揭开或标记,直接返回
revealed[row][col] = true; // 标记该格子为已揭开
if (mineField[row][col]) { // 如果该格子是地雷
buttons[row][col].setText("X"); // 显示地雷
buttons[row][col].setBackground(Color.RED); // 设置背景色为红色
playExplosionSound(); // 播放爆炸声
stopTimer(); // 停止计时器
revealAllMines(); // 揭开所有地雷
int response = JOptionPane.showConfirmDialog(this, "你输了!是否重新开始?", "游戏结束", JOptionPane.YES_NO_OPTION);
if (response == JOptionPane.YES_OPTION) {
startNewGame(); // 如果选择重新开始,启动新游戏
} else {
System.exit(0); // 否则退出游戏
}
} else {
int adjacentMines = countAdjacentMines(row, col); // 计算相邻地雷数
if (adjacentMines > 0) {
buttons[row][col].setText(String.valueOf(adjacentMines)); // 显示相邻地雷数
} else {
buttons[row][col].setEnabled(false); // 禁用按钮
revealAdjacent(row, col); // 递归揭开相邻格子
}
if (checkWin()) { // 检查是否获胜
stopTimer(); // 停止计时器
checkLeaderboard(); // 检查是否进入英雄榜
int response = JOptionPane.showConfirmDialog(this, "你赢了!是否重新开始?", "游戏结束", JOptionPane.YES_NO_OPTION);
if (response == JOptionPane.YES_OPTION) {
startNewGame(); // 如果选择重新开始,启动新游戏
} else {
System.exit(0); // 否则退出游戏
}
}
}
}
// 处理右键点击事件
private void handleRightClick(int row, int col) {
if (revealed[row][col]) return; // 如果格子已被揭开,直接返回
if (flagged[row][col]) { // 如果格子已被标记
flagged[row][col] = false; // 取消标记
buttons[row][col].setText(""); // 清除文字
remainingMines++; // 剩余雷数加1
} else {
flagged[row][col] = true; // 标记格子
buttons[row][col].setText("旗"); // 显示旗子
remainingMines--; // 剩余雷数减1
}
mineLabel.setText("剩余雷数: " + remainingMines); // 更新剩余雷数标签
}
// 计算某格子周围的地雷数
private int countAdjacentMines(int row, int col) {
int count = 0;
for (int r = -1; r <= 1; r++) { // 遍历该格子周围的8个格子
for (int c = -1; c <= 1; c++) {
int nr = row + r; // 计算相邻格子的行号
int nc = col + c; // 计算相邻格子的列号
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && mineField[nr][nc]) { // 检查是否为地雷
count++; // 地雷计数加1
}
}
}
return count; // 返回地雷数
}
// 递归揭开相邻的空白格子
private void revealAdjacent(int row, int col) {
for (int r = -1; r <= 1; r++) { // 遍历周围的格子
for (int c = -1; c <= 1; c++) {
int nr = row + r; // 相邻格子的行号
int nc = col + c; // 相邻格子的列号
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && !revealed[nr][nc]) {
handleLeftClick(nr, nc); // 递归揭开格子
}
}
}
}
// 揭开所有地雷
private void revealAllMines() {
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
if (mineField[r][c]) { // 如果是地雷
buttons[r][c].setText("X"); // 显示地雷
}
}
}
}
// 检查是否赢得游戏
private boolean checkWin() {
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
if (!mineField[r][c] && !revealed[r][c]) { // 如果有未揭开的非雷格子
return false; // 游戏未胜利
}
}
}
JOptionPane.showMessageDialog(this, "你赢了!"); // 显示胜利信息
return true; // 返回胜利
}
// 检查并更新英雄榜
private void checkLeaderboard() {
String name = JOptionPane.showInputDialog("恭喜!请输入你的名字:"); // 获取玩家姓名
if (name == null || name.trim().isEmpty()) {
return;
}
String newEntry = name + " - " + timeElapsed + "秒"; // 新记录
for (int i = 0; i < leaderboard.length; i++) {
if (leaderboard[i] == null || leaderboard[i].endsWith("秒") && timeElapsed < Integer.parseInt(leaderboard[i].split(" - ")[1].replace("秒", ""))) {
for (int j = leaderboard.length - 1; j > i; j--) {
leaderboard[j] = leaderboard[j - 1]; // 插入新的记录
}
leaderboard[i] = newEntry; // 更新记录
break;
}
}
saveLeaderboard(); // 保存英雄榜
JOptionPane.showMessageDialog(this, "英雄榜:\n" + String.join("\n", leaderboard)); // 显示英雄榜
}
// 加载英雄榜数据
private void loadLeaderboard() {
try (BufferedReader br = new BufferedReader(new FileReader(leaderboardFileName))) {
for (int i = 0; i < leaderboard.length; i++) {
leaderboard[i] = br.readLine(); // 从文件中读取记录
}
} catch (IOException e) {
Arrays.fill(leaderboard, null); // 处理文件不存在的情况
}
}
// 保存英雄榜数据
private void saveLeaderboard() {
try (BufferedWriter bw = new BufferedWriter(new FileWriter(leaderboardFileName))) {
for (String entry : leaderboard) {
if (entry != null) {
bw.write(entry); // 写入文件
bw.newLine();
}
}
} catch (IOException e) {
e.printStackTrace(); // 处理文件写入异常
}
}
// 显示英雄榜
private void showLeaderboard() {
JOptionPane.showMessageDialog(this, "英雄榜:\n" + String.join("\n", leaderboard)); // 显示英雄榜
}
// 启动计时器
private void startTimer() {
timeElapsed = 0;
timer = new Timer(1000, e -> {
timeElapsed++;
timerLabel.setText("时间: " + timeElapsed);
});
timer.start(); // 启动计时器
}
// 重置计时器
private void resetTimer() {
if (timer != null) {
timer.stop(); // 停止计时器
}
timerLabel.setText("时间: 0"); // 重置时间标签
}
// 停止计时器
private void stopTimer() {
if (timer != null) {
timer.stop(); // 停止计时器
}
}
// 模拟播放爆炸声
private void playExplosionSound() {
System.out.println("炸雷咯!"); // 控制台输出爆炸信息
}
// 主函数,启动游戏
public static void main(String[] args) {
SwingUtilities.invokeLater(Minesweeper::new); // 使用Swing事件调度线程启动游戏
}
}