资讯动态

使用Java Swing实现简单的扫雷游戏

发布时间:2026/10/7 2:46:20 来源:尧图企业网站定制
Java Swing实现扫雷游戏前言这是使用Java Swing制作扫雷游戏的笔记。本篇记录怎么让系统自动布雷绘制基础面板面板重绘、以及使用鼠标监听器实现翻开和插旗。环境JDK 17IntelliJ IDEA纯 Java 项目未使用 Maven一、整体设计整个游戏只有一个类但它同时扮演两个角色publicclasshomework0927_2extendsJPanelimplementsMouseListener{extends JPanel它本身就是一块面板负责把雷区画出来,其中extends继承可以使用JPanel的所有方法同时拥有其全部属性implements MouseListener它同时是鼠标监听器负责处理点击其中implements表示实现其后接接口表示将会实现接口所规定的所有方法。入口publicstaticvoidmain(String[]args){homework0927_2 msnewhomework0927_2();ms.initUI();}二、两个核心数组2.1 thunderArr存雷和数字publicint[][]thunderArrnewint[LINE-1][LINE-1];约定-1为雷、0为空白、0为周围雷数。2.2 state存每格的状态publicint[][]statenewint[LINE-1][LINE-1];约定0表示未翻开、1表示已翻开、2表示插旗。使用两个数组分开记录底层绘制的雷和数字属性以及顶层的是否绘制“盖子”以及旗帜这两个两件独立的事同时Java 用new创建的数组会自动全填 0不像C是随机值因此state不用手动初始化刚好与初始所有格子均盖上符合。三、绘制继承 JPanel 重写 paint3.1 为什么不用 getGraphicsgetGraphics()拿画笔画一次但这样窗口一改变大小或出现其他操作时画面就无法即时更新而paint()方法则可以在需要重绘时被自动调用窗口显示、改变大小、重新露出……都会。所以改成重写 paint把绘制的内容写进去界面需要变化时自动重绘。3.2 paint 的内容publicvoidpaint(Graphicsg){super.paint(g);//先调用父类完成基础绘画工作for(inti0;iLINE;i){//循环绘制网格g.drawLine(X0,Y0i*40,(LINE-1)*SIZEX0,Y0i*SIZE);g.drawLine(X0i*SIZE,Y0,X0i*SIZE,(LINE-1)*SIZEY0);}if(start){//仅在第一次绘制时生效也就是创建随机雷组randomThunder();thunderNumber();startfalse;//布尔值变为false此后不再调用}drawThunder(g);//绘制底层雷和数字}3.3 start 标记的作用paint会被反复调用而雷区则会在第一次绘制前就被确定因此仅在第一次时start设为true设置雷区此后设置雷区的代码将不再被调用保证布雷和算数字只做一次做完就把它设成false。四、布雷与算数字4.1 随机布雷避免重复publicvoidrandomThunder(){intnum0;//记录当前雷数while(numLINE){//要放LINE 颗雷intx(int)(Math.random()*thunderArr.length);//随机雷坐标inty(int)(Math.random()*thunderArr.length);if(thunderArr[y][x]!-1){//验证是否位置重复thunderArr[y][x]-1;num;//只有不重复时才增加计数}}}4.2 给雷周围填数字publicvoidthunderNumber(){for(inti0;ithunderArr.length;i){for(intj0;jthunderArr[0].length;j){if(thunderArr[i][j]-1){//判定地雷markThunder(j,i);//让雷周围8格数字1}}}}publicvoidmarkThunder(intx,inty){//x列y行if(x1thunderArr.lengththunderArr[y][x1]!-1)thunderArr[y][x1]1;if(x-10thunderArr[y][x-1]!-1)thunderArr[y][x-1]1;...其余6个方向...}关键每一格的判定都有两个条件①不越界 ②相邻格不是雷即! -1。防止相邻的雷互相1导致变为0雷被抹掉五、把每格画出来drawThunder采用的是先画内容再盖盖子的思路publicvoiddrawThunder(Graphicsg){for(inti0;ithunderArr.length;i){for(intj0;jthunderArr[0].length;j){if(thunderArr[i][j]0){//数字格g.setFont(newFont(Arial,Font.BOLD,24));g.drawString(thunderArr[i][j],j*SIZEX017,(i1)*SIZEY0-17);//此处原本需要一个字符串然而我们能读到的只有数组中的int类型因此使用java中任何类型与string拼接都会转变为string加上一个空白字符串转化为string}if(thunderArr[i][j]-1){//雷黑圆g.setColor(Color.black);g.fillOval(j*SIZEX010,i*SIZEY010,SIZE-20,SIZE-20);}if(state[i][j]!1){//没翻开则盖盖子g.setColor(Color.LIGHT_GRAY);g.fillRect(j*SIZEX01,i*SIZEY01,SIZE-2,SIZE-2);}if(state[i][j]2){//插旗 → 红矩形g.setColor(Color.red);g.fillRect(j*SIZEX012,i*SIZEY012,SIZE-24,SIZE-24);}}}}数字和雷总是先画然后没翻开的格子会被灰色盖子盖住默认状态下看不见翻开后state1不再画盖子下面的数字/雷就露出来了若插旗则在盖子上再插旗坐标换算第 j 列、第 i 格左上角是X0 j*SIZE、Y0 i*SIZE。六、鼠标交互6.1 注册监听器类声明加了implements MouseListener然后在窗口里把自己注册为监听器this.addMouseListener(this);第一个this是被监听的面板第二个this是负责处理的监听器——自己监听自己。6.2 坐标 → 行列换算intx(e.getX()-X0)/SIZE;// 列inty(e.getY()-Y0)/SIZE;// 行整数除法直接得到第几格再用e.getX() X0 ...把网格外的点击挡掉仅接受格子范围内的点击。6.3 左键翻开、右键插旗、踩雷重开publicvoidmousePressed(MouseEvente){intx(e.getX()-X0)/SIZE;inty(e.getY()-Y0)/SIZE;if(e.getX()X0e.getY()Y0xthunderArr.lengthythunderArr.length){if(e.getButton()MouseEvent.BUTTON1){//左键if(state[y][x]0){if(thunderArr[y][x]-1){//踩雷state[y][x]1;// 展示踩中的雷repaint();JOptionPane.showMessageDialog(this,失败点击确认重新开始);restart();}else{openCell(y,x);//安全格 → 翻开}}check();}if(e.getButton()MouseEvent.BUTTON3){//右键if(state[y][x]0){state[y][x]2;//插旗}elseif(state[y][x]2){state[y][x]0;//取消旗}check();}}repaint();//重新绘制}6.4 空白格自动展开openCell递归点开的格子如果是空白周围没雷应该把它周围的格子也翻开一层层扩散publicvoidopenCell(introw,intcol){if(row0||rowthunderArr.length||col0||colthunderArr[0].length)return;//越界停仅在格子范围内递归if(state[row][col]!0)return;//已翻开/已插旗也触发停止state[row][col]1;//先翻开自己if(thunderArr[row][col]0){//自己是空白才向四周扩散openCell(row-1,col-1);openCell(row-1,col);openCell(row-1,col1);openCell(row,col-1);openCell(row,col1);openCell(row1,col-1);openCell(row1,col);openCell(row1,col1);}}这里用到了递归。边界和已翻开两个return保证了不会无限递归。6.5 失败与胜利踩雷时弹窗提示并restart()重开publicvoidrestart(){for(inti0;ithunderArr.length;i){for(intj0;jthunderArr[0].length;j){thunderArr[i][j]0;//清空雷数组state[i][j]0;//清空状态数组}}starttrue;//重置start状态下次绘制时重新随机雷组repaint();}胜利判断所有雷都插了旗、且所有非雷格都翻开了publicvoidcheck(){for(inti0;iLINE-1;i){for(intj0;jLINE-1;j){if(thunderArr[i][j]-1){if(state[i][j]!2)return;//若还有没插旗的雷则不触发操作}else{if(state[i][j]!1)return;//有非雷格没翻开不触发操作}}}JOptionPane.showMessageDialog(this,恭喜你成功了点击确认重新开始);//前面没有触发return说明所有格子验证通过restart();}七、错误记录相邻雷被抹掉markThunder一开始漏了! -1两颗雷相邻时一颗会给另一颗1把它从-1变成0。加上判断才对。空白展开会无限递归一开始没加已翻开就停的判断相邻空白互相调用出现无限递归错误。八、总结本篇用 Swing 做了一个基本能够游玩的扫雷二维数组存雷区、paint重绘保证画面正确、MouseListener处理左键翻开和右键插旗、递归实现空白自动展开并添加了胜负判断。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑