基于Java实现的扫雷游戏

     基于Java实现扫雷游戏


        扫雷游戏是一款经典的智力游戏。具体要求如下:

  1.         扫雷游戏分为初级、中级和高级3个级别,扫雷英雄榜存储每个级别的最好成绩,即挖出全部的地雷且用时最少者。单击游戏菜单可以选择初级、中级或高级查看英雄榜。
  2.         选择级别后将出现相应级别的扫雷区域,这时用户单击雷区中的任何一个方块便启动计时器。
  3.         用户要揭开某个方块,可单击它。若所揭方块是雷,用户便输了这一局程序发出爆炸的声音。若所揭方块不是雷,则显示一个数字,该数字代表和该方块相邻的方块中是雷的方块总数(相邻方块最多可有8个)同时将周围不是雷的方块揭开。
  4.         如果用户认为某个方块是雷,在方块上右击,可以在方块上标识一个用户认为是雷的图标(再单击一次可取消所做的标记),即给出一个扫雷标记,相当于扫雷期间在怀疑是雷的方块上插个小红旗。用户每标记一个扫雷标记(无论用户的标记是否正确),程序就把“剩余雷数”减少一个,并显示该剩余雷数。扫雷顺利后,如果成绩进入前 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事件调度线程启动游戏
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java小诚

新手一个,望包涵

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值