• 【Android】自己动手做个扫雷游戏


    1. 游戏规则

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

    1.1 数字生成规则

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

    1 1 1
    1 1
    1 1 1

    如果遇到边界就忽略

    1
    1 1

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

    1 1 1
    1 2
    1 2
    . 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

  • 相关阅读:
    高精度入门(减法、加法、乘法)之 CODE[VS] 3115、3116、3117
    DP经典 之 CODE[VS] 1576 最长严格上升子序列 (O(n^2) 和 O(nlogn))
    CODE[VS] 1098 均分纸牌 ( 2002年NOIP全国联赛提高组)
    C++ string 与 int 等类型 的相互转换
    组合数学 + 大数乘法 + 大数除法 之 hdu 1261 字串数
    自然语言理解 之 统计词频
    Leetcode_10【正则表达式匹配】
    01——字符串反转
    Leetcode_09【回文数】
    Leetcode_07【整数反转】
  • 原文地址:https://www.cnblogs.com/luoyesiqiu/p/10053924.html
Copyright © 2020-2023  润新知