【Cocos Creator实战教程(5)】——扫雷

来源:互联网 发布:太阳能系统计算软件 编辑:程序博客网 时间:2024/06/06 09:43

还记得小时上微机课,老师什么都不会,讲了一会怎么开机关机后就让我们随便玩了,但是连网都没有啊,只能玩windows自带的游戏,最经典的当然要数扫雷了,先来回味一下最初版本的扫雷
这里写图片描述
后来windows更新换代,扫雷也换了一下皮肤,虽然大多数人可能再也没点开过扫雷那个游戏
这里写图片描述

现在我们就来山寨一个经典版的扫雷,你会发现看着简单的扫雷好像做起来也要动一下脑子

首先我们新建一个工程

作为新时代的接班人,有必要跟国际接一下轨

如果我们起个名字叫“saolei”是不是很low?

所以我们应该把工程的名字叫做“Minesweeper”

建立一些文件夹,导入资源文件什么乱七八糟的。。。

(那些很基础的东西我就不讲了,这篇主要侧重实现扫雷的思路)

整体思路

  • 扫雷游戏里有很多小方块,我们这里用Tile表示方块的含义,我们用网格的Layout存放这些Tile,按照扫雷高级难度的标准我们要添加16x30个Tile,这里定义一个Tile的大小为30x30,Layout的大小就是480x900
    这里写图片描述
  • 很明显我们要用脚本想Layout里动态添加Tile,所以这里我们要制作一个Tile 的Prefab
    这里写图片描述
  • 这个Tile还是有很多种形态的,就像这些
    这里写图片描述
    我们知道Tile是可以点击的,点开前我们可以对他进行标记(插旗,问号),点开后他会显示周围雷的情况(1到8或者空或者是雷),我们为Tile添加两个用于区别的属性,一个是state,一个是type,state代表Tile的点击状态,包括:None(未点击),Flag(插旗),Doubt(疑问),Cliked(点开),type代表Tile点开后的种类,包括数字和雷,之所以要用两个属性区别,是因为我们要对每一个Tile进行初始化,每一个Tile在开始游戏时就要确定下来。

    Tile.js

const TYPE = cc.Enum({    ZERO:0,    ONE:1,    TWO:2,    THREE:3,    FOUR:4,    FIVE:5,    SIX:6,    SEVEN:7,    EIGHT:8,    BOMB:9});const STATE = cc.Enum({   NONE:-1,//未点击   CLIKED:-1,//已点开   FLAG:-1,//插旗   DOUBT:-1,//疑问});//其他脚本访问module.exports = {    STATE:STATE,    TYPE:TYPE,};cc.Class({    extends: cc.Component,    properties: {        picNone:cc.SpriteFrame,        picFlag:cc.SpriteFrame,        picDoubt:cc.SpriteFrame,        picZero:cc.SpriteFrame,        picOne:cc.SpriteFrame,        picTwo:cc.SpriteFrame,        picThree:cc.SpriteFrame,        picFour:cc.SpriteFrame,        picFive:cc.SpriteFrame,        picSix:cc.SpriteFrame,        picSeven:cc.SpriteFrame,        picEight:cc.SpriteFrame,        picBomb:cc.SpriteFrame,        _state: {            default: STATE.NONE,            type: STATE,            visible: false        },        state: {            get: function () {                return this._state;            },            set: function(value){                if (value !== this._state) {                    this._state = value;                    switch(this._state) {                        case STATE.NONE:                            this.getComponent(cc.Sprite).spriteFrame = this.picNone;                            break;                        case STATE.CLIKED:                            this.showType();                            break;                        case STATE.FLAG:                            this.getComponent(cc.Sprite).spriteFrame = this.picFlag;                            break;                        case STATE.DOUBT:                            this.getComponent(cc.Sprite).spriteFrame = this.picDoubt;                            break;                        default:break;                    }                }            },            type:STATE,        },        type: {            default:TYPE.ZERO,            type:TYPE,        },    },    showType:function(){        switch(this.type){            case TYPE.ZERO:                this.getComponent(cc.Sprite).spriteFrame = this.picZero;                break;            case TYPE.ONE:                this.getComponent(cc.Sprite).spriteFrame = this.picOne;                break;            case TYPE.TWO:                this.getComponent(cc.Sprite).spriteFrame = this.picTwo;                break;            case TYPE.THREE:                this.getComponent(cc.Sprite).spriteFrame = this.picThree;                break;            case TYPE.FOUR:                this.getComponent(cc.Sprite).spriteFrame = this.picFour;                break;            case TYPE.FIVE:                this.getComponent(cc.Sprite).spriteFrame = this.picFive;                break;            case TYPE.SIX:                this.getComponent(cc.Sprite).spriteFrame = this.picSix;                break;            case TYPE.SEVEN:                this.getComponent(cc.Sprite).spriteFrame = this.picSeven;                break;            case TYPE.EIGHT:                this.getComponent(cc.Sprite).spriteFrame = this.picEight;                break;            case TYPE.BOMB:                this.getComponent(cc.Sprite).spriteFrame = this.picBomb;                break;            default:break;        }    }});
  • 在Game脚本里设置一些游戏参数
    这里写图片描述
    之前我们做过一个五子棋,这里有很多地方都用了一样的方法(比如用一维数组表示二维数组),可以参考一下实战教程1,先把代码放出来

Game.js

const GAME_STATE = cc.Enum({    PREPARE:-1,    PLAY:-1,    DEAD:-1,    WIN:-1});const TOUCH_STATE = cc.Enum({    BLANK:-1,    FLAG:-1,});cc.Class({    extends: cc.Component,    properties: {        tilesLayout:cc.Node,        tile:cc.Prefab,        btnShow:cc.Node,        tiles:[],//用一个数组保存所有tile的引用,数组下标就是相应tile的tag        picPrepare:cc.SpriteFrame,        picPlay:cc.SpriteFrame,        picDead:cc.SpriteFrame,        picWin:cc.SpriteFrame,        gameState:{            default:GAME_STATE.PREPARE,            type:GAME_STATE,        },        touchState:{//左键点开tile,右键插旗            default:TOUCH_STATE.BLANK,            type:TOUCH_STATE,        },        row:0,        col:0,        bombNum:0,    },    onLoad: function () {        this.Tile = require("Tile");        var self = this;        for(let y=0;y<this.row;y++){            for(let x=0;x<this.col;x++){                let tile = cc.instantiate(this.tile);                tile.tag = y*this.col+x;                tile.on(cc.Node.EventType.MOUSE_UP,function(event){                    if(event.getButton() === cc.Event.EventMouse.BUTTON_LEFT){                        self.touchState = TOUCH_STATE.BLANK;                    }else if(event.getButton() === cc.Event.EventMouse.BUTTON_RIGHT){                        self.touchState = TOUCH_STATE.FLAG;                    }                    self.onTouchTile(this);                });                this.tilesLayout.addChild(tile);                this.tiles.push(tile);            }        }        this.newGame();    },    newGame:function(){        //初始化场景        for(let n=0;n<this.tiles.length;n++){            this.tiles[n].getComponent("Tile").type = this.Tile.TYPE.ZERO;            this.tiles[n].getComponent("Tile").state = this.Tile.STATE.NONE;        }        //添加雷        var tilesIndex = [];        for(var i=0;i<this.tiles.length;i++){            tilesIndex[i] = i;        }        for(var j=0;j<this.bombNum;j++){            var n = Math.floor(Math.random()*tilesIndex.length);            this.tiles[tilesIndex[n]].getComponent("Tile").type = this.Tile.TYPE.BOMB;            tilesIndex.splice(n,1);//从第n个位置删除一个元素            //如果没有splice方法可以用这种方式            // tilesIndex[n] = tilesIndex[tilesIndex.length-1];            // tilesIndex.length--;        }        //标记雷周围的方块        for(var k=0;k<this.tiles.length;k++){            var tempBomb = 0;            if(this.tiles[k].getComponent("Tile").type == this.Tile.TYPE.ZERO){                var roundTiles = this.tileRound(k);                for(var m=0;m<roundTiles.length;m++){                    if(roundTiles[m].getComponent("Tile").type == this.Tile.TYPE.BOMB){                        tempBomb++;                    }                }                this.tiles[k].getComponent("Tile").type = tempBomb;            }        }        this.gameState = GAME_STATE.PLAY;        this.btnShow.getComponent(cc.Sprite).spriteFrame = this.picPlay;    },    //返回tag为i的tile的周围tile数组    tileRound:function(i){        var roundTiles = [];        if(i%this.col > 0){//left            roundTiles.push(this.tiles[i-1]);        }        if(i%this.col > 0 && Math.floor(i/this.col) > 0){//left bottom            roundTiles.push(this.tiles[i-this.col-1]);           }        if(i%this.col > 0 && Math.floor(i/this.col) < this.row-1){//left top            roundTiles.push(this.tiles[i+this.col-1]);        }        if(Math.floor(i/this.col) > 0){//bottom            roundTiles.push(this.tiles[i-this.col]);        }        if(Math.floor(i/this.col) < this.row-1){//top            roundTiles.push(this.tiles[i+this.col]);        }        if(i%this.col < this.col-1){//right            roundTiles.push(this.tiles[i+1]);        }        if(i%this.col < this.col-1 && Math.floor(i/this.col) > 0){//rihgt bottom            roundTiles.push(this.tiles[i-this.col+1]);        }        if(i%this.col < this.col-1 && Math.floor(i/this.col) < this.row-1){//right top            roundTiles.push(this.tiles[i+this.col+1]);        }        return roundTiles;    },    onTouchTile:function(touchTile){        if(this.gameState != GAME_STATE.PLAY){            return;        }        switch(this.touchState){            case TOUCH_STATE.BLANK:                if(touchTile.getComponent("Tile").type === 9){                    touchTile.getComponent("Tile").state = this.Tile.STATE.CLIKED;                    this.gameOver();                    return;                }                var testTiles = [];                if(touchTile.getComponent("Tile").state === this.Tile.STATE.NONE){                    testTiles.push(touchTile);                    while(testTiles.length){                        var testTile = testTiles.pop();                        if(testTile.getComponent("Tile").type === 0){                            testTile.getComponent("Tile").state = this.Tile.STATE.CLIKED;                            var roundTiles = this.tileRound(testTile.tag);                            for(var i=0;i<roundTiles.length;i++){                                if(roundTiles[i].getComponent("Tile").state == this.Tile.STATE.NONE){                                    testTiles.push(roundTiles[i]);                                }                            }                        }else if(testTile.getComponent("Tile").type > 0 && testTile.getComponent("Tile").type < 9){                            testTile.getComponent("Tile").state = this.Tile.STATE.CLIKED;                        }                    }                    this.judgeWin();                }                break;            case TOUCH_STATE.FLAG:                if(touchTile.getComponent("Tile").state == this.Tile.STATE.NONE){                    touchTile.getComponent("Tile").state = this.Tile.STATE.FLAG;                }else if(touchTile.getComponent("Tile").state == this.Tile.STATE.FLAG){                    touchTile.getComponent("Tile").state = this.Tile.STATE.NONE;                }                break;            default:break;        }    },    judgeWin:function(){        var confNum = 0;        //判断是否胜利        for(let i=0;i<this.tiles.length;i++){            if(this.tiles[i].getComponent("Tile").state === this.Tile.STATE.CLIKED){                confNum++;            }        }        if(confNum === this.tiles.length-this.bombNum){            this.gameState = GAME_STATE.WIN;            this.btnShow.getComponent(cc.Sprite).spriteFrame = this.picWin;        }    },   gameOver:function(){        this.gameState = GAME_STATE.DEAD;        this.btnShow.getComponent(cc.Sprite).spriteFrame = this.picDead;    },    onBtnShow:function(){        if(this.gameState === GAME_STATE.PREPARE){            this.newGame();        }        if(this.gameState === GAME_STATE.DEAD){            // this.bombNum--;            this.newGame();        }        if(this.gameState === GAME_STATE.WIN){            // this.bombNum++;            this.newGame();        }    }});

扫雷算法

  • 随机添加地雷:
    这里要保证每次添加的地雷位置都不能重复
var tilesIndex = [];for(var i=0;i<this.tiles.length;i++){    tilesIndex[i] = i;}for(var j=0;j<this.bombNum;j++){            var n = Math.floor(Math.random()*tilesIndex.length);            this.tiles[tilesIndex[n]].getComponent("Tile").type = this.Tile.TYPE.BOMB;            tilesIndex.splice(n,1);//从第n个位置删除一个元素            //如果没有splice方法可以用这种方式            // tilesIndex[n] = tilesIndex[tilesIndex.length-1];            // tilesIndex.length--;}
  • 计算Tile周围雷的数目
    先要建立一个能得到Tile周围Tile数组的方法(Game.js里的tileRound方法),要注意边界检测,然后对返回的Tile数组判断Type就行了

  • 点开相连空地区域

    最简单的方法是用递归,但是调用函数的次数太多了,然后Creator就出Bug了,所以这里我们用一种非递归的方式实现

简单的流程图示意:

从点开的这个Tile进行处理,调用tileRound方法判断周围Tile是否是空地且未被点开,如果不是,则跳过,如果是,则将其自动点开,同时把这几个位置加入栈循环判断。流程图如下:当前位置是空白位置?----否---> 非空白的处理        |        | 是        |        V       入栈        |        V+--->栈为空?-------->是---> 结束|       ||       |否|       ||       V|  出栈一个元素|       ||       V|  点开该元素所指的位置|       ||       V|  上左下右的位置如果是空白且未点开则入栈|       |--------+

来一句算法的名言:所有递归都能转化为循环(以后你跟别人聊天时,要不经意的说出这句话,逼格瞬间提升)

看一下最终效果
这里写图片描述

就这样吧,刚考完科目一整个人有点飘,写的可能有点乱,有什么不懂的可以留言

资源和工程文件: http://download.csdn.net/detail/potato47/9634246
-

微信号:xinshouit (新手程序员) 更新会在里面通知

这里写图片描述

0 0
原创粉丝点击