首页 > 程序开发 > 移动开发 > 其他 >

【安卓开发】自己动手做个扫雷游戏

2016-08-02

【安卓开发】自己动手做个扫雷游戏

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[8][2]={

{-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数组代表块的集合,及游戏的地图,存储着游戏的主要数据。

(3)Point类

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

(4)Mine类

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

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

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

create()方法:在地图中随机生成雷的位置。

open(int x,int y)方法:点开某个位置的块,x和y代表Tile数组的索引。

(5)MainActivity类

安卓主窗口,没啥好说的。

(6)MainView类

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

3. 代码示例

Mine.java

package com.woc.mine;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;

import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;
import java.util.Set;
/**
 * Created by zyw on 2016/7/29.
 */
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;
  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 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;
            }

        }
    }

    /**
     * 创建雷
     */
    public void create()
    {
        Set minePoint=new HashSet();
        //随机产生雷
        for (; minePoint.size()< mineNum; )
        {
            int x,y;
            x=rd.nextInt(mapCol);
            y=rd.nextInt(mapRow);
            minePoint.add(new Point(x,y));
        }

        //在矩阵中标记雷的位置
        for(Iterator 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 x
     * @param y
     * @param isFirst
     */

    public void open(int x,int y,boolean isFirst)
    {
        if(tile[y][x].value==-1&&isFirst)
        {
            init();
            create();
            open(x,y,true);
            return;
        }
        else if(tile[y][x].value>0)
        {
            tile[y][x].open=true;
        }
        else{
            Queue qu=new LinkedList();
            qu.offer(new Point(x,y));

            for (int i=0;i<8;i++)
            {
                int offsetX=x+dir[i][0],offsetY=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.value==-1)
                    {
                        canvas.drawCircle((x+j*tileWidth)+tileWidth/2,(y+i*tileWidth)+tileWidth/2,tileWidth/2,new Paint());
                    }
                }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,10,10, tilePaint);
                    }
                }
            }
        }
        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

package com.woc.mine; import android.content.Context; import android.content.DialogInterface; import android.graphics.Canvas; import android.support.v7.app.AlertDialog; import android.view.MotionEvent; import android.view.View; /** * Created by zyw on 2016/7/29. */ 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(); mine.create(); }catch (Exception e){ e.printStackTrace(); } } public void logic() { int count=0; for (int i=0;i=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(idxX,idxY,isFirst); isFirst=false; invalidate(); if(mine.tile[idxY][idxX].value==-1) { new AlertDialog.Builder(context) .setCancelable(false) .setMessage("很遗憾,你踩到雷了!") .setPositiveButton("继续", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { mine.init(); mine.create(); invalidate(); isFalse=true; } }) .setNegativeButton("退出", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { System.exit(0); } }) .create() .show(); } if(isFalse) { isFalse=false; return true; } logic(); } } return true; } } 

MainActivity.java

package com.woc.mine;

import android.app.Activity;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.util.DisplayMetrics;

/**
 * Created by zyw on 2016/7/29.
 */
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个雷。\n\n--卧槽工作室")
                .setPositiveButton("我知道了",null)
                .create()
                .show();
    }
}
相关文章
最新文章
热点推荐