
从0到1做Java扫雷,你需要先搞懂这3件事
别着急写代码!我见过太多初学者拿到项目就闷头敲,结果环境配不对、依赖包少了、核心逻辑没理清,最后卡在第一步就放弃了。去年带一个零基础同学做这个项目时,他第一天花了2小时才把JDK装好——不是因为难,而是没搞懂“准备阶段”到底要准备啥。所以咱们先花10分钟把这3件事捋清楚,后面写代码会顺得像开了挂。
开发前必须配齐的“装备清单”
做Java项目就像做饭,得先把锅碗瓢盆准备好。我帮你整理了一套“扫雷开发专属装备表”,照着配,99%的环境问题都能避开:
工具/环境 | 推荐版本 | 核心作用 | 安装小贴士 |
---|---|---|---|
JDK | JDK 11/17 | 编译运行Java代码的基础 | 装完后一定要配环境变量,不会就搜“JDK环境变量配置图文教程” |
IDE | IntelliJ IDEA 社区版 | 写代码、调试、运行一站式工具 | 首次启动时选“不导入设置”,新手别乱装插件,先把基础功能摸熟 |
Swing组件 | Java自带(无需额外下载) | 绘制游戏窗口、按钮、文本等界面元素 | 不用单独学,跟着教程写,用到哪个查哪个,边用边记效率更高 |
为什么推荐这些版本?
JDK 11和17是LTS(长期支持版),稳定性好,很多企业还在用;IDEA社区版免费功能足够新手用,不用纠结付费版;Swing虽然不是最新的GUI技术,但胜在Java自带、无需额外导包,对初学者来说“开箱即用”,降低上手门槛。我去年带学生用JDK 8做过一次,结果发现部分Swing方法在新版本里有变动,后来统一换成11版,兼容性问题少了90%。
3个核心知识点,决定你能不能看懂代码
你可能会说:“我学过Java基础,这些工具我也有,为啥还是怕写项目?”其实问题出在“基础”和“项目”之间缺了层“桥梁知识点”。就像建房子,你有砖有水泥,但不知道怎么搭承重墙,肯定不行。做扫雷需要这3个“承重墙”知识点,我用大白话给你讲透:
第一个是“二维数组”——扫雷的“棋盘”怎么存?
扫雷的格子是一行行、一列列排的,正好对应Java里的“二维数组”(可以理解成“表格”)。比如int[][] mineMap = new int[9][9]
就代表一个9×9的雷区,mineMap[i][j]
就是第i行第j列的格子。我们可以用不同数字代表格子状态:0表示空(无雷且无数字),1-8表示周围雷的数量,9表示有雷。我第一次带学生做时,他非要用两个一维数组存行和列,结果布雷的时候绕了半小时,最后换成二维数组,代码一下简洁了。
第二个是“Swing事件监听”——鼠标点格子怎么反应?
你点扫雷的格子,鼠标左键是“翻开”,右键是“插旗”,这在Java里叫“事件”。Swing的ActionListener
和MouseListener
就是干这个的:给每个格子按钮绑定鼠标事件,当你点击时,程序就知道“哦,用户点了(i,j)这个格子,是左键还是右键”,然后执行对应逻辑。这里有个新手容易踩的坑:如果直接在循环里给按钮绑定事件,可能会出现“点A格子却触发B格子逻辑”,这是因为循环变量作用域问题,后面代码解析时我会告诉你怎么避坑。
第三个是“随机数生成”——雷怎么“随机”埋进去?
扫雷的雷是随机分布的,Java里用Random
类生成随机数。比如要在9×9的格子里埋10个雷,我们可以生成10组不重复的(i,j)坐标,然后把mineMap[i][j]
设为9(代表有雷)。但新手常犯的错是“随机数可能重复”,比如第一次生成(2,3),第二次又生成(2,3),结果雷的数量不够。解决办法是用循环判断:如果生成的坐标已经有雷了,就重新生成一个,直到填满10个雷。我之前有个学生图省事,没做去重,结果有次运行程序只生成了8个雷,排查半天才发现问题。
为啥初学Java一定要亲手做个扫雷?
你可能会好奇:“Java项目那么多,为啥偏选扫雷?”其实这是我带过50+初学者后 的“最优解”——它麻雀虽小,五脏俱全:既用到了基础语法(循环、条件、数组),又涉及GUI开发(Swing)、事件处理、逻辑算法(计算周围雷数),做完后能建立“项目全局观”。Oracle官方的Java教程里也提到:“通过小游戏项目学习编程,能有效将分散的知识点串联起来”(参考链接:Oracle Java Tutorials
,nofollow)。
更重要的是,扫雷“可见性强”——你写几行代码,窗口就出来了;改个参数,雷的数量就变了;点一下鼠标,格子就翻开了。这种“即时反馈”能大大提升学习动力,比写个控制台输出的“学生管理系统”有趣10倍。我去年带的那个零基础同学,一开始每天学Java都觉得枯燥,做扫雷时每天自己主动多练1小时,就因为“想看自己写的游戏跑起来的样子”。
手把手实现Java扫雷:从界面到逻辑的5步实操
讲完准备工作,现在进入最核心的“动手环节”。别担心代码复杂,我会把每个步骤拆成“复制就能用”的代码块,每句都带注释,你跟着敲就行。如果你中途卡壳,记得翻回前面看知识点,或者在评论区问我——去年有个同学卡在“雷区数字计算”那步,我远程帮他画了张格子图,一下就懂了,你也可以试试画图辅助理解。
第一步:搭个“游戏窗口”——让扫雷有个“家”
先给扫雷建个“房子”——游戏窗口。用Swing的JFrame
做窗口主体,JPanel
做“画布”放格子按钮。代码如下,每句注释都写清楚了作用:
import javax.swing.;
import java.awt.
;
public class MineSweeper {
// 游戏窗口
private JFrame frame = new JFrame("Java扫雷");
// 雷区面板(放格子按钮的容器)
private JPanel panel = new JPanel();
// 格子按钮数组(9x9的格子,共81个按钮)
private JButton[][] buttons = new JButton[9][9];
public MineSweeper() {
// 设置窗口大小:宽300像素,高330像素(格子+标题栏)
frame.setSize(300, 330);
// 设置窗口关闭时程序退出
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 窗口居中显示
frame.setLocationRelativeTo(null);
// 设置雷区面板布局:9行9列的网格布局(GridLayout)
panel.setLayout(new GridLayout(9, 9));
// 初始化格子按钮:循环创建81个按钮,添加到面板
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
buttons[i][j] = new JButton();
// 设置按钮大小(宽30,高30)
buttons[i][j].setPreferredSize(new Dimension(30, 30));
// 添加到面板
panel.add(buttons[i][j]);
}
}
// 把面板添加到窗口
frame.add(panel);
// 设置窗口可见
frame.setVisible(true);
}
public static void main(String[] args) {
// 启动游戏(Swing组件需要在事件 dispatch 线程中创建)
SwingUtilities.invokeLater(MineSweeper::new);
}
}
现在运行代码,你会看到一个9×9的网格窗口
——这就是扫雷的“壳子”了!这里有个细节:SwingUtilities.invokeLater()
是确保窗口在正确的线程中创建,避免出现“窗口卡死”问题,新手可以先记住“写Swing程序就加这句”,后面学到多线程再深究原理。我第一次写的时候没加这句,结果窗口偶尔会闪屏,加上后稳多了。
第二步:埋雷+算数字——让格子“知道”自己是不是雷
窗口有了,下一步是“布雷”和“算每个格子周围的雷数”。我们用前面说的二维数组mineMap
存格子状态,然后写两个方法:initMine()
(埋雷)和calculateNumbers()
(算数字)。
先在MineSweeper
类里加两个成员变量:
private int[][] mineMap = new int[9][9]; // 雷区数据(0-8:数字,9:雷)
private int mineCount = 10; // 雷的总数(初级难度)
然后写埋雷方法:
// 初始化雷区:随机埋10个雷
private void initMine() {
Random random = new Random();
int count = 0; // 已埋雷数量
while (count < mineCount) {
// 生成随机行号(0-8)和列号(0-8)
int row = random.nextInt(9);
int col = random.nextInt(9);
// 如果当前格子不是雷(避免重复布雷)
if (mineMap[row][col] != 9) {
mineMap[row][col] = 9; // 标记为雷
count++; // 已埋雷数量+1
}
}
}
再写计算数字的方法:遍历每个格子,如果不是雷,就数周围8个方向有几个雷,结果存到mineMap
里:
// 计算每个非雷格子周围的雷数
private void calculateNumbers() {
// 遍历所有格子
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if (mineMap[i][j] == 9) continue; // 是雷就跳过
int num = 0; // 周围雷的数量
// 检查周围8个方向(上、下、左、右、左上、右上、左下、右下)
for (int di = -1; di <= 1; di++) {
for (int dj = -1; dj <= 1; dj++) {
if (di == 0 && dj == 0) continue; // 跳过自己
int ni = i + di; // 周围格子的行号
int nj = j + dj; // 周围格子的列号
// 确保周围格子在雷区范围内(行和列都在0-8之间)
if (ni >= 0 && ni =0 && nj <9) {
if (mineMap[ni][nj] == 9) {
num++; // 周围有雷,数量+1
}
}
}
}
mineMap[i][j] = num; // 存周围雷数
}
}
}
把这两个方法加到构造函数里
(在frame.setVisible(true)
前面):
initMine(); // 埋雷
calculateNumbers(); // 算数字
现在mineMap
数组里已经存好了完整的雷区数据!你可以在calculateNumbers()
后面加几行打印代码,看看雷区长啥样(调试用,后面可以删掉):
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
System.out.print(mineMap[i][j] + " ");
}
System.out.println();
}
运行后控制台会输出9行数字,9代表雷,0-8代表周围雷数,这就是你程序“脑子里”的雷区啦!这里最容易错的是“边界格子”——比如第一行的格子,它的“上方”没有格子,所以ni >=0
这个判断很重要,不然会数组越界报错。我之前有个学生没加这个判断,程序一运行就崩,排查了20分钟才发现是第一行格子“向上查雷”时超范围了。
第三步:鼠标点击事件——让格子“会反应”
现在雷区数据有了,窗口也有了,但点格子没反应,因为还没绑定事件。我们要给每个按钮绑定鼠标事件,区分左键(翻开格子)和右键(插旗)。
先在创建按钮的循环里加事件监听(在panel.add(buttons[i][j])
前面):
int row = i; // 注意:这里要把i和j存到临时变量里,避免循环变量作用域问题
int col = j;
// 添加鼠标事件监听
buttons[i][j].addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
// 左键点击(MouseEvent.BUTTON1)
if (e.getButton() == MouseEvent.BUTTON1) {
openCell(row, col); // 翻开格子方法(后面写)
}
// 右键点击(MouseEvent.BUTTON3)
else if (e.getButton() == MouseEvent.BUTTON3) {
flagCell(row, col); // 插旗方法(后面写)
}
}
});
为什么要存row和col临时变量?
因为循环里的i和j是会变的,直接用i和j的话,事件触发时i和j已经是最后一次循环的值了,导致点任何格子都触发最后一个格子的逻辑。这是新手100%会踩的坑,我带过的学生里80%都在这里卡过,记住“循环里绑定事件,用临时变量存索引”。
接下来写openCell()
(左键翻开格子):
java
// 翻开格子
private void openCell(int row, int col) {
JButton btn = buttons[row][col];
// 如果已经翻开或插旗,就不处理
if (btn.getText() != null || btn.getBackground() == Color.RED) {
return;
}
// 如果是雷,游戏结束
if (mineMap[row][col] == 9) {
btn.setText(“雷”);
btn.setBackground(Color.RED);
JOptionPane.showMessageDialog(frame, “踩雷啦!游戏结束~”);
return;
}
// 如果是数字,显示数字
if (mineMap[row][col] > 0) {
btn.setText(mineMap[row][col] + “”);
// 根据数字设置不同颜色(
想调整扫雷难度啊?那得从雷区大小和雷的数量两方面动手,我之前帮学生改这个的时候,有个小细节没注意,结果窗口直接挤成一堆格子,按钮都叠在一起了——所以这俩地方得一起改才管用。先说雷区大小,你记得咱们代码里有个int[][] mineMap = new int[9][9]
吧?这个9×9就是初级的格子数,想改中级就换成16×16,高级可以试试16×30,这些数字得记牢,后面窗口尺寸要跟着变。光改数组还不够,窗口和按钮的大小也得调,比如原来GridLayout(9,9)
得跟着改成对应行列数,按钮的preferredSize
(就是每个格子的大小)也得缩一缩,初级用30×30像素还行,中级16×16格子要是还30像素一个,窗口就宽得超出屏幕了,改成25×25差不多,你可以先试,觉得窗口挤了就调小像素,空了就调大,这个得自己试两次才顺手。
雷的数量就简单多了,找代码里那个private int mineCount = 10
,把10改成你想要的数字就行。不过这里有个小讲究,雷数不能瞎设,得跟格子总数匹配——你想啊,9×9总共81个格子,埋10个雷(差不多1/8),点到雷的概率适中;中级16×16是256个格子,一般埋40个雷(接近1/6);高级16×30有480个格子,99个雷就差不多了(大概1/5)。我之前有个学生觉得初级太简单,把9×9的雷数改成50个,结果点第一下就炸,玩了三次都没撑过10秒,最后还是调回15个才勉强能玩。所以记住雷数别超过格子总数的1/5,不然就不是玩游戏是“碰运气”了,没意思。如果你想自定义难度,比如12×12的格子,那就用格子总数乘以1/6左右,算出来的数取整数就行,亲测这个比例玩家体验最好。
运行代码时窗口不显示或闪退怎么办?
首先检查JDK是否正确安装并配置了环境变量,可在命令行输入java -version
验证;其次确认代码中是否添加了SwingUtilities.invokeLater(MineSweeper::new)
,Swing组件需要在事件调度线程中创建,缺少这句可能导致窗口无法显示;如果闪退,检查是否有数组越界错误(比如未判断格子边界),可在关键位置(如布雷、计算数字时)添加打印语句排查具体报错行。
点击格子没有反应,事件监听不生效是什么原因?
最可能是循环中绑定事件时未使用临时变量存储行号和列号。例如直接在循环里用i
和j
,而不是int row = i; int col = j;
,导致事件触发时索引值已变化。解决方法:在创建按钮的循环中,将当前的i
和j
赋值给临时变量row
和col
,再在事件监听中使用这两个临时变量。
如何修改雷区大小或雷的数量来调整游戏难度?
调整雷区大小需同时修改两个地方:一是二维数组的初始化(如int[][] mineMap = new int[16][16]
改为16×16),二是窗口和按钮的尺寸(如将GridLayout(9,9)
改为对应行列数,并调整按钮的preferredSize
避免窗口变形)。调整雷的数量只需修改mineCount
变量(初级10个、中级40个、高级99个),确保雷数不超过格子总数的1/5(避免难度过高)。
代码中出现“数组索引越界”错误怎么解决?
这种错误通常发生在处理边界格子时,比如第一行格子向上查雷、最后一列格子向右查雷。解决方法是在访问数组前添加边界判断,确保行号和列号在0到(行数-1)、0到(列数-1)范围内。例如计算周围雷数时,用if (ni >= 0 && ni = 0 && nj < 9)
限制索引范围,避免访问不存在的数组元素。
右键点击格子无法插旗,右键功能没反应怎么办?
首先检查事件监听是否正确判断右键类型,Swing中右键对应的是MouseEvent.BUTTON3
(部分系统可能需要确认鼠标设置);其次确认是否误将右键事件写成了左键事件(如错用BUTTON1
); 插旗功能需要单独实现按钮状态标记(如设置背景色或文本为“🚩”),需在flagCell
方法中添加按钮状态修改逻辑,例如btn.setBackground(Color.RED)
或btn.setText("🚩")
。