Cocos扫雷游戏核心算法思想

一、 扫雷游戏实现核心思路解析

数据和视图尽量分离。采用面向对象的实现设计数据模块。格子作为一类对象,雷场作为一类对象,雷场由格子构成。

二、 扫雷游戏核心数据模块

1.  Cell.js单元格类

// 单个单元格,用于保存数据

// x,y,坐标,或者行和列,

// info显示文本,空表示什么都没有,周边也没有地雷// *表示此处是一颗地雷,数字表示其周边有几颗地雷function Cell(x, y, info){

this.x = x;

this.y = y;

this.info = info;

};

module.exports = Cell;

2.  MineField.js雷场类

// 雷场(行数,列数,地雷数)

function MineField(rowNum, colNum, mineNum){

this.rowNum = rowNum;

this.colNum = colNum;

this.mineNum = mineNum;

// 调用【1】画格子

this.init();

// 调用【2】藏地雷

this.hideMine();

// 调用【3】留暗号

this.markNumber();

};

module.exports = MineField;

【1】画格子

雷场由Cell的对象构成的数组组成,实质就是给雷场的cells数组赋值。

// 【1】利用原型扩展方法:初始化雷场的所有格子数据

MineField.prototype.init = function(){

let cellsNum = this.rowNum * this.colNum;

this.cells = new Array(cellsNum);

for(let i=0; i<this.rowNum; i++){

for(let j=0; j<this.colNum; j++){

let index = this.getIndexByXY(i,j);//this.colNum * i + j;

this.cells[index] = new Cell(i, j, "");

        }

    }

};

补充提炼通过坐标获取格子索引的方法,以供后续其它地方用:

// 【1-1】通过坐标获取单元格的所处格子的索引

MineField.prototype.getIndexByXY = function(x, y){

return x * this.colNum + y;

}

// 通过单元格对象获取单元格所处格子的索引

MineField.prototype.getIndexByCell = function(cell){

return this.getIndexByXY(cell.x, cell.y);

}

在此文件头部引入Cell类:

var Cell = require("Cell");

【2】藏地雷

实质就是修改雷场的cells数组中的随机一些索引的cell的info属性值。

// 【2】藏地雷:将地雷数据设置到this.cells中的Cell的info中去

MineField.prototype.hideMine = function(){

// 随机无重复元素的数组,且范围限定在[0,this.rowNum*this.colNum);

let end = this.colNum * this.rowNum;

// 记录所有地雷所在的cells的索引

this.mineIndexs = ArrayUtils.randChoiseFromTo(0, end, this.mineNum);

    console.log("地雷位置序号:",this.mineIndexs);

// 找到相应格子的位置,设置其cell对象的info属性为*,表示地雷

this.mineCells = new Array(this.mineNum);

for(let i=0,len=this.mineIndexs.length; i<len; i++){

let index = this.mineIndexs[i];

let cell = this.cells[index];

        cell.info = "*";

// 保存所有地雷所在的单元格

this.mineCells[i] = cell;

    }

};

方法randChoiseFromTo参考四、ArrayUtils.js数组工具类。

在此文件头部引入数组工具类:

var ArrayUtils = require("ArrayUtils");

【3】留暗号

实质就是在地雷周边8个格子中标上数字,数值为此单元格周边8个单元格中雷的数量。如下图所示:

// 【3】留暗号:标记地雷周围所有单元格的数字,也就是设置其info属性

MineField.prototype.markNumber = function(){

// 遍历所有地雷单元格,每次找到其周边非雷格子,给其数字加1

console.log("this.mineCells:",this.mineCells)

for(let i=0,len=this.mineCells.length; i<len; i++){

// 【3-1】拿到地雷单元格 周围的所有的非雷单元格(应该是数字的单元格)

let numberCells = this.getNumberCellsAround(this.mineCells[i]);

// 【3-2】更新地雷周围所有非雷(数字)单元格的数字标记

this.updateNumberMarks(numberCells);

    }

};

【3-1】获取某颗地雷周围所有单元格

如下图所示,假如要获取(0,0)周围8个单元格,则偏移量就是其周边8个单元格的坐标:

同时,偏移后,我们还要判断这个格子是否超出雷场。即便宜后x、y值不能小于0,且不能大于行或列的最大值。

偏移量offset和判断是否超出雷场区域的方法如下:

// 【3-1-1】周围8个坐标相对于中心坐标(0,0)的偏移量

var offset = [{x:-1,y:-1},{x:0,y:-1},{x:1,y:-1},

    {x:-1,y:0},{x:1,y:0},

    {x:-1,y:1},{x:0,y:1},{x:1,y:1},

];

// 【3-1-2】判断坐标为x,y的单元格cell是否超出了区域

MineField.prototype.outOfFiled = function(x, y){

return x<0 || x>=this.rowNum || y<0 || y>=this.colNum;

};

如果地雷周围的单元格是数字,则我们不需要计算数值,应排除。

// 【3-1】拿到cell周围的所有的非雷单元格(应该是数字的单元格)

MineField.prototype.getNumberCellsAround = function(cell){

let result = [];

for(let i=0,len=offset.length; i<len; i++){

// 【3-1-1】得到相对于cell偏移后的x、y坐标

let x = cell.x + offset[i].x;

let y = cell.y + offset[i].y

// 【3-1-2】判断坐标为x,y的单元格cell是否超出了区域

if(this.outOfFiled(x, y)){

continue;

        }

// 如果是地雷继续下一次循环

let index = this.getIndexByXY(x,y); //x*this.colNum + y;

let cellSide = this.cells[index];

if (cellSide.info === "*"){

continue;

        }

// 如果没有超出雷场区域,且为非雷单元格,则添加到数组中

result.push(new Cell(x, y, ""));

    }

return result;

};

【3-2】更新所有地雷周围所有非雷(数字)单元格的数字标记

// 【3-2】更新地雷周围所有非雷(数字)单元格的数字标记

MineField.prototype.updateNumberMarks = function(numberCells){

/**

    * 设置逻辑:①如果原来info属性为*,不需要设置,【已经排除了】

    *      ②如果原来info属性为"",证明是第一次标记,标记info为1

    *      ③如果原来属性不为*,也不为空,则在原有值基础上加1

    */

for(let i=0,len=numberCells.length; i<len; i++){

let index = this.getIndexByCell(numberCells[i]);

if(this.cells[index].info === ""){

this.cells[index].info = 1;

        }else{

let num = parseInt(this.cells[index].info);

this.cells[index].info = ++num;

        }

    }

};

三、 ArrayUtils.js数组工具类(直接使用)

// 数组工具类

var ArrayUtils = function(){};

// 【1】初始化得到有序元素数组:从[start,end)的自然数序列

ArrayUtils.initOrderArray = function(start, end){

let sortArray = [];

for(let i= start; i<end; i++){

        sortArray.push(i);

    }

return sortArray;

};

// 【2】从数组arr中随机抽取count个元素,返回数组

ArrayUtils.randChoiseFromArr = function(arr, count){

let result = arr;

// 随机排序,打乱顺序

result.sort(function(){

return 0.5 - Math.random();

    });

// 返回打乱顺序后的数组中的前count个元素

return result.slice(0,count);

};

// 【3】从从start到end中的连续整数中随机抽取count个数字

ArrayUtils.randChoiseFromTo = function(start, end, count){

let arr = this.initOrderArray(start,end);

return this.randChoiseFromArr(arr, count);

};

// 【2】-【方式二】从数组arr中随机抽取count个元素,返回数组

ArrayUtils.getRandomArrayElements = function(arr, count) {

// 从0位置取到结束位置存入shffled数组

let shuffled = arr.slice(0);

let i = arr.length;

let min = i - count;

let temp = 0;

let index = 0;

// 随机一个位置的元素和最后一个元素交换

    // 随机一个位置元素和倒数第二个元素交换

    // 假设i=8,count=3,则min=5,

    // 循环体中[i]=7,6,5,也就是说最后三个元素要从数组中随机取

    // 循环结束后,从min=5的位置取到结束,即取3个元素。

while(i-- > min) {

        index = Math.floor((i + 1) * Math.random());

        temp = shuffled[index];

        shuffled[index] = shuffled[i];

        shuffled[i] = temp;

    }

return shuffled.slice(min);

};

module.exports = ArrayUtils;

四、 数据校验测试

1.  Game_mgr.js挂载到Canvas节点上

var MineField = require("MineField");cc.Class({

extends: cc.Component,

    properties: {

        row : 9,

        col : 9,

        mineNum : 10,

    },

    onLoad () {

// 横竖9个单元格,共10颗雷

this.mineField = new MineField(this.row, this.col, this.mineNum);        console.log(this.mineField);    },

});

挂载到Canvas节点上,运行测试结果如下:

3.  优化测试-验证数据正确与否

发现显示结果不便于核实数据是否正确,我们优化下,在MineField中添加printResult方法:

// 【4】提供打印测试的方法,便于观察数据是否正确

MineField.prototype.printResult = function(){

for(let i=0; i<this.rowNum; i++){

let line = "| ";

for(let j=0; j<this.colNum; j++){

let cell = this.cells[i*this.colNum + j];

            line = line.concat(cell.info + " | ");

        }

        console.log(line);

    }

};

为了打印时能够上下对齐,我们将MineField.js代码中原有""(空字符串)替换成" "(空格)。

然后,将Game_mgr.js中的代码做如下调整:

//console.log(this.mineField);

this.mineField.printResult();

运行,浏览器console窗口如下:正确!

五、 数据与视图绑定

新建一个空节点MineField作为雷场,将res中的block拖到MineField内,作为地砖,在block节点内新建空节点around_bombs,在此节点上添加Label组件,用于显示此地砖的信息info。之后将block做成预制体,便于动态生成雷场所有地砖。

动态生成的过程中,将每个地砖跟MineField的cells数组中的元素绑定。

在Game_mgr.js的properties中添加属性,同时通过编辑器绑定属性值:

// 地砖预制体、和根节点

block_prefab : {type:cc.Prefab, default:null,},

block_root : {type:cc.Node, default:null,},

在Game_mgr.js的onLoad方法中添加如下代码:

// 初始化游戏界面

this.showMineField();

在Game_mgr.js中增加showMineField实现:

// 显示雷场格子

showMineField(){

// 获取地砖预制体的宽度

var block_width = this.block_prefab.data.width;

// 计算第一个格子相对于中心锚点的偏移量

var x_offset = - block_width * this.col/2;

var y_offset = block_width * this.row/2;

// block的锚点也在中心,而不是左下角,故初始偏移量要往右上角移动

x_offset += block_width/2;

    y_offset += block_width*2;      // 稍微往上移点

for(var i=0; i<this.row; i++){

for(var j=0; j<this.col; j++){

var block = cc.instantiate(this.block_prefab);

// 【*】将每个地砖跟MineField的cells数组中的元素绑定

var index = this.mineField.getIndexByXY(i,j);

            block.cell = this.mineField.cells[index];

this.block_root.addChild(block);

// 注意:i是行,j是列,当然行列数相等是不会有影响,

            // 【*】行列不等时会影响后续边界判断逻辑

  block.setPosition(j*block_width+x_offset, y_offset-i*block_width);

            console.log("block[",i,j,"]=",block.cell.toString());

        }

    }

},

在Cell.js中增加toString方法显示对象信息:

Cell.prototype.toString = function () {

  return "{ x : " + this.x + ", y : " + this.y + ", info : " + this.info + " }";

}

编译运行,结果如下:

将信息显示到地砖上:

block.cell = this.mineField.cells[index];

// 显示地砖内部信息

this.showBlockInnerInfo(block);

信息显示到地砖上的实现方法(便于后续触摸调用):

// 显示地砖内部信息

showBlockInnerInfo(block){

    block.getChildByName("around_bombs").getComponent(cc.Label).string = block.cell.info;

},

编译运行结果如下:

仔细思考,发现刚才Game_mgr.js其实就是控制MineField这个节点的,故我们将其修改为MineField_Ctrl.js。将Canvas上的用户自定义组件remove,在MineField节点上添加MineField_Ctrl组件,将其中block_root属性去掉,将代码中this.block_root替换为this.node。
给大家推荐个学习交流群 点击链接即可加入群链接

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,089评论 1 32
  • VBA订制工具栏 http://club.excelhome.net/thread-1047254-1-1.htm...
    大海一滴写字的地方阅读 2,239评论 0 0
  • Android的databinding已经出来好久了,一直也没有用到项目中,这两天在郭霖的公众号上看到分析data...
    wutongke阅读 9,614评论 8 32
  • 七月末,暑气正浓。天气热得像蒸笼一样。热得人喘息困难。但是由于到了雨季,前几天刚下了一场中雨,使得热辣辣的天气得到...
    苍鹰在上阅读 190评论 0 1
  • 儿子,还有十来天你就回来了,日子越近,似乎越难熬,你大概也是归心似箭吧?不然近期为何频繁地汇报你的情况,不时还要视...
    麦子2008阅读 1,691评论 29 13