全文手敲代码,教你用Java实现扫雷小游戏

Posted 华为云开发者联盟

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了全文手敲代码,教你用Java实现扫雷小游戏相关的知识,希望对你有一定的参考价值。

摘要:本程序共封装了五个类,分别是主类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扫雷,用户在图形化用户界面内利用鼠标监听事件标记雷区,左上角表示剩余雷的数量,右上角动态显示使用的时间。用户可选择中间组件按钮重新游戏。为了解决程序窗口闪动的问题,本程序采用了双缓冲技术。

程序的总体界面布局:

项目结构:

程序测试:

请大家指正!

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

以上是关于全文手敲代码,教你用Java实现扫雷小游戏的主要内容,如果未能解决你的问题,请参考以下文章

C语言小游戏:扫雷实现

扫雷游戏(C语言实现)初级版和优化版(增加了自动展开标记地雷功能,同时排除了第一次排到地雷的情况)

扫雷游戏(C语言实现)初级版和优化版(增加了自动展开标记地雷功能,同时排除了第一次排到地雷的情况)

原生 JavaScript 实现扫雷 (分析+代码实现)

C语言实现扫雷游戏(一步步教你如何写扫雷)

C语言的扫雷简化版