提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 开发环境
- 项目结构
- 下载地址:
- 一、运行画面展示
- 二、代码部分
- 1.代码
开发环境
开发工具: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) {
}
}
}