Bootstrap

基于Java Swing 实现的gui扫雷游戏(超详细每行代码都有注释)

游戏流程

  • 开始游戏弹出难度选择框

  • 选择难度

  • 根据难度生成游戏界面

  • 翻开棋盘

    • 是炸弹游戏结束显示所有地雷。
    • 不是计算周围炸弹数
      • 炸弹数不为0显示在该位置
      • 炸弹数为0递归展开棋盘直到周围炸弹数不为0停止
  • 翻开所有非炸弹后游戏结束

编写难度选择窗口

public class Main {
    private JFrame frame;

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Main()::level);
    }

    private void level(){
 		frame = new JFrame("难度选择"); // 创建难度选择界面的窗口
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 设置窗口关闭操作

        JPanel panel = new JPanel(); // 创建一个面板用于放置难度选择按钮
        panel.setLayout(new GridLayout(1, 3)); // 使用GridLayout布局,将按钮排列成一行三列

        JButton button1 = new JButton("初级"); // 创建初级难度按钮
        button1.addActionListener(e->{
			// 这里创建游戏界面
        });
        panel.add(button1); // 将初级难度按钮添加到面板

        JButton button2 = new JButton("中级");
        button2.addActionListener(e->{

        });
        panel.add(button2);

        JButton button3 = new JButton("高级");
        button3.addActionListener(e->{

        });
        panel.add(button3);

        frame.getContentPane().add(panel);
        frame.setSize(300, 100);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}

效果展示

在这里插入图片描述

编写扫雷窗口

增加一个size变量表示棋盘大小

// 棋盘大小
private static  int SIZE = 0;

修改难度选择界面三个按钮的监听事件

        button1.addActionListener(e->{
            SIZE = 8;
            SwingUtilities.invokeLater(main::createGameGUI);
        });
        button2.addActionListener(e->{
            SIZE = 16;
            SwingUtilities.invokeLater(main::createGameGUI);
        });
        button3.addActionListener(e->{
            SIZE = 30;
            SwingUtilities.invokeLater(main::createGameGUI);
        });

生成游戏界面

private void createGameGUI() {
        frame = new JFrame("扫雷游戏"); // 创建游戏界面窗口
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 设置窗口关闭操作

        JPanel panel = new JPanel(new GridLayout(SIZE, SIZE));
        buttons = new JButton[SIZE][SIZE]; // 创建按钮二维数组,用于表示扫雷棋盘上的每个方块
		
    	// 初始化棋盘
        for (int i = 1; i <= SIZE; i++) {
            for (int j = 1; j <= SIZE; j++) {
                buttons[i - 1][j - 1] = new JButton(); // 创建一个按钮
                panel.add(buttons[i - 1][j - 1]); // 将按钮添加到面板上

            }
        }
        frame.getContentPane().add(panel); // 将面板添加到窗口的内容面板
        frame.setSize(500+SIZE*22, 500+SIZE*22); // 设置窗口大小
        frame.setLocationRelativeTo(null); // 将窗口居中显示
        frame.setResizable(false); // 禁止窗口大小调整
        frame.setVisible(true); // 显示窗口
    }

初级

在这里插入图片描述

中级

在这里插入图片描述

高级

在这里插入图片描述

完善游戏界面创建

完善难度选择界面三个按钮的监听事件

    // 地雷数量
    private static int LEI_SIZE = 0;
        button1.addActionListener(e->{
            SIZE = 8; // 设置棋盘大小为8x8
            LEI_SIZE=10; // 设置地雷数量为10
            frame.setVisible(false); // 隐藏难度选择界面
            SwingUtilities.invokeLater(main::createGameGUI); // 调用创建游戏界面的方法
        });
        button2.addActionListener(e->{
            SIZE = 16;
            LEI_SIZE=35;
            frame.setVisible(false);
            SwingUtilities.invokeLater(main::createGameGUI);
        });
        button3.addActionListener(e->{
            SIZE = 30;
            LEI_SIZE=70;
            frame.setVisible(false);
            SwingUtilities.invokeLater(main::createGameGUI);
        });

我们准备两个数组,一个记录棋盘是否翻开(棋盘靠这个数组显示),一个记录炸弹与非炸弹的位置

// 0 表示未翻开    
private int[][] show;
// 0 表示不是雷
private int[][] lei;

完善createGameGUI()

private JButton[][] buttons;
private void createGameGUI() {
        frame = new JFrame("扫雷游戏"); // 创建游戏界面窗口
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 设置窗口关闭操作

        JPanel panel = new JPanel(new GridLayout(SIZE, SIZE));
        buttons = new JButton[SIZE][SIZE]; // 创建按钮二维数组,用于表示扫雷棋盘上的每个方块
        show = new int[SIZE+2][SIZE+2]; // 这里多加两行两列是防止后面数组越界
        lei = new int[SIZE+2][SIZE+2];
        COUNT = 0;
        // 初始化棋盘
        for (int i = 1; i <= SIZE; i++) {
            for (int j = 1; j <= SIZE; j++) {
                buttons[i - 1][j - 1] = new JButton(); // 创建一个按钮
                // ButtonListener 自定义监听器
                buttons[i - 1][j - 1].addMouseListener(new ButtonListener(i, j)); // 为按钮添加鼠标事件监听器
                panel.add(buttons[i - 1][j - 1]); // 将按钮添加到面板上
            }
        }
        gameInit(); // 初始化游戏,布雷
        frame.getContentPane().add(panel); // 将面板添加到窗口的内容面板
        frame.setSize(500+SIZE*22, 500+SIZE*22); // 设置窗口大小
        frame.setLocationRelativeTo(null); // 将窗口居中显示
        frame.setResizable(false); // 禁止窗口大小调整
        frame.setVisible(true); // 显示窗口
    }

实现棋盘初始化

    private void gameInit() {
        Random random = new Random(); // 创建一个随机数生成器对象
        int count = 0; // 用于记录已布置的地雷数量
        while (count < LEI_SIZE) { // 循环,直到布置足够数量的地雷
            // 随机选择位置布雷
            int i = random.nextInt(1000) % SIZE + 1; // 随机生成一个行号(1 到 SIZE)
            int j = random.nextInt(1000) % SIZE + 1; // 随机生成一个列号(1 到 SIZE)
            if (lei[i][j] != 1) { // 如果选择的位置没有地雷
                lei[i][j] = 1; // 在该位置布置地雷
                ++count; // 增加已布置地雷数量的计数
            }
        }
    }

自定义监听器(翻开棋盘的处理)

// 字体
private static Font font = new Font("SansSerif", Font.PLAIN, 28);
// 已翻开位置数量
private static int COUNT = 0;
	// 这里继承MouseAdapter而不是实现MouseListener
    // MouseAdapter 类是 MouseListener 接口的适配器类,
    // 它提供了对 MouseListener 接口的默认实现,可以选择性地重写需要的方法,而无需实现所有方法。
    private class ButtonListener extends MouseAdapter {
        private final int row;
        private final int col;

        public ButtonListener(int row, int col) {
            this.row = row;
            this.col = col;
        }

        // 鼠标点击事件
        @Override
        public void mousePressed(MouseEvent e) {
            if (SwingUtilities.isRightMouseButton(e)) {  // 判断是否是鼠标右键点击
                if (show[row][col] == 0) {
                    show[row][col] = 2;  // 使用 'F' 表示标记
                    buttons[row - 1][col - 1].setFont(font); // 设置字体
                    buttons[row - 1][col - 1].setText("F");  // 设置按钮文本为 'F'
                } else if (show[row][col] == 2) {
                    show[row][col] = 0;  // 取消标记
                    buttons[row - 1][col - 1].setText("");  // 清空按钮文本
                }
            } else {  // 非右键点击,执行原有的翻开逻辑
                if (show[row][col] == 0) {
                    paiLei(row, col);
                }
            }
        }
	
        private void paiLei(int row,int col){
            if (lei[row][col] == 1){ // 如果点击的位置是地雷
                // 添加一个踩到地雷展示全部地雷的逻辑
                showAllLei();
                JOptionPane.showMessageDialog(frame, "踩到地雷,游戏结束"); // 显示游戏结束的消息框
                resetGame(); // 重置游戏
                resetGame(); // 重置游戏
            } // 如果点击的位置不是地雷
            zhanKai(row, col); // 调用展开方法继续游戏
        }
        // 
        private void showAllLei() {
            for (int i = 1; i < lei.length - 1; i++) {
                for (int j = 1; j < lei.length - 1; j++) {
                    if (lei[i][j] == 1) {
                        buttons[i - 1][j - 1].setEnabled(false);
                        buttons[i - 1][j - 1].setFont(new Font("SansSerif", Font.PLAIN, 50));
                        buttons[i - 1][j - 1].setText("*");
                    }
                }
            }
        }
        private void zhanKai(int row,int col){
            // 判断是否点击的位置是地雷,或者超出了棋盘范围
            if (lei[row][col] == 1 || row < 1 || row > SIZE || col < 1 || col > SIZE) return;
            // 标记该位置为翻开状态
            show[row][col] = 1;
            // 禁用按钮,防止再次点击
            buttons[row - 1][col - 1].setEnabled(false);
            int count = getLeiCount(row, col); // 获取周围地雷的数量
            if (count == 0) {
                // 如果周围没有地雷,递归展开周围一圈的方块
                for (int i = row - 1; i <= row + 1; i++) {
                    for (int j = col - 1; j <= col + 1; j++) {
                        if (show[i][j] == 0) {
                            zhanKai(i, j);
                        }
                    }
                }
            } else {
                // 显示周围地雷数量,设置字体大小为 28
                buttons[row - 1][col - 1].setFont(font);
                buttons[row - 1][col - 1].setText(String.valueOf(count));
            }
            COUNT++; // 增加已翻开方块的计数
            System.out.println(COUNT);
            if (COUNT == (SIZE * SIZE - LEI_SIZE)) {
                JOptionPane.showMessageDialog(frame, "成功排雷,游戏结束"); // 游戏胜利,显示胜利消息
                resetGame(); // 重置游戏
            }
        }
        private int getLeiCount(int row, int col) {
            int count = 0;
            if (lei[row - 1][col - 1] == 1) count++;
            if (lei[row - 1][col] == 1) count++;
            if (lei[row - 1][col + 1] == 1) count++;
            if (lei[row][col - 1] == 1) count++;
            if (lei[row][col + 1] == 1) count++;
            if (lei[row + 1][col - 1] == 1) count++;
            if (lei[row + 1][col] == 1) count++;
            if (lei[row + 1][col + 1] == 1) count++;
            return count;
        }
   }

展示

在这里插入图片描述

在这里插入图片描述

完整代码

package com.hzy.saoleGUI;


import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Random;


/**
 * @title: Main
 * @Author zxwyhzy
 * @Date: 2023/5/28 21:42
 * @Version 1.0
 */
public class Main {
    private JFrame frame;
    private JButton[][] buttons;

    // 记录位置状态 0 未翻开, 1 翻开 ,2 标记
    private int[][] show;
    // 记录 雷的位置 1是雷 0不是雷
    private int[][] lei;
    // 翻开位置个数
    private static int COUNT = 0;
    // 棋盘大小
    private static int SIZE = 0;
    // 地雷数量
    private static int LEI_SIZE = 0;
    private static Font font = new Font("SansSerif", Font.PLAIN, 28);

    public static void main(String[] args) {
        Main main = new Main();
        SwingUtilities.invokeLater(() -> main.level(main));
    }

    private void level(Main main) {
        frame = new JFrame("难度选择"); // 创建难度选择界面的窗口
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 设置窗口关闭操作

        JPanel panel = new JPanel(); // 创建一个面板用于放置难度选择按钮
        panel.setLayout(new GridLayout(1, 3)); // 使用GridLayout布局,将按钮排列成一行三列

        JButton button1 = new JButton("初级"); // 创建初级难度按钮
        button1.addActionListener(e -> {
            SIZE = 8; // 设置棋盘大小为8x8
            LEI_SIZE = 10; // 设置地雷数量为10
            frame.setVisible(false); // 隐藏难度选择界面
            SwingUtilities.invokeLater(main::createGameGUI); // 调用创建游戏界面的方法
        });
        panel.add(button1); // 将初级难度按钮添加到面板

        JButton button2 = new JButton("中级");
        button2.addActionListener(e -> {
            SIZE = 16;
            LEI_SIZE = 35;
            frame.setVisible(false);
            SwingUtilities.invokeLater(main::createGameGUI);
        });
        panel.add(button2);

        JButton button3 = new JButton("高级");
        button3.addActionListener(e -> {
            SIZE = 30;
            LEI_SIZE = 70;
            frame.setVisible(false);
            SwingUtilities.invokeLater(main::createGameGUI);
        });
        panel.add(button3);

        frame.getContentPane().add(panel);
        frame.setSize(300, 100);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private void createGameGUI() {
        frame = new JFrame("扫雷游戏"); // 创建游戏界面窗口
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 设置窗口关闭操作

        JPanel panel = new JPanel(new GridLayout(SIZE, SIZE));
        buttons = new JButton[SIZE][SIZE]; // 创建按钮二维数组,用于表示扫雷棋盘上的每个方块
        show = new int[SIZE + 2][SIZE + 2]; // 这里多加两行两列是防止后面数组越界
        lei = new int[SIZE + 2][SIZE + 2];
        COUNT = 0;
        // 初始化棋盘
        for (int i = 1; i <= SIZE; i++) {
            for (int j = 1; j <= SIZE; j++) {
                buttons[i - 1][j - 1] = new JButton(); // 创建一个按钮
                buttons[i - 1][j - 1].addMouseListener(new ButtonListener(i, j)); // 为按钮添加鼠标事件监听器
                panel.add(buttons[i - 1][j - 1]); // 将按钮添加到面板上
            }
        }
        gameInit(); // 初始化游戏,布雷
        frame.getContentPane().add(panel); // 将面板添加到窗口的内容面板
        frame.setSize(500 + SIZE * 22, 500 + SIZE * 22); // 设置窗口大小
        frame.setLocationRelativeTo(null); // 将窗口居中显示
        frame.setResizable(false); // 禁止窗口大小调整
        frame.setVisible(true); // 显示窗口
    }

    private void resetGame() {
        frame.setVisible(false);
        SwingUtilities.invokeLater(() -> level(this));
    }

    private void gameInit() {
        Random random = new Random(); // 创建一个随机数生成器对象
        int count = 0; // 用于记录已布置的地雷数量
        while (count < LEI_SIZE) { // 循环,直到布置足够数量的地雷
            // 随机选择位置布雷
            int i = random.nextInt(1000) % SIZE + 1; // 随机生成一个行号(1 到 SIZE)
            int j = random.nextInt(1000) % SIZE + 1; // 随机生成一个列号(1 到 SIZE)
            if (lei[i][j] != 1) { // 如果选择的位置没有地雷
                lei[i][j] = 1; // 在该位置布置地雷
                ++count; // 增加已布置地雷数量的计数
            }
        }
    }

    // 这里继承MouseAdapter而不是实现MouseListener
    // MouseAdapter 类是 MouseListener 接口的适配器类,
    // 它提供了对 MouseListener 接口的默认实现,可以选择性地重写需要的方法,而无需实现所有方法。
    private class ButtonListener extends MouseAdapter {
        private final int row;
        private final int col;

        public ButtonListener(int row, int col) {
            this.row = row;
            this.col = col;
        }

        @Override
        public void mousePressed(MouseEvent e) {
            if (SwingUtilities.isRightMouseButton(e)) {  // 判断是否是鼠标右键点击
                if (show[row][col] == 0) {
                    show[row][col] = 2;  // 使用 'F' 表示标记
                    buttons[row - 1][col - 1].setFont(font);
                    // buttons[row - 1][col - 1].setText("F");  // 设置按钮文本为 'F'
                    System.out.println("设置标记");
                    buttons[row - 1][col - 1].setIcon(new ImageIcon("src/com/hzy/saoleGUI/a.png"));
                } else if (show[row][col] == 2) {
                    show[row][col] = 0;  // 取消标记
                    // buttons[row - 1][col - 1].setText("");  // 清空按钮文本
                    buttons[row - 1][col - 1].setIcon(new ImageIcon(""));
                    System.out.println("清除标记");
                }
            } else {  // 非右键点击,执行原有的翻开逻辑
                if (show[row][col] == 0) {
                    paiLei(row, col);
                }
            }
        }

        private void paiLei(int row, int col) {
            if (lei[row][col] == 1) { // 如果点击的位置是地雷
                // 添加一个踩到地雷展示全部棋盘的逻辑
                showAllLei();
                JOptionPane.showMessageDialog(frame, "踩到地雷,游戏结束"); // 显示游戏结束的消息框
                resetGame(); // 重置游戏
            } // 如果点击的位置不是地雷
            zhanKai(row, col); // 调用展开方法继续游戏
        }

        private void showAllLei() {
            for (int i = 1; i < lei.length - 1; i++) {
                for (int j = 1; j < lei.length - 1; j++) {
                    if (lei[i][j] == 1) {
                        buttons[i - 1][j - 1].setEnabled(false);
                        buttons[i - 1][j - 1].setFont(new Font("SansSerif", Font.PLAIN, 50));
                        buttons[i - 1][j - 1].setText("*");
                    }
                }
            }
        }

        private void zhanKai(int row, int col) {
            // 判断是否点击的位置是地雷,或者超出了棋盘范围
            if (lei[row][col] == 1 || row < 1 || row > SIZE || col < 1 || col > SIZE) return;
            // 标记该位置为翻开状态
            show[row][col] = 1;
            // 禁用按钮,防止再次点击
            buttons[row - 1][col - 1].setEnabled(false);
            int count = getLeiCount(row, col); // 获取周围地雷的数量
            if (count == 0) {
                // 如果周围没有地雷,递归展开周围一圈的方块
                for (int i = row - 1; i <= row + 1; i++) {
                    for (int j = col - 1; j <= col + 1; j++) {
                        if (show[i][j] == 0) {
                            zhanKai(i, j);
                        }
                    }
                }
            } else {
                // 显示周围地雷数量,设置字体大小为 28

                buttons[row - 1][col - 1].setFont(font);
                buttons[row - 1][col - 1].setText(String.valueOf(count));
            }
            COUNT++; // 增加已翻开方块的计数
            System.out.println(COUNT);
            if (COUNT == (SIZE * SIZE - LEI_SIZE)) {
                JOptionPane.showMessageDialog(frame, "成功排雷,游戏结束"); // 游戏胜利,显示胜利消息
                resetGame(); // 重置游戏
            }
        }

        private int getLeiCount(int row, int col) {
            int count = 0;
            if (lei[row - 1][col - 1] == 1) count++;
            if (lei[row - 1][col] == 1) count++;
            if (lei[row - 1][col + 1] == 1) count++;
            if (lei[row][col - 1] == 1) count++;
            if (lei[row][col + 1] == 1) count++;
            if (lei[row + 1][col - 1] == 1) count++;
            if (lei[row + 1][col] == 1) count++;
            if (lei[row + 1][col + 1] == 1) count++;
            return count;
        }
    }
}


右键插入旗帜

修改 mousePressed() 方法

@Override
        public void mousePressed(MouseEvent e) {
            if (SwingUtilities.isRightMouseButton(e)) {  // 判断是否是鼠标右键点击
                if (show[row][col] == 0) {
                    show[row][col] = 2;  // 使用 'F' 表示标记
                    buttons[row - 1][col - 1].setFont(font);
                    // buttons[row - 1][col - 1].setText("F");  // 设置按钮文本为 'F'
                    System.out.println("设置标记");
					buttons[row - 1][col - 1].setIcon(new ImageIcon("src/com/hzy/saoleGUI/a.png"));
                } else if (show[row][col] == 2) {
                    show[row][col] = 0;  // 取消标记
                    // buttons[row - 1][col - 1].setText("");  // 清空按钮文本
                    buttons[row - 1][col - 1].setIcon(new ImageIcon(""));
                    System.out.println("清除标记");
                }
            } else {  // 非右键点击,执行原有的翻开逻辑
                if (show[row][col] == 0) {
                    paiLei(row, col);
                }
            }
        }

演示

这里随便截了张图当旗帜
在这里插入图片描述

;