【Android】本身動手作個掃雷遊戲

1. 遊戲規則

掃雷是玩法極其簡單的小遊戲,點擊玩家認爲不存在雷的區域,標記出所有地雷所在的區域,便可得到勝利。當點擊不包含雷的塊的時候,可能它底下存在一個數,也多是一個空白塊。當點擊中有數字的塊時,遊戲會展示當前點擊塊所包含的數字。當點擊空白塊時,地圖會展開,造成一個大小和形狀不規則的圖形,該圖形的邊界是數字塊,也能夠想成展開的是一個被數字包圍着的不規則圖形。
picjava

1.1 數字生成規則

掃雷遊戲中是經過數字來判斷雷的位置的,那麼,數字的生成規則是什麼呢?假設遊戲中只有一個雷,那麼,他的將被1這個數字包圍着,如圖:react

1 1 1
1 1
1 1 1

若是遇到邊界就忽略git

1
1 1

可見,遊戲是先生成雷而後再根據雷的位置生成數字的,咱們再看下面的圖:github

1 1 1
1 2
1 2
. 1 1

在上圖中,塊中有兩個數字爲2的塊,它是數字疊加的結果,圍繞着雷的區域重合了,重合的區域塊的數字相加,該塊的數字就會變成相加後的數字。算法

1.2 本博文的例子掃雷的規則

玩家須要把全部的空白塊點開,留下玩家認爲有雷的塊,當所剩餘的塊數和雷的數量相等時,玩家勝利。若是在此以前,點到有雷的方塊,玩家失敗。canvas

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類

該類表示遊戲中的「塊」,咱們給它聲明三個成員。dom

short    value;
boolean flag;
boolean open;

value存儲該塊的值。-1表示雷塊;0表示空白塊;>0表明數字塊。
flag存儲該雷是否被玩家標記(在本例子中無做用,保留,方便擴展)。
open存儲該塊是否被用戶點開過。ide

(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

相關文章
相關標籤/搜索