Java版扫雷小游戏,外观精美

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


开发环境

开发工具:eclipse2021-12
JDK版本:JDK15.0.1


项目结构

在这里插入图片描述

下载地址:

链接:https://pan.baidu.com/s/1IkGy-UKHtxngzokrKSYqOQ
提取码:t58l


一、运行画面展示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


二、代码部分

1.代码

package com.test;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;


@SuppressWarnings("serial")
public class MineSweeper extends JFrame {
    
    

	// 初始化窗口的长、宽的常量
    private static final int DEFAULT_WIDTH = 1440;
    private static final int DEFAULT_HEIGHT = 900;

    // 字体设置变量
    private Font infoNumber = new Font("Arial", Font.PLAIN, 30); // 时间数字字体
    private Font fText = new Font("幼圆", Font.BOLD, 25); // 文本字体
    private Font fMenu = new Font("微软雅黑", Font.PLAIN, 15); // 文本字体

    // 颜色设置变量
    private String c_info = "#CDBA96"; // 信息面板背景颜色
    private String c_minePan = "#FFFAFA"; // 雷面板背景颜色

    // 观感变量
    String lafWin = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel";
    String lafNim = "javax.swing.plaf.nimbus.NimbusLookAndFeel";

    // 判断run()方法是否运行的标志
    private boolean flag;

    // 常量定义
    private final int EMPTY = 0; // 雷面板空状态
    private final int MINE = 1; // 雷面板雷状态
    private final int CHECKED = 2; // 雷面板已检查的状态
    private final int NO_FLAG = 0; // 按钮没有设置旗帜的状态
    private final int IS_FLAG = 1; // 按钮没有设置旗帜的状态
    private final int MINE_NUMBER_H = 60; // 雷的总数量
    private final int ROW = 16; // 行数
    private final int COL = 30; // 列数

    private JTextField timeText; // 显示时间的文本框
    private JTextField mineText; // 显示剩余雷数的文本框
    private JPanel minePanel; // 主面板,用于放置雷
    private JPanel infoPanel; // 信息面板
    private JMenuBar menuBar; // 菜单栏

    private JPanel[][] mineJan; // 雷面板数组
    private JButton[][] mineBut; // 雷按钮数组
    private JLabel[][] mineLab; // 雷标签数组
    private int[][] map; // 雷面板标志数组
    private int[][] mineFalg; // 雷按钮标志数组,用户标记的雷数量,右击出现“旗”

    ActionListener menuAc = new MenuAction(); // 菜单事件处理类实例
    ActionListener buttonAc = new ButtonAction(); // 按钮事件处理类实例
    MouseListener mouseAC = new mouseAction(); // 鼠标事件类实例
	
	
    //程序入口
	public static void main(String[] args) throws Exception  {
    
    
		MineSweeper frame = new MineSweeper();//初始化构造函数,进入构造函数public XinSaoLei()
		
		// 窗口居中显示
        Toolkit kit = Toolkit.getDefaultToolkit();
        int screenWidth = kit.getScreenSize().width;
        int screenHeight = kit.getScreenSize().height;
		frame.setBounds((screenWidth - DEFAULT_WIDTH) / 2, (screenHeight - DEFAULT_HEIGHT) / 2, DEFAULT_WIDTH,
				DEFAULT_HEIGHT);

        frame.setTitle("扫雷");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
 
        
	}

    /**
     * 实例化窗口的构造方法,构造函数,创建frame后执行
     */
    public MineSweeper() throws Exception {
    
    

        menu();
        infoPanel();
        minePanel();
    }

	private void menu() throws Exception {
    
    

        menuBar = new JMenuBar();

        // 改变菜单栏的观感为window观感
        UIManager.setLookAndFeel(lafWin);
        SwingUtilities.updateComponentTreeUI(menuBar);
        pack();

        JMenu menuG = new JMenu("游戏(G)");
        JMenu menuH = new JMenu("帮助(H)");
        JMenuItem itemNew = new JMenuItem("新游戏(N)");
        JMenuItem itemExit = new JMenuItem("退出(E)");
        JMenuItem itemAbout = new JMenuItem("关于扫雷(A)");
        addMenu(menuG);
        addMenu(menuH);
        addMenuItem(menuG, itemNew);
        addMenuItem(menuG, itemExit);
        addMenuItem(menuH, itemAbout);

        setJMenuBar(menuBar);
    }
    
    /**
     * 菜单栏设计 
     * 一级菜单:游戏(G) 帮助(H) 
     * 二级菜单:新游戏(N) 退出(E) 关于游戏(A)
     */
    // 添加一级菜单的方法
    private void addMenu(JMenu menu) {
    
    
        menu.setFont(fMenu);
        menuBar.add(menu);
    }

    // 添加二级菜单的方法
    private void addMenuItem(JMenu menu, JMenuItem menuItem) {
    
    
        menuItem.setFont(fMenu);
        menuItem.addActionListener(menuAc); // 为菜单项注册事件监听器
        menu.add(menuItem);
    }
    
    /**
     * 信息面板设计: 
     * 1.采用默认流布局  
     * 2.添加两个标签组件:时间、雷数
     * 3.添加两个文本框组件:时间文本框、剩余雷数文本框
     */
    private void infoPanel() {
    
    
        infoPanel = new JPanel();
        infoPanel.setBackground(Color.decode(c_info)); // 设置背景颜色

        JLabel mine = new JLabel("雷数");
        mine.setHorizontalAlignment(SwingConstants.CENTER); // 字体居中显示
        mine.setFont(fText); // 设置字体

        mineText = new JTextField(3);
        mineText.setEditable(false); // 设置为不可编辑
        mineText.setHorizontalAlignment(SwingConstants.CENTER);
        mineText.setFont(infoNumber);

        JLabel time = new JLabel("时间");
        time.setFont(fText);
        time.setHorizontalAlignment(SwingConstants.CENTER);

        timeText = new JTextField("0", 3);
        timeText.setEditable(false);
        timeText.setHorizontalAlignment(SwingConstants.CENTER);
        timeText.setFont(infoNumber);

        // 流布局的特点,组件的排序方式为添加组件的顺序。
        infoPanel.add(time);
        infoPanel.add(timeText);
        infoPanel.add(mineText);
        infoPanel.add(mine);

        getContentPane().add(infoPanel, BorderLayout.SOUTH); // 将信息面板添加到窗口BorderLayout布局的下方位置
    }
   
    /**
     * 主面板设计: 
     * 1.采用网格布局
     * 2.为每一个雷设置一个空面板,用放置按钮跟标签,采用卡片布局(卡片布局的优点在于所有组件共享一个
     *   容器,并且容器只显示最上层的组件)
     * 3.空面板上添加标签,标签用于显示雷数(int)或者雷(Icon)。
     * 4.添加空白按钮,用来遮挡标签。并添加事件,当发生点击事件时,将该按钮移除,显示出标签。
     */
    private void minePanel() throws Exception {
    
    
        minePanel = new JPanel();

        // 采用Nimbus观感
        UIManager.setLookAndFeel(lafNim);
        SwingUtilities.updateComponentTreeUI(minePanel);
        pack();

        minePanel.setLayout(new GridLayout(16, 30)); // 设置网格布局
        minePanel.setBackground(Color.decode(c_minePan));
        minePanel.setBorder(BorderFactory.createEmptyBorder(10, 15, 10, 15)); // 设置一个空边框
        addMinePan();
        init();
        getContentPane().add(minePanel, BorderLayout.CENTER); // 将主面板添加到窗口BorderLayout布局的中间位置
    }
    
    /**
     * 添加雷面板
     */
    private void addMinePan() throws Exception {
    
    
        mineJan = new JPanel[16][30];

        for (int i = 0; i < 16; i++) {
    
    
            for (int j = 0; j < 30; j++) {
    
    

                mineJan[i][j] = new JPanel();
                mineJan[i][j].setLayout(new BorderLayout());
                mineJan[i][j].setBackground(Color.decode(c_minePan));

                minePanel.add(mineJan[i][j]);
            }
        }
    }
    
    /**
     * 初始化方法
     */
    private void init() throws Exception {
    
    

        flag = false;
        mineBut = new JButton[ROW][COL];
        mineLab = new JLabel[ROW][COL];
        for (int i = 0; i < ROW; i++) {
    
    
            for (int j = 0; j < COL; j++) {
    
    
                map = new int[16][30];
                map[i][j] = EMPTY;
                mineFalg = new int[16][30];
                mineFalg[i][j] = NO_FLAG;
            }
        }

        for (int i = 0; i < ROW; i++) {
    
    
            for (int j = 0; j < COL; j++) {
    
    
                addMineBut(i, j);
                addMineLab(i, j);
                mineJan[i][j].repaint();
            }
        }
        addMine();
        setIcon();
    }
    
	/*
	 * 重新开始游戏初始化方法
	 */
    private void init1() throws Exception {
    
    
        for (int i = 0; i < ROW; i++) {
    
    
            for (int j = 0; j < COL; j++) {
    
    
                map[i][j] = EMPTY;
                mineFalg[i][j] = NO_FLAG;
                mineLab[i][j].setText(null);
                mineLab[i][j].setBorder(BorderFactory.createEmptyBorder());
                mineJan[i][j].remove(mineJan[i][j]);
                mineJan[i][j].remove(mineBut[i][j]);
            }
        }

        for (int i = 0; i < ROW; i++) {
    
    
            for (int j = 0; j < COL; j++) {
    
    
                addMineBut(i, j);
                addMineLab(i, j);
                mineJan[i][j].repaint();
            }
        }
        addMine();
        setIcon();
    }
    
    /**
     * 添加雷按钮
     */
    private void addMineBut(int i, int j) {
    
    

        mineBut[i][j] = new JButton();
        mineBut[i][j].setName(i + "_" + j);
        mineBut[i][j].setFocusable(false);
        mineBut[i][j].addActionListener(buttonAc);
        mineBut[i][j].addMouseListener(mouseAC);
        mineJan[i][j].add(mineBut[i][j]);

    }
    
    /**
     * 添加雷标签
     */
    private void addMineLab(int i, int j) {
    
    
        mineLab[i][j] = new JLabel();
        mineLab[i][j].setHorizontalAlignment(JLabel.CENTER);
        mineLab[i][j].setBorder(BorderFactory.createMatteBorder(1, 1, 1, 1, Color.BLACK));//设置边框样式
        mineLab[i][j].setFont(fText);//设置Lab标签字体属性
    }

    /**
     * 随机生成雷的方法: 雷的表示:可以用二维数组的下标表示,所以可以用随机数Math.random()方法来生成随机横纵坐标,
     * random()方法是随机生成0~1之间的小数,但是不会生成0和1.所以random()*16就表示随机生成0~16之间的浮点数,不包括0和16
     * 因为坐标是整数类型,所以生成的随机横纵坐标需要强制转换为int类型 有一点需要注意的是,强制转换类型并不是四舍五入转换,例如:
     * random()*30生成的数的范围应该是0.0000000000000...1 ~ 29.999999999999999....
     * 强制转换为int类型之后,范围会变成0 ~ 29
     * 
     * @param mineNum:表示要生成雷的总数
     * @param row:表示随机雷的横坐标的int类型
     * @param col:表示随机雷的纵坐标的int类型
     * 
     */
    private void addMine() {
    
    
        for (int mineNum = 1; mineNum <= MINE_NUMBER_H;) {
    
    
            int row = (int) (Math.random() * 16);
            int col = (int) (Math.random() * 30);
            // 为了避免有相同位置的雷的生成,需要简单判断一下
            if (map[row][col] == EMPTY) {
    
    
                map[row][col] = MINE;
                mineLab[row][col].setText("雷");
                mineNum++;
            }
        }
    }

    /**
     * 为非雷按钮设置相对应的图标
     */
    private void setIcon() {
    
    
        for (int i = 0; i < ROW; i++) {
    
    
            for (int j = 0; j < COL; j++) {
    
    
                if (map[i][j] != MINE) {
    
    
                    int c = mN(i, j);
                    if (c == 0) {
    
    
                        mineLab[i][j].setText("0");
                    } else {
    
    
                        mineLab[i][j].setText(String.valueOf(c));
                    }
                }
            }
        }
    }
    
    /**
     * 计算每个格周围八个格的雷的总数量的方法,并返回int类型的雷的数量值
     */
    private int mN(int i, int j) {
    
    
        int count = 0; // 雷的数量
        int row = i;
        int col = j;
        for (int m = -1; m < 2; m++) {
    
    
            i = row;
            i += m;
            for (int n = -1; n < 2; n++) {
    
    
                j = col;
                j += n;
                if (i >= 0 && j >= 0 && i < 16 && j < 30) {
    
    
                    if (map[i][j] == MINE) {
    
    
                        count++;
                    }
                }
            }

        }
        return count;
    }
    
    private class MenuAction implements ActionListener {
    
    

        @Override
        public void actionPerformed(ActionEvent e) {
    
    
            String item = e.getActionCommand();
            if (item.equals("新游戏(N)")) {
    
    
                try {
    
    
                    init1();
                } catch (Exception e1) {
    
    
                    e1.printStackTrace();
                }
            }
            if (item.equals("退出(E)")) {
    
    
                System.exit(0);
            }
            if (item.equals("关于扫雷(A)")) {
    
    
                @SuppressWarnings("unused")
				int about = JOptionPane.showConfirmDialog(menuBar, "作者:Chen.\n时间:2022-10-31", "所有者",
                        JOptionPane.YES_OPTION);
            }
        }
    }
    
    /**
     * 按钮点击事件 每次点击按钮,都将按钮移除,显示标签。 1.如果是雷,则游戏结束 2.如果不是雷,就调用检查周围是否为空的方法isBlank()
     */
    private class ButtonAction implements ActionListener {
    
    

        @Override
        public void actionPerformed(ActionEvent e) {
    
    
            Object obj = e.getSource();
            String[] name = ((JButton) obj).getName().split("_");
            int i = Integer.parseInt(name[0]);
            int j = Integer.parseInt(name[1]);
            showLab(i, j);
            if (map[i][j] == MINE) {
    
    
//                flag = true;
                showMine();
                int msg = JOptionPane.showConfirmDialog(mineBut[i][j], "很不幸,你踩到雷了,是否再来一次", "消息",JOptionPane.YES_NO_OPTION);
                if (msg == JOptionPane.YES_OPTION) {
    
    
                    try {
    
    
                        init1();
                    } catch (Exception e1) {
    
    
                        e1.printStackTrace();
                    }
                } else {
    
    
                    System.exit(0);
                }

                return;
            }
            isBlank(i, j);
            isClean();
        }
    }
    
    private void showLab(int i, int j) {
    
    
        mineJan[i][j].remove(mineBut[i][j]);
        mineJan[i][j].add(mineLab[i][j]);
        mineJan[i][j].updateUI();
        mineJan[i][j].repaint();
    }
    
    /**
     * 显示所有的雷
     */
    private void showMine() {
    
    
        for (int i = 0; i < 16; i++) {
    
    
            for (int j = 0; j < 30; j++) {
    
    
                if (map[i][j] == MINE) {
    
    
                    showLab(i, j);
                }
            }
        }
    }
    
    /**
     * 判断剩余雷数
     */
    private void isClean() {
    
    
        int mineFound = MINE_NUMBER_H;
        for (int i = 0; i < ROW; i++) {
    
    
            for (int j = 0; j < COL; j++) {
    
    
                if (mineFalg[i][j] == IS_FLAG) {
    
    
                    mineFound--;
                }
            }
        }
        mineText.setText("" + mineFound);
        if (mineFound == 0) {
    
    
            flag = true;
            showMine();
        }
    }
    
    /**
     * 空白区自动翻开的方法:
     * 先检测该点是否为空,如果不是,则跳过方法。
     * 如果为空,则分别检测周围的8个空格,如果周围空格有空,则递归调用该方法,直到所有的连接空白区都
     * 被翻开
     */
    private void isBlank(int i, int j) {
    
    
        if (mN(i, j) == 0) {
    
    
            map[i][j] = CHECKED;
            int row = i;
            int col = j;
            for (int m = -1; m < 2; m++) {
    
    
                i = row;
                i += m;
                for (int n = -1; n < 2; n++) {
    
    
                    j = col;
                    j += n;
                    if (i >= 0 && j >= 0 && i < 16 && j < 30) {
    
    
                        if (mN(i, j) == 0) {
    
    
                            if (map[i][j] != MINE && map[i][j] != CHECKED) {
    
    
                                showLab(i, j);
                                isBlank(i, j);
                            }
                        } else {
    
    
                            showLab(i, j); // 检测到周围格子不为空,也要显示这个格子,前提这个格子不是雷
                        }
                    }
                }
            }
        }
    }
    
    /**
     * 鼠标右键事件: 当对按钮点击右键时,先判断该按钮是否已经被标记了: 1)如果没有,则标记上旗子,并将按钮设为不可点击,剩余雷数减1;
     * 2)如果标记了,则将按钮旗子移去,并将按钮设为可点击,剩余雷数加1
     */
    private class mouseAction implements MouseListener {
    
    
        @Override
        public void mouseClicked(MouseEvent e) {
    
    
            if (e.getButton() == MouseEvent.BUTTON3) {
    
    
                Object obj = e.getSource();
                JButton button = (JButton) e.getComponent();
                String[] name = ((JButton) obj).getName().split("_");
                int i = Integer.parseInt(name[0]);
                int j = Integer.parseInt(name[1]);
                if (mineFalg[i][j] == NO_FLAG) {
    
    
//                    button.setDisabledIcon(img(9));测试setDisabledIcon函数功能
                    button.setText("旗");
                    button.setFont(fMenu);//设置按钮上文字格式
                    button.setEnabled(false);
                    mineFalg[i][j] = IS_FLAG;
                } else if (mineFalg[i][j] == IS_FLAG) {
    
    
                    button.setText(null);
                    button.setEnabled(true);
                    mineFalg[i][j] = NO_FLAG;
                }
                isClean(); // 每次点击都需要检查雷数
            }

        }

        @Override
        public void mousePressed(MouseEvent e) {
    
    

        }

        @Override
        public void mouseReleased(MouseEvent e) {
    
    

        }

        @Override
        public void mouseEntered(MouseEvent e) {
    
    

        }

        @Override
        public void mouseExited(MouseEvent e) {
    
    

        }

    }
    
}



猜你喜欢

转载自blog.csdn.net/weixin_45345143/article/details/127623373