用Java编写扫雷游戏:一步步教你

发表时间: 2022-12-15 07:29

摘要:本程序共封装了五个类,分别是主类GameWin类,绘制底层地图和绘制顶层地图的类MapBottom类和MapTop类,绘制底层数字的类BottomNum类,以及初始化地雷的BottomRay类和工具GameUtil类,用于存静态参数和方法。

本文分享自华为云社区《Java实现扫雷小游戏【完整版】-云社区-华为云》,作者:橙子!。

效果展示

主类: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();    void launch(){        GameUtil.START_TIME=System.currentTimeMillis();        this.setVisible(true);        this.setSize(width,height);        this.setLocationRelativeTo(null);        this.setTitle("Java扫雷小游戏");        this.setDefaultCloseOperation(EXIT_ON_CLOSE);        //鼠标事件        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;                        }                        //去掉break,任何时候都监听鼠标事件                    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;                            }                        }                        break;                    default:                }            }        });        while (true){            repaint();            try {                Thread.sleep(40);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }    @Override    public void paint(Graphics g) {        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.BLACK);        //画竖线        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 {    //存放坐标    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-12            y= (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 = 5;    //地图的宽    static int MAP_W = 11;    //地图的高    static int MAP_H = 11;    //雷区偏移量    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 失败    static int state = 0;    //倒计时    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);    }}

总结

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

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

游戏的设计类似windows扫雷,用户在图形化用户界面内利用鼠标监听事件标记雷区,左上角表示剩余雷的数量,右上角动态显示使用的时间。用户可选择中间组件按钮重新游戏。为了解决程序窗口闪动的问题,本程序采用了双缓冲技术。

程序的总体界面布局:

项目结构:

程序测试:

请大家指正!

点击下方,第一时间了解华为云新鲜技术~

华为云博客_大数据博客_AI博客_云计算博客_开发者中心-华为云