Java实现扫雷小游戏【完整版】大团子限定

大家好,我是黄小黄同学,学习Java已经有一段时间啦,今天我们来使用 Java的Swing技术与IO流 实现大团子限定版扫雷小游戏吧!本文内容为详细教程及完整代码实现, 文末附扫雷程序完整资源(源码与图片素材),供大家免费下载学习!
Java实现扫雷小游戏【完整版】大团子限定_第1张图片


文章目录

  • 1 系统概述
  • 2 需求分析
  • 3 总体设计
    • 3.1 系统总体功能设计
    • 3.2 系统总体流程设计
  • 4 系统实现
    • 4.1 Properties类
    • 4.2 Bottom类
    • 4.3 ShowBottomCount类
    • 4.4 PaintBottomArea类
    • 4.5 Cover类
    • 4.6 MinTime类
    • 4.7 GamePanel类
  • 5 结果展示
    • 5.1 扫雷游戏界面的整体设计
    • 5.2 扫雷游戏失败判定部分的设计
    • 5.3 扫雷游戏胜利判定部分的设计
  • 6 完整资源


1 系统概述

本次扫雷游戏程序的设计参考Windows XP系统提供的扫雷游戏,实现扫雷的基本游戏功能:游戏的失败判定、游戏的胜利判定、游戏中表示周围雷数的数字生成、地雷的随机生成、游戏的重置、剩余雷数的展示功能、标记地雷功能、计时功能、历史最高分展示功能等,并对界面进行个性化美化。其中必要的游戏组件部分,如:地雷、表示地雷数量的数字、标记用的独角兽、控制游戏开始与结束的团子表情、得分展示区等均使用Photoshop绘制。扫雷程序使用Java语言实现,界面部分使用Java中的GUI技术,得分以及游戏逻辑则通过顺序表、二维数组实现,并通过IO流进行分数的保存。


2 需求分析

扫雷游戏是一个小型益智游戏,可用于人们日常的休闲、娱乐等场景。本次游戏设计涉及一维数组、二维数组、Swing中的常用组件、GUI中的事件处理(事件监听器、鼠标监听器)、类的封装与继承、static静态变量、包装类、随机数、IO流等方面的知识。

具体需求概要如下:
(1) 游戏界面应当尽可能美观,大小应当合适并居中显示,窗口应有“扫雷”字样,且窗口大小不可再调整;
(2) 游戏界面应当有必要的说明性图示或文字信息。在窗口顶部区域应当展示剩余雷数、最短用时、当前用时的信息。顶部中间区域应当有组件可以控制游戏的开始和结束;
(3) 游戏界面的雷区部分应当以10×10网格绘制,且雷区应当随机在不同位置生成10个地雷;
(4) 网格应该包含两种状态,被覆盖与未被覆盖。被覆盖时应当不显示格子的内容,未被覆盖则显示该格子的内容(雷或者数字);
(5) 游戏开始与重新开始功能的实现:鼠标左键点击游戏界面顶部中央区域的表情,实现游戏的开始与重新开始的操作;
(6) 数字显示功能的实现:在雷区没有雷的位置应当显示一个整数,该整数范围[0,8],表示其所有邻居方格(该方格周围8个格子)所包含的雷数,当雷数为0时不显示数字,其余情况显示对应的数字;
(7) 翻开功能的实现:点击网格范围内的任一方格,显示该方格的内容(可能为地雷,也可能为数字),若翻开的网格内容为数字“0”,则翻开其周围所有的邻居方格。特别地,当格子已经被翻开(未被覆盖),则不可再覆盖;
(8) 标记功能的实现:鼠标右键单击被覆盖的格子,则该网格显示“独角兽”的图片(无论该区域是否真的有雷,都可以标记),当游戏失败时若该处不是雷,则更换成标记错误的图片;
(9) 单击数字功能的实现:鼠标右键单击已经显示的数字,且周围标记数等于此数字,则翻开其余未翻开的邻居方格;
(10) 剩余雷数展示功能的实现:在界面顶部的剩余雷数功能框中展示剩余雷数(地雷总数-标记数),已标记的区域并不代表一定是雷;
(11) 游戏的失败判定:当标记的方格数=地雷总数时进行判定,如果被标记的方格中有不是地雷的,则游戏失败;当鼠标左键单击网格时进行判定,如果翻开后为地雷,则游戏失败;当鼠标右键单击数字时进行判定,若该数字方格的邻居方格中被标记的数量=该数字显示的雷数则翻开所有邻居格子,若含有未被标记的地雷,则游戏失败;
(12) 游戏的胜利判定:当标记的方格数=地雷总数时进行判定,如果被标记的方格均为地雷,则游戏胜利;
(13) 游戏的计时功能:当游戏运行时即开始计时,时间显示在游戏界面顶部的“当前用时”状态栏,以秒为单位;当游戏胜利或者失败时停止计时。
(14) 最短用时功能的实现:当游戏胜利时判断游戏用时和最短用时状态栏的大小,若游戏用时<最短用时,则更新最短用时的状态栏,否则不进行更新。

操作一览表:
Java实现扫雷小游戏【完整版】大团子限定_第2张图片


3 总体设计

3.1 系统总体功能设计

此程序大方向上包含游戏规则判断功能、主界面控制功能、鼠标左键翻开功能、鼠标右键标记功能。其中主界面控制功能中包含地雷的初始化、数字的初始化、计时功能、最短用时以及显示剩余雷数功能;游戏规则判断功能包括游戏的胜利判定、游戏的失败判定以及游戏开始与重置;鼠标左键翻开功能包含翻开显示地雷或者翻开显示数字;鼠标右键标记功能包括标记格子以及单击数字功能(依据游戏规则判断是否翻开其周围的邻居格子)。具体功能结构图如下:
Java实现扫雷小游戏【完整版】大团子限定_第3张图片

3.2 系统总体流程设计

开始进入扫雷程序时则游戏直接开始,计时器开始计时,同时初始化10个地雷并生成数字。而后开始判断是否鼠标左键点击菜单顶部的表情区域位置,如若点击,则计时器归0重新计时,游戏重新开始。否则,程序则监听鼠标单击操作,根据鼠标左键或右键的点击判断该翻开格子还是标记格子。当翻开的格子为地雷时,则判定游戏失败,此时扫雷程序结束,计时器停止。系统总体设计流程图如下:
Java实现扫雷小游戏【完整版】大团子限定_第4张图片


4 系统实现

在设计扫雷游戏时需要编写7个Java源文件:Bottom.java、Cover.java、GamePanel.java、MinTime.java、PaintBottomArea.java、Properties.java、ShowBottomCount.java。除了需要编写上述java源文件给出的类外,还需要Java系统提供一些必要的类,如:JPanel、JFrame、File、Image等类。
其关系结构如下:
Java实现扫雷小游戏【完整版】大团子限定_第5张图片

4.1 Properties类

该类为工具类用于存放扫雷程序所需要的各种参数,且所有属性使用静态变量,便于其他类使用。该类中还将游戏所需要的图片组件存储为静态对象,其他类可以直接使用该对象,具体可见代码注释(记得将绝对路径改为自己存储图片的地址)。

代码如下:

import java.awt.*;

/*
    工具类
    用于存放扫雷界面所需要的各种参数
    所有属性使用静态变量 便于其他类使用
 */
public class Properties {
    static int Grid_Width=10;//横着的格子的数量
    static int Grid_Heigh=10;//竖着的格子的数量
    static int Grid_Offset=45;//网格起点的坐标偏移量(45,45)
    static int Grid_Length=50;//游戏面板每个格子的宽度
    static int Bottom_Count=10;//地雷个数
    static int Sign_Count=0;//标记独角兽的数量
    //游戏用时相关参数
    static long Start_Time;
    static long End_Time;
    //二维数组中-1表示雷,0-8表示周围8个格子的雷数 这里扩大了二维数组的范围,避免边界判断
    static int[][] Data_Bottom=new int[Grid_Width+2][Grid_Heigh+2];
    //游戏状态相关量,0表示游戏中,1表示胜利,2表示失败
    static int status=0;
    //鼠标事件相关参数
    static int Mouse_X;
    static int Mouse_Y;
    //鼠标被点击则为true
    static boolean Left_Click=false;
    static boolean Right_Click=false;
    /*
        游戏需要的图片载入
     */
    //地雷图片
    static Image bottom=Toolkit.getDefaultToolkit().getImage(
            "C:\\Users\\26510\\IdeaProjects\\黄小黄\\数据结构与算法课程设计_扫雷\\src\\ImagesForGame\\bottom2.jpg");
    //数字0到8的图片导入,0为一个灰色背景
    static Image[] c=new Image[9];
    static {
        for (int i = 0; i <= 8; i++) {
            c[i]=Toolkit.getDefaultToolkit().getImage(
                    "C:\\Users\\26510\\IdeaProjects\\黄小黄\\数据结构与算法课程设计_扫雷\\src\\ImagesForGame\\"+i+".png");
        }
    }
    //覆盖界面的绘制 -1无图片 0为没有点开时的覆盖图片 1为标记独角兽的图片 2为标记错误图片
    static int[][] Top=new int[Grid_Width+2][Grid_Heigh+2];
    static Image cover=Toolkit.getDefaultToolkit().getImage(
            "C:\\Users\\26510\\IdeaProjects\\黄小黄\\数据结构与算法课程设计_扫雷\\src\\ImagesForGame\\cover.png");
    static Image sign=Toolkit.getDefaultToolkit().getImage(
            "C:\\Users\\26510\\IdeaProjects\\黄小黄\\数据结构与算法课程设计_扫雷\\src\\ImagesForGame\\sign.jpg");
    static Image wrong=Toolkit.getDefaultToolkit().getImage(
            "C:\\Users\\26510\\IdeaProjects\\黄小黄\\数据结构与算法课程设计_扫雷\\src\\ImagesForGame\\wrong.png");
    //游戏中,游戏胜利,游戏失败时顶部中间的表情变化
    static Image start=Toolkit.getDefaultToolkit().getImage(
            "C:\\Users\\26510\\IdeaProjects\\黄小黄\\数据结构与算法课程设计_扫雷\\src\\ImagesForGame\\start.jpg");
    static Image win=Toolkit.getDefaultToolkit().getImage(
            "C:\\Users\\26510\\IdeaProjects\\黄小黄\\数据结构与算法课程设计_扫雷\\src\\ImagesForGame\\win.jpg");
    static Image over=Toolkit.getDefaultToolkit().getImage(
            "C:\\Users\\26510\\IdeaProjects\\黄小黄\\数据结构与算法课程设计_扫雷\\src\\ImagesForGame\\over.jpg");
    //当前用时,剩余雷数,最短用时图像组件
    static Image time=Toolkit.getDefaultToolkit().getImage(
            "C:\\Users\\26510\\IdeaProjects\\黄小黄\\数据结构与算法课程设计_扫雷\\src\\ImagesForGame\\time.jpg");
    static Image minecount=Toolkit.getDefaultToolkit().getImage(
            "C:\\Users\\26510\\IdeaProjects\\黄小黄\\数据结构与算法课程设计_扫雷\\src\\ImagesForGame\\minecount.jpg");
}

4.2 Bottom类

地雷类,包含地雷坐标的相关参数以及地雷位置的初始化方法。具体如下:
(1) 地雷的坐标:int 类型的x和y,两者均使用Math类的random方法生成随机数,用于生成地雷的坐标;
(2) 存放地雷位置的数组:int[]类型的locate,大小为两倍的地雷数量长度,因为这是个一维数组,每两个位置存储的是一个地雷的坐标;
(3) 用于避免地雷重合的参数:boolean 类型的flag,初始化为true,若值为true则表示可以存储。在该类初始化地雷坐标的方法中,应该将每次生成的坐标与locate中的值进行比较,若已经包含,则将其值更改为false,不再放置该坐标,重新生成一个新坐标,直到不重复为止;
(4) 随机生成地雷的方法:newStartGame(),该方法中将随机生成的非重复的地雷坐标存储到locate数组中,最后通过该数组确定生成地雷的位置,将二维数组Data_Bottom对应位置的值设为-1

代码如下:

/*
    地雷类
    地雷坐标相关参数
    地雷位置初始化
 */
public class Bottom {
    //存放地雷位置,相邻两个元素分别为x、y坐标
    int[] locate=new int[2*Properties.Bottom_Count];
    //地雷的坐标
    int x,y;
    //判断地雷是否重合的标记,若为true则可以放置地雷
    boolean flag=true;
    //游戏重新开始时需要用到重新生成地雷的方法
    void newStartGame(){
        //随机生成地雷
        for (int i = 0; i < Properties.Bottom_Count*2; i+=2) {
            x=(int)(Math.random()*Properties.Grid_Width+1);//1-11
            y=(int)(Math.random()*Properties.Grid_Heigh+1);//1-11
            //解决地雷重合问题,bug修复
            for (int j = 0; j < i; j+=2) {
                if(x==locate[j]&&y==locate[j+1]){
                    i=i-2;//如果随机坐标重合,则回退重新生成坐标
                    flag=false;
                    break;
                }
            }
            if(flag){
                locate[i]=x;
                locate[i+1]=y;
            }
            flag=true;
        }
        //将地雷的相应坐标位置的二维数组存值-1表示地雷
        for (int i = 0; i < Properties.Bottom_Count*2; i+=2) {
            //System.out.println(locate[i]+" "+locate[i+1]);//测试生成的随机坐标是否重复
            Properties.Data_Bottom[locate[i]][locate[i+1]]=-1;
        }
    }
}

4.3 ShowBottomCount类

在该类中包含一个newBottomNum()方法,用于计算每一方格周围邻居格子中所含的地雷数目,并将地雷数目存储到对应位置的Data_Bottom数组中。该方法通过遍历邻居格子位置的Data_Bottom数组的值来实现,如果邻居位置值为-1,则记录一次,最后把记录的值存储到Data_Bottom数组中。

代码如下:

/*
    显示方格周围8个格子的雷的个数
 */
public class ShowBottomCount {
    //显示周围雷数量,范围为0-8
    void newBottomNum(){
        for (int i = 1; i <= Properties.Grid_Heigh; i++) {
            for (int j = 1; j <= Properties.Grid_Width; j++) {
                //判断该位置是否是雷,如果该位置是雷,则周围的8个格子存储的数字加1,表示雷数增加1
                if(Properties.Data_Bottom[i][j]==-1){
                    for (int k = i-1; k <= i+1 ; k++) {
                        for (int l = j-1; l <= j+1 ; l++) {
                            if(Properties.Data_Bottom[k][l]>=0){
                                Properties.Data_Bottom[k][l]++;
                            }
                        }
                    }
                }
            }
        }
    }
}

4.4 PaintBottomArea类

该类主要用于绘制雷区、绘制游戏组件、游戏图片。

代码如下:

import javax.swing.*;
import java.awt.*;

/*
    绘制雷区
    绘制游戏组件
    游戏图片
 */
public class PaintBottomArea extends JPanel{
    Bottom bt=new Bottom();
    ShowBottomCount sb=new ShowBottomCount();
    {
        bt.newStartGame();
        sb.newBottomNum();
    }
    //游戏重新开始的方法设计(底层地雷和数字)
    void reStartGame(){
        //将地雷的二维数组参数重置为0
        for (int i = 1; i <= Properties.Grid_Heigh; i++) {
            for (int j = 1; j <= Properties.Grid_Width; j++) {
                Properties.Data_Bottom[i][j]=0;
            }
        }
        //重新生成地雷,重新计算数字
        bt.newStartGame();
        sb.newBottomNum();
    }
    void myPaint(Graphics g){
        //棋盘格绘制
        for (int i = 0; i <= Properties.Grid_Width; i++) {
            g.setColor(Color.orange);
            g.drawLine(Properties.Grid_Offset+i*Properties.Grid_Length,
                    3*Properties.Grid_Offset,
                    Properties.Grid_Offset+i*Properties.Grid_Length,
                    3*Properties.Grid_Offset+Properties.Grid_Heigh*Properties.Grid_Length);
        }
        for (int i = 0; i <= Properties.Grid_Heigh; i++) {
            g.setColor(Color.orange);
            g.drawLine(Properties.Grid_Offset,
                    3*Properties.Grid_Offset+i*Properties.Grid_Length,
                    Properties.Grid_Offset+Properties.Grid_Width*Properties.Grid_Length,
                    3*Properties.Grid_Offset+i*Properties.Grid_Length);
        }
        for (int i = 1; i <= Properties.Grid_Width; i++) {
            for (int j = 1; j <= Properties.Grid_Heigh ; j++) {
                //地雷绘制
                if (Properties.Data_Bottom[i][j] == -1) {
                    g.drawImage(Properties.bottom,
                            Properties.Grid_Offset+(i-1)*Properties.Grid_Length+1,
                            3*Properties.Grid_Offset+(j-1)*Properties.Grid_Length+1,
                            Properties.Grid_Length-2,
                            Properties.Grid_Length-2,
                            null);
                }
                //数字绘制
                if (Properties.Data_Bottom[i][j] >= 0) {
                    g.drawImage(Properties.c[Properties.Data_Bottom[i][j]],
                            Properties.Grid_Offset+(i-1)*Properties.Grid_Length+1,
                            3*Properties.Grid_Offset+(j-1)*Properties.Grid_Length+1,
                            Properties.Grid_Length-2,
                            Properties.Grid_Length-2,
                            null);
                }
            }
        }
        //顶部中间表情绘制,问号脸为游戏中,哭脸为游戏失败,惊讶脸为胜利  0表示游戏中,1表示胜利,2表示失败
        switch (Properties.status){
            case 0:
                Properties.End_Time=System.currentTimeMillis()/1000;//获取结束时间
                g.drawImage(Properties.start,
                        Properties.Grid_Offset+Properties.Grid_Length*(Properties.Grid_Width/2-1),
                        35,
                        null);
                break;
            case 1:
                g.drawImage(Properties.win,
                        Properties.Grid_Offset+Properties.Grid_Length*(Properties.Grid_Width/2-1),
                        35,
                        null);
                break;
            case 2:
                g.drawImage(Properties.over,
                        Properties.Grid_Offset+Properties.Grid_Length*(Properties.Grid_Width/2-1),
                        35,
                        null);
                break;
        }
        //顶层其他组件绘制,包含剩余雷数(每标记一个位置,则剩余雷数减1,标记位置并不意味着一定有雷),使用时间,历史最短用时
        g.drawImage(Properties.minecount,5,35,null);
        g.drawImage(Properties.time,Properties.Grid_Offset+300,35,null);
        //绘制剩余雷数
        g.setColor(Color.red);
        g.setFont(new Font("宋体",Font.BOLD,30));
        g.drawString(""+(Properties.Bottom_Count-Properties.Sign_Count),Properties.Grid_Offset+5,125);
        //绘制使用时间
        g.drawString((Properties.End_Time-Properties.Start_Time)+"s",Properties.Grid_Offset+305,125);
        //绘制最短用时
        g.drawString(MinTime.Min_Time+"s",Properties.Grid_Offset+140,125);
    }
}

4.5 Cover类

该类用于游戏顶层的网格绘制,分为四种情况,分别为有覆盖、无覆盖、标记正确、标记错误,具体如下:
(1) 关于鼠标的位置坐标属性:int 类型的girld_x与girld_y,用于存储转化后的鼠标坐标,即鼠标位于横向第几个格子,纵向第几个格子;
(2) reStartGame()方法:该方法用于重置顶层方格的状态参数,游戏需要重新开始的时候调用该方法;
(3) gameLogic()方法:该方法实现游戏的逻辑部分,包括实现鼠标的左键翻开,右键标记功能;游戏胜利与失败的判定等;
(4) open(int x,int y)方法:该方法用于实现鼠标左键翻开(x,y)处网格内容为数字0时,自动打开周围所有邻居格子。即当周围格子未被翻开且处于雷区时,递归翻开,知道邻居格子都被翻开后,递归结束(当Data_Bottom=0时 周围格子一定没有雷);
(5) openNum(int x,int y)方法:实现右键点击(x,y)处数字时,判断该格子周围的邻居格子中已经标记的数目(count)是否与该数字相同,如若相同,则打开该格子周围所有处于覆盖状态下的格子;
(6) isWon()方法:该方法具有boolean类型的返回值,用于判断游戏是否失败。如果胜利则返回true,反之则返回false,该方法被gameLogic()方法调用;
(7) victory()方法:该方法具有boolean类型的返回值,用于判断游戏是否胜利。如果胜利则返回true,反之则返回false,该方法被gameLogic()方法调用;
(8) showBottom()方法:用于游戏失败后显示游戏中所有的地雷;
(9) myPaint(Graphics g)方法:用于绘制顶层的方格,即绘制覆盖状态的方格、标记状态的方格、标记错误状态的方格。

代码如下:

/*
    游戏界面覆盖层的绘制
    分为四种情况
    有覆盖、无覆盖、标记正确、标记错误
 */
import java.awt.*;

public class Cover {
    //鼠标停留在第几个格子,(x,y)表示横向第x个纵向第y个格子
    int girld_x;
    int girld_y;
    //游戏重新开始的方法设计(顶层覆盖层)
    void reStartGame(){
        //将地雷的二维数组参数重置为0
        for (int i = 1; i <= Properties.Grid_Heigh; i++) {
            for (int j = 1; j <= Properties.Grid_Width; j++) {
                Properties.Top[i][j]=0;
            }
        }
    }
    //游戏逻辑实现
    void gameLogic(){
        //点击左侧非网格区域后网格还会被打开,应该确保鼠标的位置大于偏移量
        girld_x=0;girld_y=0;
        if(Properties.Mouse_X>Properties.Grid_Offset&&Properties.Mouse_Y>3*Properties.Grid_Offset){
            girld_x=(Properties.Mouse_X-Properties.Grid_Offset)/Properties.Grid_Length+1;
            girld_y=(Properties.Mouse_Y-3*Properties.Grid_Offset)/Properties.Grid_Length+1;
        }
        if(girld_x>=1&&girld_x<=Properties.Grid_Width&&girld_y>=1&&girld_y<=Properties.Grid_Heigh){
            //左键被点击
            if(Properties.Left_Click==true){
                //若覆盖,则打开
                if(Properties.Top[girld_x][girld_y]==0){
                    Properties.Top[girld_x][girld_y]=-1;
                }
                open(girld_x,girld_y);
                Properties.Left_Click=false;//释放状态
            }
            //右键被点击
            if(Properties.Right_Click==true){
                //如果当前格子为覆盖状态,则标记独角兽
                if(Properties.Top[girld_x][girld_y]==0){
                    Properties.Top[girld_x][girld_y]=1;
                    Properties.Sign_Count++;
                }
                //如果当前格子为标记状态,则取消标记,恢复覆盖状态
                else if(Properties.Top[girld_x][girld_y]==1){
                    Properties.Top[girld_x][girld_y]=0;
                    Properties.Sign_Count--;
                }
                //如果该区域被打开
                else if(Properties.Top[girld_x][girld_y]==-1){
                    openNum(girld_x,girld_y);
                }
                Properties.Right_Click=false;//释放状态
            }
        }
        victory();
        isWon();
    }

    //点击空白格子自动翻开周围格子(当Data_Bottom=0时 周围格子一定没有雷)
    void open(int x,int y){
        if(Properties.Data_Bottom[x][y]==0){
            for (int i = x-1; i <= x+1 ; i++) {
                for (int j = y-1; j <= y+1 ; j++) {
                    //当格子未被翻开且鼠标位于雷区时,递归打开周围格子
                    if(Properties.Top[i][j]!=-1){
                        if(Properties.Top[i][j]==1){
                            Properties.Sign_Count--;//如果有标记,则释放标记数
                        }
                        Properties.Top[i][j]=-1;//则翻开
                        if(i>=1&&j>=1&&i<=Properties.Grid_Heigh&&j<=Properties.Grid_Width){
                            open(i,j);
                        }
                    }
                }
            }
        }
    }

    //右键点击数字时
    void openNum(int x,int y){
        //标记数记录
        int count=0;
        if(Properties.Data_Bottom[x][y]>0){
            for (int i = x-1; i <= x+1 ; i++) {
                for (int j = y-1; j <= y+1 ; j++) {
                    //当格子为标记状态时
                    if(Properties.Top[i][j]==1){
                        count++;
                    }
                }
            }
            if(count==Properties.Data_Bottom[x][y]){
                for (int i = x-1; i <= x+1 ; i++) {
                    for (int j = y-1; j <= y+1 ; j++) {
                        //当格子未被标记,则翻开
                        if(Properties.Top[i][j]!=1){
                            Properties.Top[i][j]=-1;
                            //处于绘制雷区位置时,递归打开空格区域
                            if(i>=1&&j>=1&&i<=Properties.Grid_Heigh&&j<=Properties.Grid_Width){
                                open(i,j);
                            }
                        }
                    }
                }
            }
        }
    }

    //失败判定,true则胜利,false则失败
    boolean isWon(){
        //当标记的独角兽数量与雷数相同时,则认定游戏结束
        if(Properties.Sign_Count==Properties.Bottom_Count){
            for (int i = 1; i <= Properties.Grid_Heigh; i++) {
                for (int j = 1; j <= Properties.Grid_Width; j++) {
                    if(Properties.Top[i][j]==0){
                        Properties.Top[i][j]=-1;
                    }
                }
            }
        }
        //遍历雷区
        for (int i = 1; i <= Properties.Grid_Heigh; i++) {
            for (int j = 1; j <= Properties.Grid_Width; j++) {
                //若该处为雷且没有被覆盖,则判定为失败
                if(Properties.Data_Bottom[i][j]==-1&&Properties.Top[i][j]==-1){
                    showBottom();
                    Properties.status=2;//标记失败
                    return false;
                }
            }
        }
        return true;
    }

    //失败后显示游戏中所有雷
    void showBottom(){
        for (int i = 1; i <= Properties.Grid_Heigh; i++) {
            for (int j = 1; j <= Properties.Grid_Width; j++) {
                //如果底层是雷,且该处未被标记成独角兽,则显示雷
                if(Properties.Data_Bottom[i][j]==-1&&Properties.Top[i][j]!=1){
                    Properties.Top[i][j]=-1;
                }
                //如果底层不是雷,且该处被标记成独角兽,则显示标记错误
                if(Properties.Data_Bottom[i][j]!=-1&&Properties.Top[i][j]==1){
                    Properties.Top[i][j]=2;
                }
            }
        }
    }

    //游戏胜利判定,true为胜利,false未胜利
    boolean victory(){
        //未打开的格子数统计
        int count=0;
        //遍历雷区
        for (int i = 1; i <= Properties.Grid_Heigh; i++) {
            for (int j = 1; j <= Properties.Grid_Width; j++) {
                if(Properties.Top[i][j]!=-1){
                    count++;
                }
            }
        }
        //当未打开的格子数等于雷数的时候胜利,所有未打开的格子显示标记独角兽
        if(count==Properties.Bottom_Count){
            for (int i = 1; i <= Properties.Grid_Heigh; i++) {
                for (int j = 1; j <= Properties.Grid_Width; j++) {
                    if(Properties.Top[i][j]==0){
                        Properties.Top[i][j]=1;
                    }
                }
            }
            Properties.status=1;//标记成功
            return true;
        }
        return false;
    }

    //绘制方法
    void myPaint(Graphics g){
        gameLogic();
        //覆盖层
        for (int i = 1; i <= Properties.Grid_Width; i++) {
            for (int j = 1; j <= Properties.Grid_Heigh ; j++) {
                if (Properties.Top[i][j] == 0) {
                    g.drawImage(Properties.cover,
                            Properties.Grid_Offset+(i-1)*Properties.Grid_Length+1,
                            3*Properties.Grid_Offset+(j-1)*Properties.Grid_Length+1,
                            Properties.Grid_Length-2,
                            Properties.Grid_Length-2,
                            null);
                }
                //标记独角兽
                if (Properties.Top[i][j] == 1) {
                    g.drawImage(Properties.sign,
                            Properties.Grid_Offset+(i-1)*Properties.Grid_Length+1,
                            3*Properties.Grid_Offset+(j-1)*Properties.Grid_Length+1,
                            Properties.Grid_Length-2,
                            Properties.Grid_Length-2,
                            null);
                }
                //标记错误
                if (Properties.Top[i][j] == 2) {
                    g.drawImage(Properties.wrong,
                            Properties.Grid_Offset+(i-1)*Properties.Grid_Length+1,
                            3*Properties.Grid_Offset+(j-1)*Properties.Grid_Length+1,
                            Properties.Grid_Length-2,
                            Properties.Grid_Length-2,
                            null);
                }
            }
        }
    }
}

4.6 MinTime类

用于实现游戏的最短用时展示功能。

代码如下:

import java.io.*;

/*
    此类使用IO流实现历史最短用时的读取操作
    该类实现游戏的最短用时功能模块
 */
public class MinTime {
    static long Min_Time;//最短用时
    public void highest() throws IOException {
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(
                    "D:\\Ideaproject2021\\黄小黄\\src\\score\\MinTime.txt"));
        //时间短则存储(在游戏胜利的前提下)
            if((Properties.End_Time-Properties.Start_Time)<Min_Time&&Properties.status==1) {
                Min_Time=Properties.End_Time-Properties.Start_Time;
                //测试
                System.out.println("最短用时"+Min_Time);
                String s1 = String.valueOf(Min_Time);
                bos.write(s1.getBytes());
            }
            bos.close();
    }
    //用于游戏开始时从文件读取最短时间
    public void readHighest() throws IOException{
        BufferedInputStream bis=new BufferedInputStream(new FileInputStream(
                "D:\\Ideaproject2021\\黄小黄\\src\\score\\MinTime.txt"));
        byte[] bys=new byte[1024];
        int len;
        while ((len= bis.read(bys))!=-1){
            System.out.print(new String(bys,0,len));//测试用
            String s2=new String(bys,0,len);
            Min_Time=Long.valueOf(s2);
        }
        bis.close();
    }
}

4.7 GamePanel类

该类为扫雷程序的主类,包含程序的主入口main()方法,在该类中实现了窗口的居中显示,并在该类的构造方法中调用其他以定义类的方法。其中width和heigh表示界面的宽度和高度。pb、mt、gamePanel、cover分别为PaintBottomArea、MinTime、Image与Cover类的引用对象,用于调用相应的方法。

代码如下:

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;

/*
    界面整体布局
 */
public class GamePanel extends JFrame{
    int width=Properties.Grid_Width*Properties.Grid_Length+2*Properties.Grid_Offset;
    int height=Properties.Grid_Heigh*Properties.Grid_Length+4*Properties.Grid_Offset;
    PaintBottomArea pb=new PaintBottomArea();
    Cover cover=new Cover();
    MinTime mt=new MinTime();
    Image gamePanel=null;
    public GamePanel() throws IOException {  
        mt.readHighest();//读取最短时间
        Properties.Start_Time=System.currentTimeMillis()/1000;//获取开始时间
        this.setTitle("扫雷");
        int screenWidth= Toolkit.getDefaultToolkit().getScreenSize().width;
        int screenHeigh= Toolkit.getDefaultToolkit().getScreenSize().height;
        this.setBounds((screenWidth-width)/2,(screenHeigh-height)/2,width,height);//使窗口居中显示
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setResizable(false);//设置窗口大小不能更改
        this.setVisible(true);
        //鼠标事件监听
        this.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                super.mouseClicked(e);
                //鼠标对应状态事件设计 0对应游戏进行中 1对应游戏胜利 2对应游戏失败
                switch (Properties.status){
                    case 0:
                        //鼠标左键事件,当鼠标左键被点击,则传递鼠标坐标,并设置鼠标点击状态为true
                        if(e.getButton()==1){
                            Properties.Mouse_X=e.getX();
                            Properties.Mouse_Y=e.getY();
                            Properties.Left_Click=true;
                        }
                        //鼠标右键事件
                        if(e.getButton()==3){
                            Properties.Mouse_X=e.getX();
                            Properties.Mouse_Y=e.getY();
                            Properties.Right_Click=true;
                        }
                    case 1:
                        //胜利则调用方法,判断是否时间最短,如果最短则存储成绩
                        try {
                            mt.highest();
                        } catch (IOException ex) {
                            ex.printStackTrace();
                        }
                    case 2:
                        //鼠标左键点击顶部中间团子表情
                        if(e.getButton()==1)
                        {
                            if((e.getX()>Properties.Grid_Offset+200)&& (e.getX()<Properties.Grid_Offset+300)&&
                                    (e.getY()>5)&&(e.getY()<3*Properties.Grid_Offset)){
                                pb.reStartGame();
                                cover.reStartGame();
                                Properties.status=0;//游戏状态设置为游戏进行中
                                Properties.Sign_Count=0;//释放标记数量
                                Properties.Start_Time=System.currentTimeMillis()/1000;//获取开始时间
                            }
                        }
                        break;
                    default:
                        break;
                }
            }
        });
        //绘制图片无法显示,使用repaint方法重绘,添加一个循环
        while (true){
            repaint();
            try {
                Thread.sleep(100);//避免界面刷新过快
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    //解决界面闪动交替出现的问题,先将顶层和底层的图片同时绘制到一个gamePanel组件中,再绘制面板
    @Override
    public void paint(Graphics g) {
        gamePanel=this.createImage(width,height);
        Graphics G=gamePanel.getGraphics();
        this.setBackground(new Color(202, 225, 253));
        pb.myPaint(G);
        cover.myPaint(G);
        g.drawImage(gamePanel,0,0,null);
    }
    public static void main(String[] args) throws IOException {
        int i = Integer.parseInt("1");
        GamePanel gm=new GamePanel();
    }
}

5 结果展示

5.1 扫雷游戏界面的整体设计

Java实现扫雷小游戏【完整版】大团子限定_第6张图片

5.2 扫雷游戏失败判定部分的设计

在扫雷游戏中,翻开格子如果遇到地雷则判定游戏失败。如果此时有标记错误的格子,即该格子没有地雷却被标记,则该格子显示标记错误,绘制出一个带红×的独角兽。无论是否有标记错误的格子,均展示所以地雷的位置,同时计时器停止,中部表情更换为哭脸。
Java实现扫雷小游戏【完整版】大团子限定_第7张图片

5.3 扫雷游戏胜利判定部分的设计

在扫雷游戏中,当剩余雷数与未翻开的格子数相同时或者当所以的雷都被标记且标记正确时,游戏判定胜利。此时应当记录本次游戏所使用的时间,如果用时更短,则记录新的用时,并将数据写入文本文件中,否则不进行最短用时状态栏的更新。如图所示,当前游戏胜利,界面的表情更换为惊讶脸,并且将所有未翻开的格子标记为独角兽。
Java实现扫雷小游戏【完整版】大团子限定_第8张图片


6 完整资源

完整资源链接如下,供大家交流学习使用!
Java-扫雷小游戏团子限定版完整资源
Java实现扫雷小游戏【完整版】大团子限定_第9张图片

你可能感兴趣的:(Java项目,JavaSE,数据结构与算法,大数据,java,开发语言,学习,intellij-idea)