android自己动手做个扫雷游戏(代码片段)

luoyesiqiu luoyesiqiu     2022-11-24     579

关键词:

1. 游戏规则

扫雷是玩法极其简单的小游戏,点击玩家认为不存在雷的区域,标记出全部地雷所在的区域,即可获得胜利。当点击不包含雷的块的时候,可能它底下存在一个数,也可能是一个空白块。当点击中有数字的块时,游戏会展现当前点击块所包含的数字。当点击空白块时,地图会展开,形成一个大小和形状不规则的图形,该图形的边界是数字块,也可以想成展开的是一个被数字包围着的不规则图形。
技术分享图片

1.1 数字生成规则

扫雷游戏中是通过数字来判断雷的位置的,那么,数字的生成规则是什么呢?假设游戏中只有一个雷,那么,他的将被1这个数字包围着,如图:

1 1 1
1 1
1 1 1

如果遇到边界就忽略

1
1 1

可见,游戏是先生成雷然后再根据雷的位置生成数字的,我们再看下面的图:

1 1 1
1 2
1 2
1 1 1

在上图中,块中有两个数字为2的块,它是数字叠加的结果,围绕着雷的区域重合了,重合的区域块的数字相加,该块的数字就会变成相加后的数字。

1.2 本博文的例子扫雷的规则

玩家需要把所有的空白块点开,留下玩家认为有雷的块,当所剩余的块数和雷的数量相等时,玩家胜利。如果在此之前,点到有雷的方块,玩家失败。

2. 游戏的算法和数据结构

2.1 空白块展开算法

空白块的展开几乎是扫雷游戏的核心了。上面说到,扫雷游戏时,点中空白块,游戏的地图块就会展开,我们可以观察到:空白块是一层一层展开的,所以,地图展开算法我们就用广度优先搜索。也许有人会问:可以用深度优先搜索算法吗?答案是可以的,但是如果在这里用的话,效率会比广度优先搜索算法效率低。

2.2 扫雷的数据结构

(1)方向数组

int[][] dir=

      -1,1,//左上角

      0,1,//正上

      1,1,//右上角

      -1,0,//正左

      1,0,//正右

      -1,-1,//左下角

      0,-1,//正下

      1,-1//右下角

;

方向数组在展开空白块的时候回用到,因为广度优先遍历就是在地图中朝各个方向走。

(2)Tile类

该类表示游戏中的“块”,我们给它声明三个成员。

short    value;
boolean flag;
boolean open;

value存储该块的值。-1表示雷块;0表示空白块;>0代表数字块。
flag存储该雷是否被玩家标记(在本例子中无作用,保留,方便扩展)。
open存储该块是否被用户点开过。

(3)Tile数组

Tile数组代表块的集合,及游戏的地图,存储着游戏的主要数据。

(4)Point类

Point类代表“位置”,声明Point类方便我们在地图中生成随机位置的雷。Point类还要重写hashCode和equals方法,为了比较位置与位置是否相同。

(5)Mine类

对上面的数据结构的封装。

Mine构造函数:对游戏地图的参数设置,比如绘制的位置,绘制的大小,块的大小,生成的雷数等。

init()方法:清空并初始化游戏地图。

create(Point p)方法:在地图中随机生成雷的位置,并产生数字。参数p是不产生雷的位置,p点可以传入用户第一次点击时的位置。生成随机位置的雷比较快速的办法是:先把地图中除p位置外所有的位置加入到链表中,然后生成0到链表大小-1之间的随机数,根据生成的随机数在链表中取元素,取完元素就把该位置从链表中移除,并把Tile数组中该位置的Tile的value设为-1。重复执行以上操作,直到生成的雷个数满足要求。产生数字的办法:遍历Tile数组,遇到雷就将他身边的八个的位置的value值加1,如果八个位置中有雷,或者该位置不存在,不执行任何操作。

open(Point p,boolean isFirst)方法:p代表点开某个位置的块,即Tile数组的索引。isFirst传入是否是第一次点击屏幕。该方法要对是不是第一次点击而作不同的操作,当玩家第一次点击块时,调用create函数生成地图。否则就进行展开地图等操作。

(6)MainView类

视图类,负责绘图和操作Mine对象。

3.代码示例

Mine.java

public class Mine 
    public   int x;//地图的在屏幕上的坐标点
    public   int y;//地图的在屏幕上的坐标点
    public    int mapCol;//矩阵宽
    public   int mapRow;//矩阵高
    private  int mineNum ;
    public static short EMPTY=0;//空
    public static short MINE=-1;//雷
    public Tile[][] tile;//地图矩阵
    public   int tileWidth;//块宽
    private  Paint textPaint;
    private Paint bmpPaint;
    private  Paint tilePaint;
    private  Paint rectPaint;
    private  Paint minePaint;
    private Random rd=new Random();
    public  int mapWidth;//绘图区宽
    public int mapHeight;//绘图区高
    public boolean isDrawAllMine=false;//标记是否画雷
  private  int[][] dir=
            -1,1,//左上角
            0,1,//正上
            1,1,//右上角
            -1,0,//正左
            1,0,//正右
            -1,-1,//左下角
            0,-1,//正下
            1,-1//右下角
    ;//表示八个方向

  public   class Tile
        short value;
        boolean flag;
        boolean open;
      public Tile()
      
          this.value=0;
          this.flag=false;
          this.open=false;
      
    

   public static class Point
        private int x;
        private int y;
        public Point(int x,int y)
        
            this.x=x;
            this.y=y;
        

        @Override
        public int hashCode() 
            // TODO Auto-generated method stub
            return 2*x+y;
        

        @Override
        public boolean equals(Object obj) 
            // TODO Auto-generated method stub
            return this.hashCode()==((Point)(obj)).hashCode();

        
    //表示每个雷块


    public Mine(int x, int y, int mapCol, int mapRow, int mineNum, int tileWidth)
    
        this.x=x;
        this.y=y;
        this.mapCol = mapCol;
        this.mapRow = mapRow;
        this.mineNum=mineNum;
        this.tileWidth=tileWidth;
        mapWidth=mapCol*tileWidth;
        mapHeight=mapRow*tileWidth;

        textPaint=new Paint();
        textPaint.setAntiAlias(true);
        textPaint.setTextSize(MainActivity.W/10);
        textPaint.setColor(Color.RED);

        bmpPaint=new Paint();
        bmpPaint.setAntiAlias(true);
        bmpPaint.setColor(Color.DKGRAY);

        tilePaint =new Paint();
        tilePaint.setAntiAlias(true);
        tilePaint.setColor(0xff1faeff);

        minePaint =new Paint();
        minePaint.setAntiAlias(true);
        minePaint.setColor(0xffff981d);

        rectPaint =new Paint();
        rectPaint.setAntiAlias(true);
        rectPaint.setColor(0xff000000);
        rectPaint.setStyle(Paint.Style.STROKE);

        tile=new Tile[mapRow][mapCol];
    
    /**
     * 初始化地图
     */
    public  void init()
    
        for (int i = 0; i< mapRow; i++)
        
            for (int j = 0; j< mapCol; j++)
            
                tile[i][j]=new Tile();
                tile[i][j].value=EMPTY;
                tile[i][j].flag=false;
                tile[i][j].open=false;
                isDrawAllMine=false;
            

        
    

    /**
     * 生成雷
     * @param exception 排除的位置,该位置不生成雷
     */
    public void create(Point exception)
    
        List<Point> allPoint=new LinkedList<Point>();

        //把所有位置加入链表
        for (int i = 0; i< mapRow; i++)//y
        
            for (int j = 0; j < mapCol; j++)//x
            
                Point point=new Point(j,i);
                if(!point.equals(exception))
                
                    allPoint.add(point);
                
            
        

        List<Point> minePoint=new LinkedList<Point>();
        //随机产生雷
        for (int i=0; i< mineNum; i++)
        
            int idx=rd.nextInt(allPoint.size());
            minePoint.add(allPoint.get(idx));
            allPoint.remove(idx);//取了之后,从所有集合中移除
        

        //在矩阵中标记雷的位置
        for(Iterator<Point> it=minePoint.iterator();it.hasNext();)
        
            Point p=it.next();
            tile[p.y][p.x].value=MINE;
        

        //给地图添加数字
        for (int i = 0; i< mapRow; i++)//y
        
            for (int j = 0; j< mapCol; j++)//x
            
                short t=tile[i][j].value;
                if(t==MINE)
                
                    for (int k=0;k<8;k++)
                    
                        int offsetX=j+dir[k][0],offsetY=i+dir[k][1];
                        if(offsetX>=0&&offsetX< mapCol &&offsetY>=0&&offsetY< mapRow ) 
                            if (tile[offsetY][offsetX].value != -1)
                            tile[offsetY][offsetX].value += 1;
                        
                    
                
            
        

    


    /**
     * 打开某个位置
     * @param op
     * @param isFirst 标记是否是第一次打开
     */

    public void open(Point op,boolean isFirst)
    
            if(isFirst)
            
                create(op);
            

            tile[op.y][op.x].open=true;
            if( tile[op.y][op.x].value==-1)
                return;
            else if( tile[op.y][op.x].value>0)//点中数字块
            
                return;
            

             //广度优先遍历用队列
            Queue<Point> qu=new LinkedList<Point>();
            //加入第一个点
            qu.offer(new Point(op.x,op.y));

            //朝8个方向遍历
            for (int i=0;i<8;i++)
            
                int offsetX=op.x+dir[i][0],offsetY=op.y+dir[i][1];
                //判断越界和是否已访问
                boolean isCan=offsetX>=0&&offsetX< mapCol &&offsetY>=0&&offsetY< mapRow;
                if(isCan)
                
                    if(tile[offsetY][offsetX].value==0 &&!tile[offsetY][offsetX].open) 
                        qu.offer(new Point(offsetX, offsetY));
                    
                    else if(tile[offsetY][offsetX].value>0)
                    
                        tile[offsetY][offsetX].open=true;
                    
                

            

            while(qu.size()!=0)
            
                Point p=qu.poll();
                tile[p.y][p.x].open=true;
                for (int i=0;i<8;i++)
                
                    int offsetX=p.x+dir[i][0],offsetY=p.y+dir[i][1];
                    //判断越界和是否已访问
                    boolean isCan=offsetX>=0&&offsetX< mapCol &&offsetY>=0&&offsetY< mapRow;
                    if(isCan)
                    
                        if( tile[offsetY][offsetX].value==0&&!tile[offsetY][offsetX].open) 
                            qu.offer(new Point(offsetX, offsetY));
                        
                        else if(tile[offsetY][offsetX].value>0)
                        
                            tile[offsetY][offsetX].open=true;
                        
                    

                
            

    

    /**
     * 绘制地图
     * @param canvas
     */
    public  void draw(Canvas canvas)
    


        for (int i = 0; i< mapRow; i++)
        
            for (int j = 0; j< mapCol; j++)
            
                Tile t=tile[i][j];
                if(t.open)
                    if(t.value>0)
                    
                        canvas.drawText(t.value+"",x+j*tileWidth,y+i*tileWidth+tileWidth,textPaint);
                    

                else
                
                    //标记,备用
                    if(t.flag)
                    

                    else
                    
                        //画矩形方块
                        RectF reactF=new RectF(x+j*tileWidth,y+i*tileWidth,x+j*tileWidth+tileWidth,y+i*tileWidth+tileWidth);
                        canvas.drawRoundRect(reactF,0,0, tilePaint);
                    
                
                //是否画出所有雷
                if( isDrawAllMine&&tile[i][j].value==-1) 
                    canvas.drawCircle((x + j * tileWidth) + tileWidth / 2, (y + i * tileWidth) + tileWidth / 2, tileWidth / 2, bmpPaint);
                
            
        

        //画边框
        canvas.drawRect(x,y,x+mapWidth,y+mapHeight, rectPaint);
        //画横线
        for (int i = 0; i< mapRow; i++) 
            canvas.drawLine(x,y+i*tileWidth,x+mapWidth,y+i*tileWidth, rectPaint);
        
        //画竖线
        for (int i = 0;i < mapCol; i++) 
            canvas.drawLine(x+i*tileWidth,y,x+i*tileWidth,y+mapHeight, rectPaint);
        

    

MainView.java

public class MainView extends View 
    private   Mine mine;
    private  boolean isFirst=true;//标记是否是本局第一次点击屏幕
    private  Context context;
    private final int mineNum=10;//产生的雷的个数
    private  final int ROW=15;//要生成的矩阵高
    private  final int COL=8;//要生成的矩阵宽
    private   int TILE_WIDTH=50;//块大小
    private  boolean isFalse=false;
    public  MainView(Context context)
    
        super(context);
        this.context=context;

        TILE_WIDTH=MainActivity.W/10;
        mine=new Mine((MainActivity.W-COL*TILE_WIDTH)/2,(MainActivity.H-ROW*TILE_WIDTH)/2,COL,ROW,mineNum,TILE_WIDTH);
        try 
            mine.init();
        catch (Exception e)
            e.printStackTrace();
        
    

    /**
     * 游戏逻辑
     */
    public void logic()
    
        int count=0;

        for (int i=0;i<mine.mapRow;i++)
        
            for (int j=0;j<mine.mapCol;j++)
            
                if(!mine.tile[i][j].open)
                
                    count++;
                
            
        
        //逻辑判断是否胜利
        if(count==mineNum)
        
            new AlertDialog.Builder(context)
                    .setMessage("恭喜你,你找出了所有雷")
                    .setCancelable(false)
                    .setPositiveButton("继续", new DialogInterface.OnClickListener() 
                        @Override
                        public void onClick(DialogInterface dialog, int which) 

                            mine.init();
                            invalidate();
                            isFirst=true;
                        
                    )
                    .setNegativeButton("退出", new DialogInterface.OnClickListener() 
                        @Override
                        public void onClick(DialogInterface dialog, int which) 
                            System.exit(0);
                        
                    )
                    .create()
                    .show();
        
    


    /**
     * 刷新View
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) 
        mine.draw(canvas);
    

    /**
     * 点击屏幕事件
     * @param event
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) 
        if(event.getAction()==MotionEvent.ACTION_DOWN)
        
            int x=(int)event.getX();
            int y=(int)event.getY();
            //判断是否点在范围内
            if(x>=mine.x&&y>=mine.y&&x<=(mine.mapWidth+mine.x)&&y<=(mine.y+mine.mapHeight))
            
                int idxX=(x-mine.x)/mine.tileWidth;
                int idxY=(y-mine.y)/mine.tileWidth;
                mine.open(new Mine.Point(idxX,idxY),isFirst);
                isFirst=false;

                if(mine.tile[idxY][idxX].value==-1)
                
                    mine.isDrawAllMine=true;
                    new AlertDialog.Builder(context)
                            .setCancelable(false)
                            .setMessage("很遗憾,你踩到雷了!")
                            .setPositiveButton("继续", new DialogInterface.OnClickListener() 
                                @Override
                                public void onClick(DialogInterface dialog, int which) 
                                    mine.init();
                                    isFalse=true;
                                    isFirst=true;

                                    invalidate();
                                
                            )
                            .setNegativeButton("退出", new DialogInterface.OnClickListener() 
                                @Override
                                public void onClick(DialogInterface dialog, int which) 
                                    System.exit(0);
                                
                            )
                            .create()
                            .show();
                
                if(isFalse)
                
                    isFalse=false;
                    invalidate();
                    return true;
                
                logic();

                invalidate();
            

        
        return true;
    

MainActivity.java

public class MainActivity extends Activity 
    public  static  int W;
    public  static  int H;
    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        DisplayMetrics dm = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(dm);
        W = dm.widthPixels;//宽度
         H = dm.heightPixels ;//高度

        setContentView(new MainView(this));
        new AlertDialog.Builder(this)
                .setCancelable(false)
                .setTitle("游戏规则")
                .setMessage("把你认为不是雷的位置全部点开,只留着有雷的位置,每局游戏有10个雷。

--卧槽工作室")
                .setPositiveButton("我知道了",null)
                .create()
                .show();
    

完整代码:https://github.com/luoyesiqiu/Mine




自己动手做个分页插件(代码片段)

PC分页,完美支持ie8+,2KB下面就是见证奇迹的时刻dome地址https://github.com/cleartime/pageSize/blob/master/dome/index.html配置详情varconfig=el:document.getElementById("page"),//绑定到你的dompageCount:10,//总页数ps:如果后两项填写了则本字段失效,两者只需其 查看详情

与其想当然的overdesign,不如自己动手做个试验(代码片段)

ConmajiaJan.29th,2019早在2012年,我曾经针对C#System.Random不同的初始化方案专门做过一次试验,得出了单次默认初始化即可获得质量很好的随机数的结论。可是这么多年过去,C#从2.0升到了4.7,还能在网上看到很多新手(甚至是老鸟)... 查看详情

python小游戏自己动手编写,你能写出几个(分享版)(代码片段)

...重温这些童年小游戏,后面还会分享源码,可以自己学习游戏编写,相信你会超有成就感!Paint涂鸦 在屏幕上绘制线条和形状单击以标记形状的开始,然后再次单击以标记其结束;可以 查看详情

虽然我不是做游戏的,闲的没事,emm,写了个扫雷小游戏(android)(代码片段)

文章目录🔥老规矩,先上效果图🔥需求分析🔥实现分析🔥代码实现MinefieldUtilRcAdapterMainActivity🔥源码下载🔥老规矩,先上效果图🔥需求分析实现扫雷高级版,高级版有30*16的网格,480... 查看详情

我对扫雷的理解(代码片段)

扫雷的理解前言一、创建扫雷整体游戏框架1、进入扫雷游戏的整体框架2、扫雷游戏的整体框架二、扫雷游戏菜单的实现三、扫雷的实现1、创建扫雷的棋盘2、存入地雷3、排查雷四、总结前言扫雷是操作一个二维数组使其呈现预... 查看详情

c语言游戏超详解扫雷游戏完整版,细节满满!!(代码片段)

目录 扫雷扫雷游戏规则介绍如何将扫雷游戏实现代码基本思路分步代码实现创建和打印游戏菜单初始化棋盘打印棋盘布置雷排查雷游戏主体——game()函数总代码实现game.htest.cgame.c总结扫雷实现扫雷的算法有很多种ÿ... 查看详情

初识c语言==>扫雷游戏(代码片段)

扫雷游戏文章目录扫雷游戏1.游戏程序主函数2.游戏实现原理棋盘大小为什么需要11x11?游戏过程3.游戏代码实现3-1.初始化和打印3-2.布置雷区3-3.玩家排查雷‘0’的作用3-4.系统扫描雷4.查看结果想必屏幕前的你,肯定玩过wi... 查看详情

[自己做个游戏服务器二]游戏服务器的基石-netty全解析,有例子,多图解释(代码片段)

目录1、Netty是什么2、Netty的优点3、核心组件3.1Netty的线程模型3.2EventLoopGroup3.3Channel3.4 option()与childOption()3.5 inbound和outbound3.6ByteBuf3.7使用Netty自带的解码器3.8Netty版本4、HelloWorld4.1官方的demo4.2idea建立maven项目4.3服务端代码4.4客 查看详情

[自己做个游戏服务器二]游戏服务器的基石-netty全解析,有例子,多图解释(代码片段)

目录1、Netty是什么2、Netty的优点3、核心组件3.1Netty的线程模型3.2EventLoopGroup3.3Channel3.4 option()与childOption()3.5 inbound和outbound3.6ByteBuf3.7使用Netty自带的解码器3.8Netty版本4、HelloWorld4.1官方的demo4.2idea建立maven项目4.3服务端代码4.4客 查看详情

leetcode0529.扫雷游戏(代码片段)

【LetMeFly】529.扫雷游戏力扣题目链接:https://leetcode.cn/problems/minesweeper/让我们一起来玩扫雷游戏!给你一个大小为mxn二维字符矩阵 board,表示扫雷游戏的盘面,其中:'M' 代表一个未挖出的地雷,'... 查看详情

leetcode0529.扫雷游戏(代码片段)

【LetMeFly】529.扫雷游戏力扣题目链接:https://leetcode.cn/problems/minesweeper/让我们一起来玩扫雷游戏!给你一个大小为mxn二维字符矩阵 board,表示扫雷游戏的盘面,其中:'M' 代表一个未挖出的地雷,'... 查看详情

c语言小游戏,编程入门必看,初级扫雷(代码片段)

文章目录一.扫雷描述二.扫雷的设计思路2.1扫雷游戏的基本结构和菜单交互2.2扫雷游戏的实现函数game()2.3扫雷棋盘的创建2.4扫雷棋盘的初始化函数InitBoard()2.5扫雷棋盘的打印函数DisplayBoard()2.6扫雷棋盘的布置雷函数SetMine()2.7扫雷棋... 查看详情

c语言实现扫雷游戏(一步步教你如何写扫雷)(代码片段)

扫雷游戏使用工具一、基本思路和流程1.扫雷游戏的游戏规则2.代码实现思路二、实现步骤(具体步骤)1.使用多文件形式2.代码实现定义各个难度等级的棋盘大小和雷的数量定义全局变量游戏规则界面菜单界面选择实现选... 查看详情

猿创征文|[自己做个游戏服务器三]将二进制流转换为具体的protobuf消息(代码片段)

...协议使用protobuf传输,具体的内容可以看下上篇文章[自己做个游戏服务器一]搞清楚游戏通信协议之p 查看详情

猿创征文|[自己做个游戏服务器三]将二进制流转换为具体的protobuf消息(代码片段)

...协议使用protobuf传输,具体的内容可以看下上篇文章[自己做个游戏服务器一]搞清楚游戏通信协议之p 查看详情

c语言实现小游戏篇我接触的第一款电脑游戏,你可以永远相信“扫雷”。[c语言实现][超详细,超清楚][有代码](代码片段)

...现(所有代码)🔫1.💣扫雷演示💣在动手之前先来看一下扫雷的演示⤵️2.💣扫雷代码的实现💣2.1扫雷的选择菜单🔫游戏的选择菜单界面非常的简单⤵️选1,🔻开始游戏🔻选0,🔻退出... 查看详情

c语言小游戏扫雷游戏的实现(代码片段)

...     利用C语言数组、函数等一些基础知识实现简单的扫雷游戏。此游戏需有一定的数组、函数知识支撑。1.游戏简要及获胜规则    扫雷是电脑上一个十分经典的小游戏,相信大家都和我一样在小的时候胡乱点过,... 查看详情

c语言实现扫雷(初阶)(代码片段)

...简述一、大概思路二、基本流程1.打印游戏菜单2.初始化扫雷面板3.打印设置好的扫雷面版4.随机设置雷的坐标5.进行排雷并判断输赢三、拓展衍生四、总代码展示1.头文件代码2.游戏实现代码3.主函数代码五、代码效果展示问题简... 查看详情