【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 (新手程序员) 更新会在里面通知
- 【Cocos Creator实战教程(5)】——扫雷
- 【Cocos Creator 实战教程(0)】——写在前面
- 【Cocos Creator实战教程(6)】——get47(数字消除)
- 【Cocos Creator 实战教程(1)】——人机对战五子棋(节点事件相关)
- 【Cocos Creator 实战教程(2)】——天天酷跑(动画、动作相关)
- 【Cocos Creator实战教程(3)】——炸弹人(TiledMap相关)
- 【Cocos Creator 实战教程(4)】——黄金矿工(上)(节点动作、碰撞体相关)
- 【Cocos Creator实战教程(7)】——猴子摘月亮(平台动作,碰撞检测详解)
- 【Cocos Creator实战教程(8)】——打砖块(物理引擎)
- 【Cocos Creator 实战教程(4)】——黄金矿工(下)(占坑,以后再写,资源文件在此)
- Cocos Creator 教程索引
- Cocos Creator教程 第一弹
- Cocos Creator—最佳构建部署实践
- 【基于Cocos Creator+Socket.io的联机对战黑白棋(5)】——写在后面
- 【Cocos Creator基础教程(组件篇)】——TiledMap(瓦片地图)
- Cocos Creator学习笔记1——LabelLocalized优化
- Cocos Creator 学习笔记——获取时间
- Cocos Creator Tiledmap——入门知识整理
- POJ 1062 昂贵的聘礼【Dijkstra算法变形】
- 250_控件点击效果
- ajax中的同步与异步
- 251_单例的Toast
- Android中HTTP Post和Get请求
- 【Cocos Creator实战教程(5)】——扫雷
- C#中有哪些不同的文本类型?
- 1.6_Android Training 学习笔记_数据保存
- hdu 3415 Max Sum of Max-K-sub-sequence(单调队列)
- mybatis中的#和$的区别
- java编程思想读书笔记 第五章 初始化与清理
- Java I/O 流
- java网络通信
- CF 98E