Cocos掃雷游戲核心算法思想

一、 掃雷游戲?qū)崿F(xiàn)核心思路解析

數(shù)據(jù)和視圖盡量分離。采用面向?qū)ο蟮膶崿F(xiàn)設(shè)計數(shù)據(jù)模塊。格子作為一類對象,雷場作為一類對象,雷場由格子構(gòu)成。

二、 掃雷游戲核心數(shù)據(jù)模塊

1.? Cell.js單元格類

// 單個單元格,用于保存數(shù)據(jù)

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

// info顯示文本,空表示什么都沒有,周邊也沒有地雷// *表示此處是一顆地雷,數(shù)字表示其周邊有幾顆地雷function Cell(x, y, info){

this.x = x;

this.y = y;

this.info = info;

};

module.exports = Cell;

2.? MineField.js雷場類

// 雷場(行數(shù),列數(shù),地雷數(shù))

function MineField(rowNum, colNum, mineNum){

this.rowNum = rowNum;

this.colNum = colNum;

this.mineNum = mineNum;

// 調(diào)用【1】畫格子

this.init();

// 調(diào)用【2】藏地雷

this.hideMine();

// 調(diào)用【3】留暗號

this.markNumber();

};

module.exports = MineField;

【1】畫格子

雷場由Cell的對象構(gòu)成的數(shù)組組成,實質(zhì)就是給雷場的cells數(shù)組賦值。

// 【1】利用原型擴展方法:初始化雷場的所有格子數(shù)據(jù)

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, "");

? ? ? ? }

? ? }

};

補充提煉通過坐標獲取格子索引的方法,以供后續(xù)其它地方用:

// 【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】藏地雷

實質(zhì)就是修改雷場的cells數(shù)組中的隨機一些索引的cell的info屬性值。

// 【2】藏地雷:將地雷數(shù)據(jù)設(shè)置到this.cells中的Cell的info中去

MineField.prototype.hideMine = function(){

// 隨機無重復(fù)元素的數(shù)組,且范圍限定在[0,this.rowNum*this.colNum);

let end = this.colNum * this.rowNum;

// 記錄所有地雷所在的cells的索引

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

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

// 找到相應(yīng)格子的位置,設(shè)置其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數(shù)組工具類。

在此文件頭部引入數(shù)組工具類:

var ArrayUtils = require("ArrayUtils");

【3】留暗號

實質(zhì)就是在地雷周邊8個格子中標上數(shù)字,數(shù)值為此單元格周邊8個單元格中雷的數(shù)量。如下圖所示:

// 【3】留暗號:標記地雷周圍所有單元格的數(shù)字,也就是設(shè)置其info屬性

MineField.prototype.markNumber = function(){

// 遍歷所有地雷單元格,每次找到其周邊非雷格子,給其數(shù)字加1

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

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

// 【3-1】拿到地雷單元格 周圍的所有的非雷單元格(應(yīng)該是數(shù)字的單元格)

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

// 【3-2】更新地雷周圍所有非雷(數(shù)字)單元格的數(shù)字標記

this.updateNumberMarks(numberCells);

? ? }

};

【3-1】獲取某顆地雷周圍所有單元格

如下圖所示,假如要獲?。?,0)周圍8個單元格,則偏移量就是其周邊8個單元格的坐標:

同時,偏移后,我們還要判斷這個格子是否超出雷場。即便宜后x、y值不能小于0,且不能大于行或列的最大值。

偏移量offset和判斷是否超出雷場區(qū)域的方法如下:

// 【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是否超出了區(qū)域

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

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

};

如果地雷周圍的單元格是數(shù)字,則我們不需要計算數(shù)值,應(yīng)排除。

// 【3-1】拿到cell周圍的所有的非雷單元格(應(yīng)該是數(shù)字的單元格)

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是否超出了區(qū)域

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

continue;

? ? ? ? }

// 如果是地雷繼續(xù)下一次循環(huán)

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

let cellSide = this.cells[index];

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

continue;

? ? ? ? }

// 如果沒有超出雷場區(qū)域,且為非雷單元格,則添加到數(shù)組中

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

? ? }

return result;

};

【3-2】更新所有地雷周圍所有非雷(數(shù)字)單元格的數(shù)字標記

// 【3-2】更新地雷周圍所有非雷(數(shù)字)單元格的數(shù)字標記

MineField.prototype.updateNumberMarks = function(numberCells){

/**

? ? * 設(shè)置邏輯:①如果原來info屬性為*,不需要設(shè)置,【已經(jīng)排除了】

? ? *? ? ? ②如果原來info屬性為"",證明是第一次標記,標記info為1

? ? *? ? ? ③如果原來屬性不為*,也不為空,則在原有值基礎(chǔ)上加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數(shù)組工具類(直接使用)

// 數(shù)組工具類

var ArrayUtils = function(){};

// 【1】初始化得到有序元素數(shù)組:從[start,end)的自然數(shù)序列

ArrayUtils.initOrderArray = function(start, end){

let sortArray = [];

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

? ? ? ? sortArray.push(i);

? ? }

return sortArray;

};

// 【2】從數(shù)組arr中隨機抽取count個元素,返回數(shù)組

ArrayUtils.randChoiseFromArr = function(arr, count){

let result = arr;

// 隨機排序,打亂順序

result.sort(function(){

return 0.5 - Math.random();

? ? });

// 返回打亂順序后的數(shù)組中的前count個元素

return result.slice(0,count);

};

// 【3】從從start到end中的連續(xù)整數(shù)中隨機抽取count個數(shù)字

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

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

return this.randChoiseFromArr(arr, count);

};

// 【2】-【方式二】從數(shù)組arr中隨機抽取count個元素,返回數(shù)組

ArrayUtils.getRandomArrayElements = function(arr, count) {

// 從0位置取到結(jié)束位置存入shffled數(shù)組

let shuffled = arr.slice(0);

let i = arr.length;

let min = i - count;

let temp = 0;

let index = 0;

// 隨機一個位置的元素和最后一個元素交換

? ? // 隨機一個位置元素和倒數(shù)第二個元素交換

? ? // 假設(shè)i=8,count=3,則min=5,

? ? // 循環(huán)體中[i]=7,6,5,也就是說最后三個元素要從數(shù)組中隨機取

? ? // 循環(huán)結(jié)束后,從min=5的位置取到結(jié)束,即取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;

四、 數(shù)據(jù)校驗測試

1.? Game_mgr.js掛載到Canvas節(jié)點上

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節(jié)點上,運行測試結(jié)果如下:

3.? 優(yōu)化測試-驗證數(shù)據(jù)正確與否

發(fā)現(xiàn)顯示結(jié)果不便于核實數(shù)據(jù)是否正確,我們優(yōu)化下,在MineField中添加printResult方法:

// 【4】提供打印測試的方法,便于觀察數(shù)據(jù)是否正確

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中的代碼做如下調(diào)整:

//console.log(this.mineField);

this.mineField.printResult();

運行,瀏覽器console窗口如下:正確!

五、 數(shù)據(jù)與視圖綁定

新建一個空節(jié)點MineField作為雷場,將res中的block拖到MineField內(nèi),作為地磚,在block節(jié)點內(nèi)新建空節(jié)點around_bombs,在此節(jié)點上添加Label組件,用于顯示此地磚的信息info。之后將block做成預(yù)制體,便于動態(tài)生成雷場所有地磚。

動態(tài)生成的過程中,將每個地磚跟MineField的cells數(shù)組中的元素綁定。

在Game_mgr.js的properties中添加屬性,同時通過編輯器綁定屬性值:

// 地磚預(yù)制體、和根節(jié)點

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

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

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

// 初始化游戲界面

this.showMineField();

在Game_mgr.js中增加showMineField實現(xiàn):

// 顯示雷場格子

showMineField(){

// 獲取地磚預(yù)制體的寬度

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數(shù)組中的元素綁定

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

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

this.block_root.addChild(block);

// 注意:i是行,j是列,當(dāng)然行列數(shù)相等是不會有影響,

? ? ? ? ? ? // 【*】行列不等時會影響后續(xù)邊界判斷邏輯

? 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 + " }";

}

編譯運行,結(jié)果如下:

將信息顯示到地磚上:

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

// 顯示地磚內(nèi)部信息

this.showBlockInnerInfo(block);

信息顯示到地磚上的實現(xiàn)方法(便于后續(xù)觸摸調(diào)用):

// 顯示地磚內(nèi)部信息

showBlockInnerInfo(block){

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

},

編譯運行結(jié)果如下:

仔細思考,發(fā)現(xiàn)剛才Game_mgr.js其實就是控制MineField這個節(jié)點的,故我們將其修改為MineField_Ctrl.js。將Canvas上的用戶自定義組件remove,在MineField節(jié)點上添加MineField_Ctrl組件,將其中block_root屬性去掉,將代碼中this.block_root替換為this.node。
給大家推薦個學(xué)習(xí)交流群 點擊鏈接即可加入群鏈接

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,666評論 1 32
  • VBA訂制工具欄 http://club.excelhome.net/thread-1047254-1-1.htm...
    大海一滴寫字的地方閱讀 2,355評論 0 0
  • Android的databinding已經(jīng)出來好久了,一直也沒有用到項目中,這兩天在郭霖的公眾號上看到分析data...
    wutongke閱讀 9,801評論 8 32
  • 七月末,暑氣正濃。天氣熱得像蒸籠一樣。熱得人喘息困難。但是由于到了雨季,前幾天剛下了一場中雨,使得熱辣辣的天氣得到...
    蒼鷹在上閱讀 232評論 0 1
  • 兒子,還有十來天你就回來了,日子越近,似乎越難熬,你大概也是歸心似箭吧?不然近期為何頻繁地匯報你的情況,不時還要視...
    麥子2008閱讀 1,982評論 29 13

友情鏈接更多精彩內(nèi)容