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

简介: 文章目录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) 最短用时功能的实现:当游戏胜利时判断游戏用时和最短用时状态栏的大小,若游戏用时<最短用时,则更新最短用时的状态栏,否则不进行更新。


🦁操作一览表:



3 总体设计

3.1 系统总体功能设计

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




3.2 系统总体流程设计

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


4 系统实现

在设计扫雷游戏时需要编写7个Java源文件:Bottom.java、Cover.java、GamePanel.java、MinTime.java、PaintBottomArea.java、Properties.java、ShowBottomCount.java。除了需要编写上述java源文件给出的类外,还需要Java系统提供一些必要的类,如:JPanel、JFrame、File、Image等类。

其关系结构如下:




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)方法:用于绘制顶层的方格,即绘制覆盖状态的方格、标记状态的方格、标记错误状态的方格。


相关文章
|
7月前
|
Java Android开发
五子棋【小游戏】(Java课设)
五子棋【小游戏】(Java课设)
43 1
|
7月前
|
Java Android开发
扫雷【小游戏】(Java课设)
扫雷【小游戏】(Java课设)
49 1
|
7月前
|
Java Android开发
贪吃蛇【小游戏】(Java课设)
贪吃蛇【小游戏】(Java课设)
41 0
|
6月前
|
Java
Java 实现 捕鱼达人 小游戏【附源码】
Java 实现 捕鱼达人 小游戏【附源码】
278 0
|
6月前
|
Java
Java实现一个坦克大战的小游戏【附源码】
Java实现一个坦克大战的小游戏【附源码】
217 0
|
4月前
|
数据可视化 Java
使用ChatGPT实现可视化操作扫雷小游戏 【java代码实现】
这篇文章介绍了使用Java语言和Swing框架实现的扫雷小游戏的详细代码和实现过程。
使用ChatGPT实现可视化操作扫雷小游戏 【java代码实现】
|
4月前
|
人工智能 Java 定位技术
人工智能ChatGPT 体验案例:使用ChatGPT实现java扫雷小游戏
这篇文章通过一个使用ChatGPT实现的Java扫雷小游戏案例,展示了ChatGPT在编程领域的应用能力。文章中包含了扫雷游戏的Java代码实现,代码中初始化了雷区地图,随机放置雷,计算每个格子周围雷的数量,并提供了一个简单的文本界面与用户交互进行游戏。游戏通过控制台输入接受玩家的指令,并给出相应的反馈。
人工智能ChatGPT 体验案例:使用ChatGPT实现java扫雷小游戏
|
7月前
|
Java 程序员 图形学
程序员教你用代码制作飞翔的小鸟--Java小游戏,正好拿去和给女神一起玩
《飞扬的小鸟》Java实现摘要:使用IntelliJ IDEA和JDK 16开发,包含小鸟类`Bird`,处理小鸟的位置、速度和碰撞检测。代码示例展示小鸟图像的加载、绘制与旋转。同时有`Music`类用于循环播放背景音乐。游戏运行时检查小鸟是否撞到地面、柱子或星星,并实现翅膀煽动效果。简单易懂,可直接复制使用。
149 0
|
4月前
|
Java
05 Java代码实现一个小游戏(剪刀石头布)和一个简易的万年历
05 Java代码实现一个小游戏(剪刀石头布)和一个简易的万年历
92 2
|
5月前
|
Java
[Java]猜数字小游戏
Java生成一个猜数字的小游戏
26 0