·【尚学堂Java开发扫雷游戏项目】1个半小时做出java扫雷小游戏_java小游戏_Java游戏开发_Java练手项目_java项目实战_java初级项目_哔哩哔哩_bilibili
前言:
- 记录的是大致的写代码过程
 - 为了视觉上更清晰,下面只是放出了完成该功能的核心代码,把每个功能的代码拼装起来,才是完整的项目代码
 - 项目中导入的图片,是缩小过它们的尺寸的
 
项目结构:

一、窗口绘制
5、GameWin
package com.study;
import javax.swing.*;
/**
 * 绘制
 */
public class GameWin extends JFrame {
    void launch(){//绘制窗口
        this.setVisible(true);//窗口是否可见
        this.setSize(500,500);//窗口大小
        this.setLocationRelativeTo(null);//窗口位置居中显示
        this.setTitle("扫雷游戏");//窗口标题
        this.setDefaultCloseOperation(EXIT_ON_CLOSE);//窗口关闭方式
    }
    public static void main(String[] args) {
        GameWin gameWin=new GameWin();
        gameWin.launch();//调用方法绘制窗口
    }
}
 
运行结果:

二、雷区绘制
5、GameWin
package com.study;
import javax.swing.*;
import java.awt.*;
/**
 * 绘制
 */
public class GameWin extends JFrame {
    MapBottom mapBottom=new MapBottom();
    public void paint(Graphics g){//绘制组件
        mapBottom.paintSelf(g);
    }
}
 
6、MapBottom
package com.study;
import java.awt.*;
/**
 * 底层地图
 * 绘制游戏相关组件
 */
public class MapBottom {
    //绘制方法
    void paintSelf(Graphics g){
        for(int i=0;i<500;i=i+50){//每隔50像素画一条线
            g.setColor(Color.red);
            //通过画线,组成网格,代表雷区
            //横坐标x1和x2相等,是竖线
            //纵坐标y1和y2相等,是横线
            g.drawLine(0,i,500,i);//横线
            g.drawLine(i,0,i,500);//竖线
        }
    }
}
 
运行结果:

三、界面规划
画草图处理变量间的数据关系,整理成公式

4、GameUtil
package com.study;
/**
 * 工具类
 * 存放静态参数
 * 工具方法
 */
public class GameUtil {
    //地图的宽
    static int MAP_W=11;
    //地图的高
    static int MAP_H=11;
    //雷区偏移量
    static int OFFSET=45;
    //格子边长
    static int SQUARE_LENGTH=50;
}
 
5、GameWin
package com.study;
import javax.swing.*;
import java.awt.*;
/**
 * 绘制
 */
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;
    void launch(){//绘制窗口
        this.setSize(width,height);//窗口大小
    }
}
 
6、MapBottom
package com.study;
import java.awt.*;
/**
 * 底层地图
 * 绘制游戏相关组件
 */
public class MapBottom {
    //绘制方法
    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);
        }
    }
}
 
运行结果:
 
四、底层地图
4、GameUtil
package com.study;
import java.awt.*;
/**
 * 工具类
 * 存放静态参数
 * 工具方法
 */
public class GameUtil {
    //底层元素 -1:雷 0:空 1-8:雷数
    static int[][] DATA_BOTTOM=new int[MAP_W+2][MAP_H+2];
    //载入图片
    static Image lei=Toolkit.getDefaultToolkit().getImage("imgs/lei.png");
}
 
6、MapBottom
package com.study;
import java.awt.*;
/**
 * 底层地图
 * 绘制游戏相关组件
 */
public class MapBottom {
    //绘制方法
    void paintSelf(Graphics g){
        //画雷格
        for(int i=1;i<=GameUtil.MAP_W;i++){
            for(int j=1;j<=GameUtil.MAP_H;j++){
                g.drawImage(GameUtil.lei,
                        GameUtil.OFFSET+(i-1)*GameUtil.SQUARE_LENGTH+1,//+1是为了露出红线
                        GameUtil.OFFSET*3+(j-1)*GameUtil.SQUARE_LENGTH+1,
                        GameUtil.SQUARE_LENGTH-2,
                        GameUtil.SQUARE_LENGTH-2,
                        null);
            }
        }
    }
}
 
运行结果:

五、地雷生成
2、BottomRay
package com.study;
/**
 * 初始化地雷
 */
public class BottomRay {
    //存放坐标
    int[] rays=new int[GameUtil.RAY_MAX*2];
    //地雷坐标
    int x,y;
    {
        for(int i=0;i<GameUtil.RAY_MAX*2;i=i+2){//随机生成地雷坐标
            x=(int)(Math.random()*GameUtil.MAP_W+1);
            y=(int)(Math.random()*GameUtil.MAP_H+1);
            rays[i]=x;//生成的地雷坐标存放到一维数组中
            rays[i+1]=y;
        }
        for(int i=0;i<GameUtil.RAY_MAX*2;i=i+2){//把一维数组里的地雷坐标转存到二维数组中,-1表示地雷
            GameUtil.DATA_BOTTOM[rays[i]][rays[i+1]]=-1;
        }
    }
}
 
4、GameUtil
package com.study;
import java.awt.*;
/**
 * 工具类
 * 存放静态参数
 * 工具方法
 */
public class GameUtil {
    //地雷个数
    static int RAY_MAX=5;
}
 
6、MapBottom
package com.study;
import java.awt.*;
/**
 * 底层地图
 * 绘制游戏相关组件
 */
public class MapBottom {
    BottomRay bottomRay=new BottomRay();
    //绘制方法
    void paintSelf(Graphics g){
        //画地雷
        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,//+1是为了露出红线
                            GameUtil.OFFSET * 3 + (j - 1) * GameUtil.SQUARE_LENGTH + 1,
                            GameUtil.SQUARE_LENGTH - 2,
                            GameUtil.SQUARE_LENGTH - 2,
                            null);
                }
            }
        }
    }
}
 
运行结果:

六、地雷重合
2、BottomRay
package com.study;
/**
 * 初始化地雷
 */
public class BottomRay {
    //是否可以放置 T:可以;F:不可以
    boolean isPlace=true;
    {
        for(int i=0;i<GameUtil.RAY_MAX*2;i=i+2){//随机生成地雷坐标
            x=(int)(Math.random()*GameUtil.MAP_W+1);
            y=(int)(Math.random()*GameUtil.MAP_H+1);
            //生成地雷坐标后,判断是否与前面生成的重复
            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;//无论是否可以放置,最终都要释放状态
        }        
    }
}
 
4、GameUtil
package com.study;
import java.awt.*;
public class GameUtil {
    //地雷个数
    static int RAY_MAX=121;
}
 
5、GameWin
package com.study;
import javax.swing.*;
import java.awt.*;
/**
 * 绘制
 */
public class GameWin extends JFrame {
    void launch(){//绘制窗口
        while(true){//让窗口实时刷新
            repaint();
            try {
                Thread.sleep(40);//每隔40毫秒刷新一次
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
 
运行结果:
 
七、数字生成

1、BottomNum
package com.study;
/**
 * 底层数据类
 */
public class BottomNum {
    {
        for(int i=1;i<=GameUtil.MAP_W;i++){
            for(int j=1;j<=GameUtil.MAP_H;j++){
                //如果遍历整个雷区时,遍历到了雷,就进一步遍历该雷周围3*3区域的雷数
                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]++;
                            }
                        }
                    }
                }
            }
        }
    }
}
 
4、GameUtil
package com.study;
import java.awt.*;
/**
 * 工具类
 * 存放静态参数
 * 工具方法
 */
public class GameUtil {
    //地雷个数
    static int RAY_MAX=5;    
    //载入数字图片
    static Image[] images=new Image[9];
    static{
        for(int i=1;i<=8;i++){
            images[i]=Toolkit.getDefaultToolkit().getImage("imgs/nums/"+i+".png");
        }
    }
}
 
6、MapBottom
package com.study;
import java.awt.*;
/**
 * 底层地图
 * 绘制游戏相关组件
 */
public class MapBottom {
    BottomNum bottomNum=new BottomNum();
    //绘制方法
    void paintSelf(Graphics g){
        //画地雷
        for(int i=1;i<=GameUtil.MAP_W;i++){    
                //雷数
                if(GameUtil.DATA_BOTTOM[i][j]>=0) {
                    g.drawImage(GameUtil.images[GameUtil.DATA_BOTTOM[i][j]],
                            GameUtil.OFFSET + (i - 1) * GameUtil.SQUARE_LENGTH + 15,//+15是为了让图片更居中
                            GameUtil.OFFSET * 3 + (j - 1) * GameUtil.SQUARE_LENGTH + 5,
                            null);
                }
            }
        }
    }
}
 
运行结果:

八、顶层绘制
4、GameUtil
package com.study;
import java.awt.*;
/**
 * 工具类
 * 存放静态参数
 * 工具方法
 */
public class GameUtil {
    //项层元素 -1:无覆盖 0:覆盖 1:插旗 2:差错旗
    static int[][] DATA_TOP=new int[MAP_W+2][MAP_H+2];
    //载入图片
    static Image top=Toolkit.getDefaultToolkit().getImage("imgs/top.png");
    static Image flag=Toolkit.getDefaultToolkit().getImage("imgs/flag.png");
    static Image noflag=Toolkit.getDefaultToolkit().getImage("imgs/noflag.png");
}
 
5、GameWin
package com.study;
import javax.swing.*;
import java.awt.*;
/**
 * 绘制
 */
public class GameWin extends JFrame {
    MapTop mapTop=new MapTop();
    public void paint(Graphics g){//绘制组件
        mapTop.paintSelf(g);
    }
}
 
7、MapTop
package com.study;
import java.awt.*;
/**
 * 顶层地图类
 * 绘制顶层组件
 * 判断逻辑
 */
public class MapTop {
    //绘制方法
    void paintSelf(Graphics g){
        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);
                }
            }
        }
    }
}
 
运行结果:
 
九、双缓存技术
- 用于解决窗口不断闪动问题
 - 闪动原因:在不停地绘制窗口,底层组件和顶层组件是分开被绘制进窗口,所以会交叉出现,造成闪动
 - 解决办法:新建一个画布,把底层组件和顶层组件都绘制好在画布上,再把画布绘制进窗口
 
5、GameWin
package com.study;
import javax.swing.*;
import java.awt.*;
/**
 * 绘制
 */
public class GameWin extends JFrame {
    //新建画布,用于组合底层组件和顶层组件
    Image offScreenImage=null;
    public void paint(Graphics g){//绘制组件
        offScreenImage=this.createImage(width,height);//初始化画布
        Graphics gImage=offScreenImage.getGraphics();//新建画笔
        gImage.setColor(Color.orange);//背景颜色
        gImage.fillRect(0,0,width,height);//填充
        mapBottom.paintSelf(gImage);//调用画笔
        mapTop.paintSelf(gImage);//调用画笔
        g.drawImage(offScreenImage,0,0,null);//将画布绘制进窗口
    }
}
 
运行结果:

十、鼠标事件
4、GameUtil
package com.study;
import java.awt.*;
/**
 * 工具类
 * 存放静态参数
 * 工具方法
 */
public class GameUtil {
    //鼠标相关
    //鼠标点击处的坐标
    static int MOUSE_X;
    static int MOUSE_Y;
    //鼠标左右键的状态
    static boolean LEFT=false;
    static boolean RIGHT=false;
}
 
5、GameWin
package com.study;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
/**
 * 绘制
 */
public class GameWin extends JFrame {
    void launch(){//绘制窗口     
        //鼠标事件
        this.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                super.mouseClicked(e);
                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;
                }
            }
        });
    }
}
 
7、MapTop
package com.study;
import java.awt.*;
/**
 * 顶层地图类
 * 绘制顶层组件
 * 判断逻辑
 */
public class MapTop {
    //判断逻辑
    void logic(){
        if(GameUtil.LEFT){
            System.out.println(GameUtil.MOUSE_X);
            System.out.println(GameUtil.MOUSE_Y);
            GameUtil.LEFT=false;//释放左键
        }
        if(GameUtil.RIGHT){
            System.out.println(GameUtil.MOUSE_X);
            System.out.println(GameUtil.MOUSE_Y);
            GameUtil.RIGHT=false;//释放右键
        }
    }
    //绘制方法
    void paintSelf(Graphics g){
        logic();//调用上面的逻辑方法
    }
}
 
运行结果:

十一、左键翻开
格子区域的数据分析:

5、GameWin
package com.study;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
/**
 * 绘制
 */
public class GameWin extends JFrame {
    void launch(){//绘制窗口
        //鼠标事件
        this.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                super.mouseClicked(e);
                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;
                }
            }
        });
    }
}
 
7、MapTop
package com.study;
import java.awt.*;
/**
 * 顶层地图类
 * 绘制顶层组件
 * 判断逻辑
 */
public class MapTop {
    //格子位置
    int temp_x;
    int temp_y;
    //判断逻辑
    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;
                }
                GameUtil.LEFT = false;//释放左键
            }
            if (GameUtil.RIGHT) {
                System.out.println(GameUtil.MOUSE_X);
                System.out.println(GameUtil.MOUSE_Y);
                GameUtil.RIGHT = false;//释放右键
            }
        }
    }
}
 
运行结果:

十二、递归翻开
7、MapTop
package com.study;
import java.awt.*;
/**
 * 顶层地图类
 * 绘制顶层组件
 * 判断逻辑
 */
public class MapTop {
    //判断逻辑
    void logic(){
        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);
            }
        }
    }
    //打开空格
    //此处用递归,递归打开点击处的空格周围相邻的所有空格
    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){//判断是否有覆盖,没有打开过的才可继续
                        GameUtil.DATA_TOP[i][j]=-1;//打开格子
                        if(i>=1 && j>=1 && i<=GameUtil.MAP_W && j<=GameUtil.MAP_H){//判断是否在雷区内
                            spaceOpen(i,j);//调用方法本身,是递归明显的特征
                        }
                    }
                }
            }
        }
    }
}
 
运行结果:

十三、右键插旗
7、MapTop
package com.study;
import java.awt.*;
/**
 * 顶层地图类
 * 绘制顶层组件
 * 判断逻辑
 */
public class MapTop {
    //判断逻辑
    void logic(){
        if(temp_x>=1 && temp_x<=GameUtil.MAP_W
                && temp_y>=1 && temp_y<=GameUtil.MAP_H) {
            if (GameUtil.RIGHT) {
                //覆盖则插旗
                if(GameUtil.DATA_TOP[temp_x][temp_y]==0){
                    GameUtil.DATA_TOP[temp_x][temp_y]=1;
                }
                //插旗则取消
                else if(GameUtil.DATA_TOP[temp_x][temp_y]==1){
                    GameUtil.DATA_TOP[temp_x][temp_y]=0;
                }
                GameUtil.RIGHT = false;//释放右键
            }
        }
    }
}
 
运行结果:
 
十四、右键翻开
7、MapTop
package com.study;
import java.awt.*;
/**
 * 顶层地图类
 * 绘制顶层组件
 * 判断逻辑
 */
public class MapTop {
    //判断逻辑
    void logic(){
        if(temp_x>=1 && temp_x<=GameUtil.MAP_W
                && temp_y>=1 && temp_y<=GameUtil.MAP_H) {
            if (GameUtil.RIGHT) {
                //数字翻开
                else if(GameUtil.DATA_TOP[temp_x][temp_y]==-1){
                    numOpen(temp_x,temp_y);
                }
            }
        }
    }
    //数字翻开
    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);//调用方法本身,是递归明显的特征
                        }
                    }
                }
            }
        }
    }
}
 
运行结果:
 
十五、失败判定
7、MapTop
package com.study;
import java.awt.*;
/**
 * 顶层地图类
 * 绘制顶层组件
 * 判断逻辑
 */
public class MapTop {
    //判断逻辑
    void logic(){
       boom();
    }
    //失败判定 true:失败;false:未失败
    boolean boom(){
        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){
                    System.out.println("fail");
                    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;
                }
            }
        }
    }
}
 
运行结果:

十六、胜利判定
7、MapTop
package com.study;
import java.awt.*;
/**
 * 顶层地图类
 * 绘制顶层组件
 * 判断逻辑
 */
public class MapTop {
    //判断逻辑
    void logic(){
        victory();
    }
    //胜利判断 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){
            System.out.println("success");
            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;
    }
}
 
运行结果:

十七、游戏状态
4、GameUtil
package com.study;
import java.awt.*;
/**
 * 工具类
 * 存放静态参数
 * 工具方法
 */
public class GameUtil {
    //游戏状态 0:游戏中;1:胜利;2:失败
    static int state=0;
    //载入图片
    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");
}
 
6、MapBottom
package com.study;
import java.awt.*;
/**
 * 底层地图
 * 绘制游戏相关组件
 */
public class MapBottom {
    //绘制方法
    void paintSelf(Graphics g){
        switch (GameUtil.state){
            case 0:
                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:
        }
    }
}
 
7、MapTop
package com.study;
import java.awt.*;
/**
 * 顶层地图类
 * 绘制顶层组件
 * 判断逻辑
 */
public class MapTop {
    //失败判定,true:失败;false:未失败
    boolean boom(){
        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;                }
            }
        }
        return false;
    }
   //胜利判断 t:胜利;f:未胜利
    boolean victory(){
        if(count==GameUtil.RAY_MAX){
            GameUtil.state=1;
        }
    }
}
 
运行结果:
失败:
胜利:
游戏中:
十八、游戏重置
1、BottomNum
package com.study;
/**
 * 底层数据类
 */
public class BottomNum {
    void newNum(){
        for(int i=1;i<=GameUtil.MAP_W;i++){
            //...
        }
    }
}
 
2、BottomRay
package com.study;
/**
 * 初始化地雷
 */
public class BottomRay {
    //生成雷
    void newRay(){
        for(int i=0;i<GameUtil.RAY_MAX*2;i=i+2){//随机生成地雷坐标
           //...
        }
    }
}
 
5、GameWin
package com.study;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
/**
 * 绘制
 */
public class GameWin extends JFrame {
    void launch(){//绘制窗口
        //鼠标事件
        this.addMouseListener(new MouseAdapter() {
            @Override
            public 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.state=0;
                            }
                        }
                        break;
                    default:
                }
            }
        });
    }
}
 
6、MapBottom
package com.study;
import java.awt.*;
/**
 * 底层地图
 * 绘制游戏相关组件
 */
public class MapBottom {
    {
        //游戏初始化界面
        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();
    }
}
 
7、MapTop
package com.study;
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;
            }
        }
    }
}
 
运行结果:

十九、数字添加
4、GameUtil
package com.study;
import java.awt.*;
/**
 * 工具类
 * 存放静态参数
 * 工具方法
 */
public class GameUtil {
    //倒计时
    static long START_TIME;
    static long END_TIME;
    //绘制数字
    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);
    }
}
 
5、GameWin
package com.study;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
/**
 * 绘制
 */
public class GameWin extends JFrame {
    void launch(){//绘制窗口
        GameUtil.START_TIME=System.currentTimeMillis();
        //鼠标事件
        this.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                switch (GameUtil.state){
                    case 0:
                    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){
                                GameUtil.FLAG_NUM=0;//释放旗数
                                GameUtil.START_TIME=System.currentTimeMillis();//重置游戏开始时间
                            }
                        }
                        break;
                    default:
                }
            }
        });
}
 
6、MapBottom
package com.study;
import java.awt.*;
/**
 * 底层地图
 * 绘制游戏相关组件
 */
public class MapBottom {
    //绘制方法
    void paintSelf(Graphics g){
        //绘制数字,剩余雷数
        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();//实时刷新
        }
    }
}
 
7、MapTop
package com.study;
import java.awt.*;
/**
 * 顶层地图类
 * 绘制顶层组件
 * 判断逻辑
 */
public class MapTop {
    //判断逻辑
    void logic(){
        if(temp_x>=1 && temp_x<=GameUtil.MAP_W
                && temp_y>=1 && temp_y<=GameUtil.MAP_H) {
            if (GameUtil.RIGHT) {
                //覆盖则插旗
                if(GameUtil.DATA_TOP[temp_x][temp_y]==0){
                    GameUtil.FLAG_NUM++;
                }
                //插旗则取消
                else if(GameUtil.DATA_TOP[temp_x][temp_y]==1){
                    GameUtil.FLAG_NUM--;
                }
            }
        }
    }
    //失败判定,true:失败;false:未失败
    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;
                    }
                }
            }
        }
    }
    //打开空格
    //此处用递归,递归打开点击处的空格周围相邻的所有空格
    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--;//释放旗子
                        }
                    }
                }
            }
        }
    }
}
 
运行结果:

二十、难度选择
2、BottomRay
package com.study;
/**
 * 初始化地雷
 */
public class BottomRay {
    //存放坐标,改成static,初始化为最大值,后续可防止数组越界
    static int[] rays=new int[GameUtil.RAY_MAX*2];
}
 
3、GameSelect
package com.study;
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.drawRect(100,50,300,100);
        GameUtil.drawWord(g,"简单",220,100,30,Color.black);
        g.drawRect(100,200,300,100);
        GameUtil.drawWord(g,"普通",220,250,30,Color.black);
        g.drawRect(100,350,300,100);
        GameUtil.drawWord(g,"困难",220,400,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=40;
                GameUtil.MAP_W=16;
                GameUtil.MAP_H=16;
                break;
            case 3:
                GameUtil.RAY_MAX=99;
                GameUtil.MAP_W=30;
                GameUtil.MAP_H=16;
                break;
            default:
        }
    }
}
 
4、GameUtil
package com.study;
import java.awt.*;
/**
 * 工具类
 * 存放静态参数
 * 工具方法
 */
public class GameUtil {
    //游戏状态 0:游戏中;1:胜利;2:失败 3:难度选择
    static int state=3;
    //游戏难度 1:简单2:普通 3:困难
    static int level;
}
 
5、GameWin
package com.study;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
/**
 * 绘制
 */
public class GameWin extends JFrame {
    GameSelect gameSelect=new GameSelect();
    //是否开始 f:未开始 t:开始
    boolean begin=false;
    void launch(){//绘制窗口
        if(GameUtil.state==3){
            this.setSize(500,500);//游戏难度选择窗口大小
        }else{
            this.setSize(width,height);//游戏界面窗口大小
        }
        //鼠标事件
        this.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                switch (GameUtil.state){
                    case 0:
                    case 1:
                    case 2:
                        //在游戏重置时点击鼠标中间的滚轮可以再次选择难度
                        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){
            begin();//时时刻刻在调用
        }
    }
    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();
        }
    }
    public void paint(Graphics g) {//绘制组件
        if (GameUtil.state == 3) {
            //难度选择窗口的背景颜色
            g.setColor(Color.white);
            g.fillRect(0,0,500,500);
            gameSelect.paintSelf(g);
        } else {
            //...
        }
    }
}
 
运行结果:























