大家好!上一期我们使用GUI技术写了一个简单的扫雷小游戏,今天对这个Java应用程序更新迭代,增加了难度选择等功能,修复了已知的几个问题。成为初学者学习的好项目!Java实现扫雷小游戏【完整版】


文章目录

  • 效果展示
    • 难度选择展示
    • 游戏界面展示
  • 代码展示
    • 主类:GameWin类
    • 底层地图MapBottom类
    • 顶层地图MapTop类
    • 底层数字BottomNum类
    • 初始化地雷BottomRay类
    • 工具GameUtil类
    • 难度选择GameSelect类
  • 项目结构
  • 程序界面布局
  • 总结

效果展示

难度选择展示

游戏界面展示


代码展示

主类:GameWin类

//主类package com.sxt;import javax.swing.*;import java.awt.*;import java.awt.event.MouseAdapter;import java.awt.event.MouseEvent;public class GameWin extends JFrame {int width = 2 * GameUtil.OFFSET + GameUtil.MAP_W * GameUtil.SQUARE_LENGTH;int height = 4 * GameUtil.OFFSET + GameUtil.MAP_H * GameUtil.SQUARE_LENGTH;Image offScreenImage = null;MapBottom mapBottom = new MapBottom();MapTop mapTop = new MapTop();GameSelect gameSelect = new GameSelect();//是否开始,false未开始,true开始boolean begin=false;void launch(){GameUtil.START_TIME=System.currentTimeMillis();this.setVisible(true);if(GameUtil.state==3){this.setSize(500,500);}else {this.setSize(width,height);}this.setLocationRelativeTo(null);this.setTitle("Java扫雷小游戏");this.setDefaultCloseOperation(EXIT_ON_CLOSE);//鼠标事件this.addMouseListener(new MouseAdapter() {@Overridepublic void mouseClicked(MouseEvent e) {super.mouseClicked(e);switch (GameUtil.state){case 0 :if(e.getButton()==1){GameUtil.MOUSE_X = e.getX();GameUtil.MOUSE_Y = e.getY();GameUtil.LEFT = true;}if(e.getButton()==3) {GameUtil.MOUSE_X = e.getX();GameUtil.MOUSE_Y = e.getY();GameUtil.RIGHT = true;}case 1 :case 2 :if(e.getButton()==1){if(e.getX()>GameUtil.OFFSET + GameUtil.SQUARE_LENGTH*(GameUtil.MAP_W/2)&& e.getX()<GameUtil.OFFSET + GameUtil.SQUARE_LENGTH*(GameUtil.MAP_W/2) + GameUtil.SQUARE_LENGTH&& e.getY()>GameUtil.OFFSET&& e.getY()<GameUtil.OFFSET+GameUtil.SQUARE_LENGTH){mapBottom.reGame();mapTop.reGame();GameUtil.FLAG_NUM=0;GameUtil.START_TIME=System.currentTimeMillis();GameUtil.state=0;}}if(e.getButton()==2){GameUtil.state=3;begin=true;}break;case 3:if(e.getButton()==1){GameUtil.MOUSE_X = e.getX();GameUtil.MOUSE_Y = e.getY();begin = gameSelect.hard();}break;default:}}});while (true){repaint();begin();try {Thread.sleep(40);} catch (InterruptedException e) {e.printStackTrace();}}}void begin(){if(begin){begin=false;gameSelect.hard(GameUtil.level);dispose();GameWin gameWin = new GameWin();GameUtil.START_TIME = System.currentTimeMillis();GameUtil.FLAG_NUM=0;mapBottom.reGame();mapTop.reGame();gameWin.launch();}}@Overridepublic void paint(Graphics g) {if(GameUtil.state==3){g.setColor(Color.lightGray);g.fillRect(0,0,500,500);gameSelect.paintSelf(g);}else {offScreenImage = this.createImage(width, height);Graphics gImage = offScreenImage.getGraphics();//设置背景颜色gImage.setColor(Color.lightGray);gImage.fillRect(0, 0, width, height);mapBottom.paintSelf(gImage);mapTop.paintSelf(gImage);g.drawImage(offScreenImage, 0, 0, null);}}public static void main(String[] args) {GameWin gameWin = new GameWin();gameWin.launch();}}

底层地图MapBottom类

//底层地图类package com.sxt;import java.awt.*;public class MapBottom {BottomRay bottomRay = new BottomRay();BottomNum bottomNum = new BottomNum();{bottomRay.newRay();bottomNum.newNum();}//重置游戏void reGame(){for (int i = 1; i <=GameUtil.MAP_W ; i++) {for (int j = 1; j <=GameUtil.MAP_H ; j++) {GameUtil.DATA_BOTTOM[i][j]=0;}}bottomRay.newRay();bottomNum.newNum();}//绘制方法void paintSelf(Graphics g){g.setColor(Color.red);//画竖线for (int i = 0; i <= GameUtil.MAP_W; i++) {g.drawLine(GameUtil.OFFSET + i * GameUtil.SQUARE_LENGTH,3*GameUtil.OFFSET,GameUtil.OFFSET+i*GameUtil.SQUARE_LENGTH,3*GameUtil.OFFSET+GameUtil.MAP_H*GameUtil.SQUARE_LENGTH);}//画横线for (int i = 0; i <=GameUtil.MAP_H; i++){g.drawLine(GameUtil.OFFSET,3*GameUtil.OFFSET+i*GameUtil.SQUARE_LENGTH,GameUtil.OFFSET+GameUtil.MAP_W*GameUtil.SQUARE_LENGTH,3*GameUtil.OFFSET+i*GameUtil.SQUARE_LENGTH);}for (int i = 1; i <= GameUtil.MAP_W ; i++) {for (int j = 1; j <= GameUtil.MAP_H; j++) {//雷if (GameUtil.DATA_BOTTOM[i][j] == -1) {g.drawImage(GameUtil.lei,GameUtil.OFFSET + (i - 1) * GameUtil.SQUARE_LENGTH + 1,GameUtil.OFFSET * 3 + (j - 1) * GameUtil.SQUARE_LENGTH + 1,GameUtil.SQUARE_LENGTH - 2,GameUtil.SQUARE_LENGTH - 2,null);}//数字if (GameUtil.DATA_BOTTOM[i][j] >=0) {g.drawImage(GameUtil.images[GameUtil.DATA_BOTTOM[i][j]],GameUtil.OFFSET + (i - 1) * GameUtil.SQUARE_LENGTH + 15,GameUtil.OFFSET * 3 + (j - 1) * GameUtil.SQUARE_LENGTH + 5,null);}}}//绘制数字 剩余雷数,倒计时GameUtil.drawWord(g,""+(GameUtil.RAY_MAX-GameUtil.FLAG_NUM),GameUtil.OFFSET,2*GameUtil.OFFSET,30,Color.red);GameUtil.drawWord(g,""+(GameUtil.END_TIME-GameUtil.START_TIME)/1000,GameUtil.OFFSET + GameUtil.SQUARE_LENGTH*(GameUtil.MAP_W-1),2*GameUtil.OFFSET,30,Color.red);switch (GameUtil.state){case 0:GameUtil.END_TIME=System.currentTimeMillis();g.drawImage(GameUtil.face,GameUtil.OFFSET + GameUtil.SQUARE_LENGTH * (GameUtil.MAP_W/2),GameUtil.OFFSET,null);break;case 1:g.drawImage(GameUtil.win,GameUtil.OFFSET + GameUtil.SQUARE_LENGTH * (GameUtil.MAP_W/2),GameUtil.OFFSET,null);break;case 2:g.drawImage(GameUtil.over,GameUtil.OFFSET + GameUtil.SQUARE_LENGTH * (GameUtil.MAP_W/2),GameUtil.OFFSET,null);break;default:}}}

顶层地图MapTop类

//顶层地图类package com.sxt;import java.awt.*;public class MapTop {//格子位置int temp_x;int temp_y;//重置游戏void reGame(){for (int i = 1; i <=GameUtil.MAP_W ; i++) {for (int j = 1; j <=GameUtil.MAP_H ; j++) {GameUtil.DATA_TOP[i][j]=0;}}}//判断逻辑void logic(){temp_x=0;temp_y=0;if(GameUtil.MOUSE_X>GameUtil.OFFSET && GameUtil.MOUSE_Y>3*GameUtil.OFFSET){temp_x = (GameUtil.MOUSE_X - GameUtil.OFFSET)/GameUtil.SQUARE_LENGTH+1;temp_y = (GameUtil.MOUSE_Y - GameUtil.OFFSET * 3)/GameUtil.SQUARE_LENGTH+1;}if(temp_x>=1 && temp_x<=GameUtil.MAP_W&& temp_y>=1 && temp_y<=GameUtil.MAP_H){if(GameUtil.LEFT){//覆盖,则翻开if(GameUtil.DATA_TOP[temp_x][temp_y]==0){GameUtil.DATA_TOP[temp_x][temp_y]=-1;}spaceOpen(temp_x,temp_y);GameUtil.LEFT=false;}if(GameUtil.RIGHT){//覆盖则插旗if(GameUtil.DATA_TOP[temp_x][temp_y]==0){GameUtil.DATA_TOP[temp_x][temp_y]=1;GameUtil.FLAG_NUM++;}//插旗则取消else if(GameUtil.DATA_TOP[temp_x][temp_y]==1){GameUtil.DATA_TOP[temp_x][temp_y]=0;GameUtil.FLAG_NUM--;}else if(GameUtil.DATA_TOP[temp_x][temp_y]==-1){numOpen(temp_x,temp_y);}GameUtil.RIGHT=false;}}boom();victory();}//数字翻开void numOpen(int x,int y){//记录旗数int count=0;if(GameUtil.DATA_BOTTOM[x][y]>0){for (int i = x-1; i <=x+1 ; i++) {for (int j = y-1; j <=y+1 ; j++) {if(GameUtil.DATA_TOP[i][j]==1){count++;}}}if(count==GameUtil.DATA_BOTTOM[x][y]){for (int i = x-1; i <=x+1 ; i++) {for (int j = y-1; j <=y+1 ; j++) {if(GameUtil.DATA_TOP[i][j]!=1){GameUtil.DATA_TOP[i][j]=-1;}//必须在雷区当中if(i>=1&&j>=1&&i<=GameUtil.MAP_W&&j<=GameUtil.MAP_H){spaceOpen(i,j);}}}}}}//失败判定t 表示失败 f 未失败boolean boom(){if(GameUtil.FLAG_NUM==GameUtil.RAY_MAX){for (int i = 1; i <=GameUtil.MAP_W ; i++) {for (int j = 1; j <=GameUtil.MAP_H ; j++) {if(GameUtil.DATA_TOP[i][j]==0){GameUtil.DATA_TOP[i][j]=-1;}}}}for (int i = 1; i <=GameUtil.MAP_W ; i++) {for (int j = 1; j <=GameUtil.MAP_H ; j++) {if(GameUtil.DATA_BOTTOM[i][j]==-1&&GameUtil.DATA_TOP[i][j]==-1){GameUtil.state = 2;seeBoom();return true;}}}return false;}//失败显示void seeBoom(){for (int i = 1; i <=GameUtil.MAP_W ; i++) {for (int j = 1; j <=GameUtil.MAP_H ; j++) {//底层是雷,顶层不是旗,显示if(GameUtil.DATA_BOTTOM[i][j]==-1&&GameUtil.DATA_TOP[i][j]!=1){GameUtil.DATA_TOP[i][j]=-1;}//底层不是雷,顶层是旗,显示差错旗if(GameUtil.DATA_BOTTOM[i][j]!=-1&&GameUtil.DATA_TOP[i][j]==1){GameUtil.DATA_TOP[i][j]=2;}}}}//胜利判断t 表示胜利 f 未胜利boolean victory(){//统计未打开格子数int count=0;for (int i = 1; i <=GameUtil.MAP_W ; i++) {for (int j = 1; j <=GameUtil.MAP_H ; j++) {if(GameUtil.DATA_TOP[i][j]!=-1){count++;}}}if(count==GameUtil.RAY_MAX){GameUtil.state=1;for (int i = 1; i <=GameUtil.MAP_W ; i++) {for (int j = 1; j <=GameUtil.MAP_H ; j++) {//未翻开,变成旗if(GameUtil.DATA_TOP[i][j]==0){GameUtil.DATA_TOP[i][j]=1;}}}return true;}return false;}//打开空格void spaceOpen(int x,int y){if(GameUtil.DATA_BOTTOM[x][y]==0){for (int i = x-1; i <=x+1 ; i++) {for (int j = y-1; j <=y+1 ; j++) {//覆盖,才递归if(GameUtil.DATA_TOP[i][j]!=-1){if(GameUtil.DATA_TOP[i][j]==1){GameUtil.FLAG_NUM--;}GameUtil.DATA_TOP[i][j]=-1;//必须在雷区当中if(i>=1&&j>=1&&i<=GameUtil.MAP_W&&j<=GameUtil.MAP_H){spaceOpen(i,j);}}}}}}//绘制方法void paintSelf(Graphics g){logic();for (int i = 1; i <= GameUtil.MAP_W ; i++) {for (int j = 1; j <= GameUtil.MAP_H; j++) {//覆盖if (GameUtil.DATA_TOP[i][j] == 0) {g.drawImage(GameUtil.top,GameUtil.OFFSET + (i - 1) * GameUtil.SQUARE_LENGTH + 1,GameUtil.OFFSET * 3 + (j - 1) * GameUtil.SQUARE_LENGTH + 1,GameUtil.SQUARE_LENGTH - 2,GameUtil.SQUARE_LENGTH - 2,null);}//插旗if (GameUtil.DATA_TOP[i][j] == 1) {g.drawImage(GameUtil.flag,GameUtil.OFFSET + (i - 1) * GameUtil.SQUARE_LENGTH + 1,GameUtil.OFFSET * 3 + (j - 1) * GameUtil.SQUARE_LENGTH + 1,GameUtil.SQUARE_LENGTH - 2,GameUtil.SQUARE_LENGTH - 2,null);}//差错旗if (GameUtil.DATA_TOP[i][j] == 2) {g.drawImage(GameUtil.noflag,GameUtil.OFFSET + (i - 1) * GameUtil.SQUARE_LENGTH + 1,GameUtil.OFFSET * 3 + (j - 1) * GameUtil.SQUARE_LENGTH + 1,GameUtil.SQUARE_LENGTH - 2,GameUtil.SQUARE_LENGTH - 2,null);}}}}}

底层数字BottomNum类

//底层数字类package com.sxt;public class BottomNum {void newNum() {for (int i = 1; i <=GameUtil.MAP_W ; i++) {for (int j = 1; j <=GameUtil.MAP_H ; j++) {if(GameUtil.DATA_BOTTOM[i][j]==-1){for (int k = i-1; k <=i+1 ; k++) {for (int l = j-1; l <=j+1 ; l++) {if(GameUtil.DATA_BOTTOM[k][l]>=0){GameUtil.DATA_BOTTOM[k][l]++;}}}}}}}}

初始化地雷BottomRay类

//初始化地雷类package com.sxt;public class BottomRay {//存放坐标static int[] rays = new int[GameUtil.RAY_MAX*2];//地雷坐标int x,y;//是否放置 T 表示可以放置 F 不可放置boolean isPlace = true;//生成雷void newRay() {for (int i = 0; i < GameUtil.RAY_MAX*2 ; i=i+2) {x= (int) (Math.random()*GameUtil.MAP_W +1);//1-12y= (int) (Math.random()*GameUtil.MAP_H +1);//1-12//判断坐标是否存在for (int j = 0; j < i ; j=j+2) {if(x==rays[j] && y==rays[j+1]){i=i-2;isPlace = false;break;}}//将坐标放入数组if(isPlace){rays[i]=x;rays[i+1]=y;}isPlace = true;}for (int i = 0; i < GameUtil.RAY_MAX*2; i=i+2) {GameUtil.DATA_BOTTOM[rays[i]][rays[i+1]]=-1;}}}

工具GameUtil类

//工具类,存放静态参数,工具方法package com.sxt;import java.awt.*;public class GameUtil {//地雷个数static int RAY_MAX = 100;//地图的宽static int MAP_W = 36;//地图的高static int MAP_H = 17;//雷区偏移量static int OFFSET = 45;//格子边长static int SQUARE_LENGTH = 50;//插旗数量static int FLAG_NUM = 0;//鼠标相关//坐标static int MOUSE_X;static int MOUSE_Y;//状态static boolean LEFT = false;static boolean RIGHT = false;//游戏状态 0 表示游戏中 1 胜利 2 失败 3 难度选择static int state = 3;//游戏难度static int level;//倒计时static long START_TIME;static long END_TIME;//底层元素-1 雷 0 空 1-8 表示对应数字static int[][] DATA_BOTTOM = new int[MAP_W+2][MAP_H+2];//顶层元素-1 无覆盖 0 覆盖 1 插旗 2 差错旗static int[][] DATA_TOP = new int[MAP_W+2][MAP_H+2];//载入图片static Image lei = Toolkit.getDefaultToolkit().getImage("imgs/lei.png");static Image top = Toolkit.getDefaultToolkit().getImage("imgs/top.gif");static Image flag = Toolkit.getDefaultToolkit().getImage("imgs/flag.gif");static Image noflag = Toolkit.getDefaultToolkit().getImage("imgs/noflag.png");static Image face = Toolkit.getDefaultToolkit().getImage("imgs/face.png");static Image over = Toolkit.getDefaultToolkit().getImage("imgs/over.png");static Image win = Toolkit.getDefaultToolkit().getImage("imgs/win.png");static Image[] images = new Image[9];static {for (int i = 1; i <=8 ; i++) {images[i] = Toolkit.getDefaultToolkit().getImage("imgs/num/"+i+".png");}}static void drawWord(Graphics g,String str,int x,int y,int size,Color color){g.setColor(color);g.setFont(new Font("仿宋",Font.BOLD,size));g.drawString(str,x,y);}}

难度选择GameSelect类

//难度选择类package com.sxt;import javax.swing.*;import java.awt.*;public class GameSelect{//判断是否点击到难度boolean hard(){if(GameUtil.MOUSE_X>100&&GameUtil.MOUSE_X<400){if(GameUtil.MOUSE_Y>50&&GameUtil.MOUSE_Y<150){GameUtil.level=1;GameUtil.state=0;return true;}if(GameUtil.MOUSE_Y>200&&GameUtil.MOUSE_Y<300){GameUtil.level=2;GameUtil.state=0;return true;}if(GameUtil.MOUSE_Y>350&&GameUtil.MOUSE_Y<450){GameUtil.level=3;GameUtil.state=0;return true;}}return false;}void paintSelf(Graphics g){g.setColor(Color.BLACK);g.drawRoundRect(100,50,300,100,40,40);g.setColor(Color.GRAY);g.fillRoundRect(100,50,300,100,40,40);GameUtil.drawWord(g,"简单模式",185,110,30,Color.black);g.drawRoundRect(100,200,300,100,40,40);g.setColor(Color.CYAN);g.fillRoundRect(100,200,300,100,40,40);GameUtil.drawWord(g,"中等模式",185,260,30,Color.black);g.drawRoundRect(100,350,300,100,40,40);g.setColor(Color.PINK);g.fillRoundRect(100,350,300,100,40,40);GameUtil.drawWord(g,"困难模式",185,410,30,Color.black);}void hard(int level){switch (level){case 1:GameUtil.RAY_MAX = 10;GameUtil.MAP_W = 9;GameUtil.MAP_H = 9;break;case 2:GameUtil.RAY_MAX = 25;GameUtil.MAP_W = 14;GameUtil.MAP_H = 14;break;case 3:GameUtil.RAY_MAX = 45;GameUtil.MAP_W = 20;GameUtil.MAP_H = 14;break;default:}}}

项目结构


本程序共封装了六个类,分别是主类GameWin类,绘制底层地图和绘制顶层地图的类MapBottom类和MapTop类,绘制底层数字的类BottomNum类,以及初始化地雷的BottomRay类和工具GameUtil类,用于存静态参数和方法,最后用于难度选择的方法封装在GameSelect类中。


程序界面布局

不同的难度雷区格子数不同!


总结

游戏的设计类似windows扫雷,用户在图形化用户界面内利用鼠标监听事件标记雷区,左上角表示剩余雷的数量,右上角动态显示使用的时间。用户可选择中间组件按钮重新游戏。

为了解决程序窗口闪动的问题,本程序采用了双缓冲技术。

在使用Java编写扫雷小游戏时遇到了很多问题,在解决问题时,确实对java的面向对象编程有了更加深入的理解。虽然GUI现在并没有很大的市场,甚至好多初学者已经放弃了学习GUI,但是利用GUI编程的过程对于培养编程兴趣,深入理解Java编程有很大的作用。


本程序是初学者练习的好项目,欢迎大家指正!