【Cocos Creator实战算法教程(1)】——扫雷

lili

发布日期: 2018-12-02 17:35:45 浏览量: 1710
评分:
star star star star star star star star star star
*转载请注明来自write-bug.com

今天开始,我们开始完整做游戏,熟悉一下制作游戏的流程,然后我们就可以大概了解一下几个经典小游戏的算法。

1. 主要思路

扫雷游戏里有很多小方块,我们这里用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

  1. const TYPE = cc.Enum({
  2. ZERO:0,
  3. ONE:1,
  4. TWO:2,
  5. THREE:3,
  6. FOUR:4,
  7. FIVE:5,
  8. SIX:6,
  9. SEVEN:7,
  10. EIGHT:8,
  11. BOMB:9
  12. });
  13. const STATE = cc.Enum({
  14. NONE:-1,//未点击
  15. CLIKED:-1,//已点开
  16. FLAG:-1,//插旗
  17. DOUBT:-1,//疑问
  18. });
  19. //其他脚本访问
  20. module.exports = {
  21. STATE:STATE,
  22. TYPE:TYPE,
  23. };
  24. cc.Class({
  25. extends: cc.Component,
  26. properties: {
  27. picNone:cc.SpriteFrame,
  28. picFlag:cc.SpriteFrame,
  29. picDoubt:cc.SpriteFrame,
  30. picZero:cc.SpriteFrame,
  31. picOne:cc.SpriteFrame,
  32. picTwo:cc.SpriteFrame,
  33. picThree:cc.SpriteFrame,
  34. picFour:cc.SpriteFrame,
  35. picFive:cc.SpriteFrame,
  36. picSix:cc.SpriteFrame,
  37. picSeven:cc.SpriteFrame,
  38. picEight:cc.SpriteFrame,
  39. picBomb:cc.SpriteFrame,
  40. _state: {
  41. default: STATE.NONE,
  42. type: STATE,
  43. visible: false
  44. },
  45. state: {
  46. get: function () {
  47. return this._state;
  48. },
  49. set: function(value){
  50. if (value !== this._state) {
  51. this._state = value;
  52. switch(this._state) {
  53. case STATE.NONE:
  54. this.getComponent(cc.Sprite).spriteFrame = this.picNone;
  55. break;
  56. case STATE.CLIKED:
  57. this.showType();
  58. break;
  59. case STATE.FLAG:
  60. this.getComponent(cc.Sprite).spriteFrame = this.picFlag;
  61. break;
  62. case STATE.DOUBT:
  63. this.getComponent(cc.Sprite).spriteFrame = this.picDoubt;
  64. break;
  65. default:break;
  66. }
  67. }
  68. },
  69. type:STATE,
  70. },
  71. type: {
  72. default:TYPE.ZERO,
  73. type:TYPE,
  74. },
  75. },
  76. showType:function(){
  77. switch(this.type){
  78. case TYPE.ZERO:
  79. this.getComponent(cc.Sprite).spriteFrame = this.picZero;
  80. break;
  81. case TYPE.ONE:
  82. this.getComponent(cc.Sprite).spriteFrame = this.picOne;
  83. break;
  84. case TYPE.TWO:
  85. this.getComponent(cc.Sprite).spriteFrame = this.picTwo;
  86. break;
  87. case TYPE.THREE:
  88. this.getComponent(cc.Sprite).spriteFrame = this.picThree;
  89. break;
  90. case TYPE.FOUR:
  91. this.getComponent(cc.Sprite).spriteFrame = this.picFour;
  92. break;
  93. case TYPE.FIVE:
  94. this.getComponent(cc.Sprite).spriteFrame = this.picFive;
  95. break;
  96. case TYPE.SIX:
  97. this.getComponent(cc.Sprite).spriteFrame = this.picSix;
  98. break;
  99. case TYPE.SEVEN:
  100. this.getComponent(cc.Sprite).spriteFrame = this.picSeven;
  101. break;
  102. case TYPE.EIGHT:
  103. this.getComponent(cc.Sprite).spriteFrame = this.picEight;
  104. break;
  105. case TYPE.BOMB:
  106. this.getComponent(cc.Sprite).spriteFrame = this.picBomb;
  107. break;
  108. default:break;
  109. }
  110. }
  111. });

在Game脚本里设置一些游戏参数

Game.js

  1. const GAME_STATE = cc.Enum({
  2. PREPARE:-1,
  3. PLAY:-1,
  4. DEAD:-1,
  5. WIN:-1
  6. });
  7. const TOUCH_STATE = cc.Enum({
  8. BLANK:-1,
  9. FLAG:-1,
  10. });
  11. cc.Class({
  12. extends: cc.Component,
  13. properties: {
  14. tilesLayout:cc.Node,
  15. tile:cc.Prefab,
  16. btnShow:cc.Node,
  17. tiles:[],//用一个数组保存所有tile的引用,数组下标就是相应tile的tag
  18. picPrepare:cc.SpriteFrame,
  19. picPlay:cc.SpriteFrame,
  20. picDead:cc.SpriteFrame,
  21. picWin:cc.SpriteFrame,
  22. gameState:{
  23. default:GAME_STATE.PREPARE,
  24. type:GAME_STATE,
  25. },
  26. touchState:{//左键点开tile,右键插旗
  27. default:TOUCH_STATE.BLANK,
  28. type:TOUCH_STATE,
  29. },
  30. row:0,
  31. col:0,
  32. bombNum:0,
  33. },
  34. onLoad: function () {
  35. this.Tile = require("Tile");
  36. var self = this;
  37. for(let y=0;y<this.row;y++){
  38. for(let x=0;x<this.col;x++){
  39. let tile = cc.instantiate(this.tile);
  40. tile.tag = y*this.col+x;
  41. tile.on(cc.Node.EventType.MOUSE_UP,function(event){
  42. if(event.getButton() === cc.Event.EventMouse.BUTTON_LEFT){
  43. self.touchState = TOUCH_STATE.BLANK;
  44. }else if(event.getButton() === cc.Event.EventMouse.BUTTON_RIGHT){
  45. self.touchState = TOUCH_STATE.FLAG;
  46. }
  47. self.onTouchTile(this);
  48. });
  49. this.tilesLayout.addChild(tile);
  50. this.tiles.push(tile);
  51. }
  52. }
  53. this.newGame();
  54. },
  55. newGame:function(){
  56. //初始化场景
  57. for(let n=0;n<this.tiles.length;n++){
  58. this.tiles[n].getComponent("Tile").type = this.Tile.TYPE.ZERO;
  59. this.tiles[n].getComponent("Tile").state = this.Tile.STATE.NONE;
  60. }
  61. //添加雷
  62. var tilesIndex = [];
  63. for(var i=0;i<this.tiles.length;i++){
  64. tilesIndex[i] = i;
  65. }
  66. for(var j=0;j<this.bombNum;j++){
  67. var n = Math.floor(Math.random()*tilesIndex.length);
  68. this.tiles[tilesIndex[n]].getComponent("Tile").type = this.Tile.TYPE.BOMB;
  69. tilesIndex.splice(n,1);//从第n个位置删除一个元素
  70. //如果没有splice方法可以用这种方式
  71. // tilesIndex[n] = tilesIndex[tilesIndex.length-1];
  72. // tilesIndex.length--;
  73. }
  74. //标记雷周围的方块
  75. for(var k=0;k<this.tiles.length;k++){
  76. var tempBomb = 0;
  77. if(this.tiles[k].getComponent("Tile").type == this.Tile.TYPE.ZERO){
  78. var roundTiles = this.tileRound(k);
  79. for(var m=0;m<roundTiles.length;m++){
  80. if(roundTiles[m].getComponent("Tile").type == this.Tile.TYPE.BOMB){
  81. tempBomb++;
  82. }
  83. }
  84. this.tiles[k].getComponent("Tile").type = tempBomb;
  85. }
  86. }
  87. this.gameState = GAME_STATE.PLAY;
  88. this.btnShow.getComponent(cc.Sprite).spriteFrame = this.picPlay;
  89. },
  90. //返回tag为i的tile的周围tile数组
  91. tileRound:function(i){
  92. var roundTiles = [];
  93. if(i%this.col > 0){//left
  94. roundTiles.push(this.tiles[i-1]);
  95. }
  96. if(i%this.col > 0 && Math.floor(i/this.col) > 0){//left bottom
  97. roundTiles.push(this.tiles[i-this.col-1]);
  98. }
  99. if(i%this.col > 0 && Math.floor(i/this.col) < this.row-1){//left top
  100. roundTiles.push(this.tiles[i+this.col-1]);
  101. }
  102. if(Math.floor(i/this.col) > 0){//bottom
  103. roundTiles.push(this.tiles[i-this.col]);
  104. }
  105. if(Math.floor(i/this.col) < this.row-1){//top
  106. roundTiles.push(this.tiles[i+this.col]);
  107. }
  108. if(i%this.col < this.col-1){//right
  109. roundTiles.push(this.tiles[i+1]);
  110. }
  111. if(i%this.col < this.col-1 && Math.floor(i/this.col) > 0){//rihgt bottom
  112. roundTiles.push(this.tiles[i-this.col+1]);
  113. }
  114. if(i%this.col < this.col-1 && Math.floor(i/this.col) < this.row-1){//right top
  115. roundTiles.push(this.tiles[i+this.col+1]);
  116. }
  117. return roundTiles;
  118. },
  119. onTouchTile:function(touchTile){
  120. if(this.gameState != GAME_STATE.PLAY){
  121. return;
  122. }
  123. switch(this.touchState){
  124. case TOUCH_STATE.BLANK:
  125. if(touchTile.getComponent("Tile").type === 9){
  126. touchTile.getComponent("Tile").state = this.Tile.STATE.CLIKED;
  127. this.gameOver();
  128. return;
  129. }
  130. var testTiles = [];
  131. if(touchTile.getComponent("Tile").state === this.Tile.STATE.NONE){
  132. testTiles.push(touchTile);
  133. while(testTiles.length){
  134. var testTile = testTiles.pop();
  135. if(testTile.getComponent("Tile").type === 0){
  136. testTile.getComponent("Tile").state = this.Tile.STATE.CLIKED;
  137. var roundTiles = this.tileRound(testTile.tag);
  138. for(var i=0;i<roundTiles.length;i++){
  139. if(roundTiles[i].getComponent("Tile").state == this.Tile.STATE.NONE){
  140. testTiles.push(roundTiles[i]);
  141. }
  142. }
  143. }else if(testTile.getComponent("Tile").type > 0 && testTile.getComponent("Tile").type < 9){
  144. testTile.getComponent("Tile").state = this.Tile.STATE.CLIKED;
  145. }
  146. }
  147. this.judgeWin();
  148. }
  149. break;
  150. case TOUCH_STATE.FLAG:
  151. if(touchTile.getComponent("Tile").state == this.Tile.STATE.NONE){
  152. touchTile.getComponent("Tile").state = this.Tile.STATE.FLAG;
  153. }else if(touchTile.getComponent("Tile").state == this.Tile.STATE.FLAG){
  154. touchTile.getComponent("Tile").state = this.Tile.STATE.NONE;
  155. }
  156. break;
  157. default:break;
  158. }
  159. },
  160. judgeWin:function(){
  161. var confNum = 0;
  162. //判断是否胜利
  163. for(let i=0;i<this.tiles.length;i++){
  164. if(this.tiles[i].getComponent("Tile").state === this.Tile.STATE.CLIKED){
  165. confNum++;
  166. }
  167. }
  168. if(confNum === this.tiles.length-this.bombNum){
  169. this.gameState = GAME_STATE.WIN;
  170. this.btnShow.getComponent(cc.Sprite).spriteFrame = this.picWin;
  171. }
  172. },
  173. gameOver:function(){
  174. this.gameState = GAME_STATE.DEAD;
  175. this.btnShow.getComponent(cc.Sprite).spriteFrame = this.picDead;
  176. },
  177. onBtnShow:function(){
  178. if(this.gameState === GAME_STATE.PREPARE){
  179. this.newGame();
  180. }
  181. if(this.gameState === GAME_STATE.DEAD){
  182. // this.bombNum--;
  183. this.newGame();
  184. }
  185. if(this.gameState === GAME_STATE.WIN){
  186. // this.bombNum++;
  187. this.newGame();
  188. }
  189. }
  190. });

最后加上扫雷算法

2. 扫雷算法

2.1 随机添加地雷:

这里要保证每次添加的地雷位置都不能重复

  1. var tilesIndex = [];
  2. for(var i=0;i<this.tiles.length;i++){
  3. tilesIndex[i] = i;
  4. }
  5. for(var j=0;j<this.bombNum;j++){
  6. var n = Math.floor(Math.random()*tilesIndex.length);
  7. this.tiles[tilesIndex[n]].getComponent("Tile").type = this.Tile.TYPE.BOMB;
  8. tilesIndex.splice(n,1);//从第n个位置删除一个元素
  9. //如果没有splice方法可以用这种方式
  10. // tilesIndex[n] = tilesIndex[tilesIndex.length-1];
  11. // tilesIndex.length--;
  12. }

2.2 计算Tile周围雷的数目

先要建立一个能得到Tile周围Tile数组的方法(Game.js里的tileRound方法),要注意边界检测,然后对返回的Tile数组判断Type就行了

2.3 点开相连空地区域

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

简单的流程图示意:

  1. 从点开的这个Tile进行处理,调用tileRound方法判断周围Tile是否是空地且未被点开,如果不是,则跳过,如果是,则将其自动点开,同时把这几个位置加入栈循环判断。
  2. 流程图如下:
  3. 当前位置是空白位置?----否---> 非空白的处理
  4. |
  5. |
  6. |
  7. V
  8. 入栈
  9. |
  10. V
  11. +--->栈为空?-------->是---> 结束
  12. | |
  13. | |否
  14. | |
  15. | V
  16. | 出栈一个元素
  17. | |
  18. | V
  19. | 点开该元素所指的位置
  20. | |
  21. | V
  22. | 上左下右的位置如果是空白且未点开则入栈
  23. | |
  24. --------+

3. 总结

来一句算法的名言:所有递归都能转化为循环

并且不得不感慨,cocos creator 更多是为策划服务,而非开发。开发入门可以,太高深的算法就死了,当然,creator编辑器一直在进化。

本教程部分资源来源于网络。

上传的附件 cloud_download cocos creator算法一.zip ( 3.07mb, 29次下载 )

keyboard_arrow_left上一篇 : 32位系统上获取SSDT表地址以及从中获取指定SSDT函数的地址 查找并使用PspTerminateThreadByPointer函数强制结束进程可以杀360进程 : 下一篇keyboard_arrow_right



TrueLove
2018-12-02 17:56:29
扫雷~~666~~正好跟着练练
你正哥来了
2019-02-24 11:28:16
运行出现下面问题:(小白初学,请指教!) Game.js:43 Uncaught TypeError: Cannot set property tag of # which has only a getter at Game.onLoad (Game.js:43) at CCClass.eval [as _invoke] (eval at createInvokeImpl (component-scheduler.js:256), :3:65) at CCClass.invoke (component-scheduler.js:154) at CCClass.activateNode (node-activator.js:227) at cc_Scene._activate (CCScene.js:107) at 27.cc.Director.runSceneImmediate (CCDirector.js:467) at boot.js:360 at CCLoader. (CCAssetLibrary.js:244) at CCLoader.js:271 at utils.js:81 onLoad @ Game.js:43 (anonymous) @ VM652:3 invoke @ component-scheduler.js:154 activateNode @ node-activator.js:227 _activate @ CCScene.js:107 runSceneImmediate @ CCDirector.js:467 (anonymous) @ boot.js:360 (anonymous) @ CCAssetLibrary.js:244 (anonymous) @ CCLoader.js:271 (anonymous) @ utils.js:81 setTimeout (async) callInNextTick @ utils.js:80 (anonymous) @ CCLoader.js:267 121.proto.allComplete @ loading-items.js:529 121.proto.itemComplete @ loading-items.js:721 121.LoadingItems.itemComplete @ loading-items.js:379 124.proto.flowOut @ pipeline.js:319 (anonymous) @ pipeline.js:67 (anonymous) @ uuid-loader.js:180 (anonymous) @ CCLoader.js:314 121.proto.allComplete @ loading-items.js:529 121.proto.itemComplete @ loading-items.js:721 121.LoadingItems.itemComplete @ loading-items.js:379 124.proto.flowOut @ pipeline.js:319 (anonymous) @ pipeline.js:67 (anonymous) @ uuid-loader.js:180 (anonymous) @ CCLoader.js:314 121.proto.allComplete @ loading-items.js:529 121.proto.itemComplete @ loading-items.js:721 121.LoadingItems.itemComplete @ loading-items.js:379 124.proto.flowOut @ pipeline.js:319 (anonymous) @ pipeline.js:67 (anonymous) @ uuid-loader.js:180 (anonymous) @ CCLoader.js:314 121.proto.allComplete @ loading-items.js:529 121.proto.itemComplete @ loading-items.js:721 121.LoadingItems.itemComplete @ loading-items.js:379 124.proto.flowOut @ pipeline.js:319 (anonymous) @ pipeline.js:67 (anonymous) @ uuid-loader.js:180 (anonymous) @ CCLoader.js:314 121.proto.allComplete @ loading-items.js:529 121.proto.itemComplete @ loading-items.js:721 121.LoadingItems.itemComplete @ loading-items.js:379 124.proto.flowOut @ pipeline.js:319 flow @ pipeline.js:87 (anonymous) @ pipeline.js:64 (anonymous) @ downloader.js:266 loadCallback @ downloader.js:106 load (async) (anonymous) @ downloader.js:122 downloadImage @ downloader.js:100 117.Downloader.handle @ downloader.js:263 flow @ pipeline.js:51 flow @ pipeline.js:84 124.proto.flowIn @ pipeline.js:275 121.proto.append @ loading-items.js:508 111.proto.flowInDeps @ CCLoader.js:325 loadDepends @ uuid-loader.js:111 loadUuid @ uuid-loader.js:290 120.Loader.handle @ loader.js:203 flow @ pipeline.js:51 (anonymous) @ pipeline.js:64 (anonymous) @ downloader.js:266 xhr.onload @ text-downloader.js:39 load (async) 126.module.exports @ text-downloader.js:36 downloadUuid @ downloader.js:131 117.Downloader.handle @ downloader.js:263 flow @ pipeline.js:51 (anonymous) @ pipeline.js:64 (anonymous) @ asset-loader.js:83 queryAssetInfo @ CCAssetLibrary.js:182 112.AssetLoader.handle @ asset-loader.js:49 flow @ pipeline.js:51 124.proto.flowIn @ pipeline.js:275 121.proto.append @ loading-items.js:508 111.proto.flowInDeps @ CCLoader.js:325 loadDepends @ uuid-loader.js:111 loadUuid @ uuid-loader.js:290 120.Loader.handle @ loader.js:203 flow @ pipeline.js:51 (anonymous) @ pipeline.js:64 (anonymous) @ downloader.js:266 xhr.onload @ text-downloader.js:39 load (async) 126.module.exports @ text-downloader.js:36 downloadUuid @ downloader.js:131 117.Downloader.handle @ downloader.js:263 flow @ pipeline.js:51 (anonymous) @ pipeline.js:64 (anonymous) @ asset-loader.js:83 queryAssetInfo @ CCAssetLibrary.js:182 112.AssetLoader.handle @ asset-loader.js:49 flow @ pipeline.js:51 124.proto.flowIn @ pipeline.js:275 121.proto.append @ loading-items.js:508 111.proto.flowInDeps @ CCLoader.js:325 loadDepends @ uuid-loader.js:111 loadUuid @ uuid-loader.js:290 120.Loader.handle @ loader.js:203 flow @ pipeline.js:51 (anonymous) @ pipeline.js:64 (anonymous) @ downloader.js:266 xhr.onload @ text-downloader.js:39 load (async) 126.module.exports @ text-downloader.js:36 downloadUuid @ downloader.js:131 117.Downloader.handle @ downloader.js:263 flow @ pipeline.js:51 (anonymous) @ pipeline.js:64 (anonymous) @ asset-loader.js:83 queryAssetInfo @ CCAssetLibrary.js:182 112.AssetLoader.handle @ asset-loader.js:49 flow @ pipeline.js:51 124.proto.flowIn @ pipeline.js:275 121.proto.append @ loading-items.js:508 111.proto.flowInDeps @ CCLoader.js:325 loadDepends @ uuid-loader.js:111 loadUuid @ uuid-loader.js:290 120.Loader.handle @ loader.js:203 flow @ pipeline.js:51 flow @ pipeline.js:42 flow @ pipeline.js:42 124.proto.flowIn @ pipeline.js:275 121.proto.append @ loading-items.js:508 111.proto.load @ CCLoader.js:290 loadJson @ CCAssetLibrary.js:228 (anonymous) @ boot.js:352 (anonymous) @ CCLoader.js:271 (anonymous) @ utils.js:81 setTimeout (async) callInNextTick @ utils.js:80 (anonymous) @ CCLoader.js:267 121.proto.allComplete @ loading-items.js:529 121.proto.itemComplete @ loading-items.js:721 121.LoadingItems.itemComplete @ loading-items.js:379 124.proto.flowOut @ pipeline.js:319 flow @ pipeline.js:87 (anonymous) @ pipeline.js:64 (anonymous) @ downloader.js:266 xhr.onload @ text-downloader.js:39 load (async) 126.module.exports @ text-downloader.js:36 117.Downloader.handle @ downloader.js:263 flow @ pipeline.js:51 flow @ pipeline.js:84 124.proto.flowIn @ pipeline.js:275 121.proto.append @ loading-items.js:508 111.proto.load @ CCLoader.js:290 (anonymous) @ boot.js:339 _prepareFinished @ CCGame.js:405 (anonymous) @ CCGame.js:490 (anonymous) @ CCLoader.js:274 (anonymous) @ utils.js:81 setTimeout (async) callInNextTick @ utils.js:80 (anonymous) @ CCLoader.js:267 121.proto.allComplete @ loading-items.js:529 121.proto.itemComplete @ loading-items.js:721 121.LoadingItems.itemComplete @ loading-items.js:379 124.proto.flowOut @ pipeline.js:319 flow @ pipeline.js:87 (anonymous) @ pipeline.js:64 (anonymous) @ downloader.js:266 loadHandler @ downloader.js:63 load (async) downloadScript @ downloader.js:71 117.Downloader.handle @ downloader.js:263 flow @ pipeline.js:51 flow @ pipeline.js:84 124.proto.flowIn @ pipeline.js:275 121.proto.append @ loading-items.js:508 111.proto.load @ CCLoader.js:290 prepare @ CCGame.js:488 run @ CCGame.js:508 onload @ boot.js:313 window.onload @ boot.js:175 load (async) (anonymous) @ boot.js:170 (anonymous) @ boot.js:398
teardrop
2019-02-24 15:24:51
bd

eject