~~~接上篇,上篇介紹了遊戲實現過程當中第一個比較繁瑣的地方,如今展示在玩家面前的是一個有血有肉的棋盤,從某種意義上說玩家已經能夠開始遊戲了,可是不夠人性化,玩家只能一個一個節點的點開,而後判斷,而後標記等等,程序暫不能人性化的輔助玩家將遊戲進行下去,趣味性不夠強,接下來就來完善這些輔助功能。函數
2、空節點點擊觸發其周圍全部空節點展開spa
此過程主要是輔助玩家快速標記雷點,加強遊戲趣味性,即當遊戲進行中,玩家點擊棋盤中某個未展開的節點M時,若是該節點是空節點(對應的數值爲0),則展開該節點的同時,須要遍歷其周圍8個方向上的節點,若是某個方向上的節點也是空節點,則再以該點爲中心重複上述過程,直到遍歷完全部相鄰節點M的空節點爲止。這個過程值得注意的是:code
1. 遊戲開始後(玩家第一次點擊),若是第一個節點是非雷點,則程序開始監聽空節點點擊事件。 對象
2. 節點M是空節點,即其周圍沒有雷點存在,用戶點擊後該點周圍8個方向上的節點均須要展開。blog
3. 遍歷節點M周圍節點時,若是遇到新的空節點,則當即以該節點爲中心重複遍歷過程,此處咱們採用遞歸處理,默認從北方節點順時針方向依次遍歷。遞歸
4. 遞歸遍歷時,節點M與其周圍節點互爲方位節點,爲了不陷入重複循環,提升效率,則過濾掉已經遍歷過的節點,這裏定義一個棧,壓入已經遍歷過的節點,每次進入遞歸時判斷節點是否已經在棧中,若是在,則忽略。遊戲
5. 遍歷時,若是節點非空(必定不是雷點),則只需展開便可,接着判斷並遍歷下一個方位節點。事件
6. 一次遞歸完成後,程序會自動清除或重置當前次遞歸遍歷產生的臨時數據和變量,開始監聽下一次空節點的點擊事件。圖片
功能效果及遍歷路徑見下例:io
如上圖,節點M點擊後,程序會按照黃色箭頭(只畫出部分)的順序依次遍歷並展開節點,直到由M節點觸發的全部相鄰的空節點遍歷完畢,則遞歸完成。
這個過程須要在棋盤控制類(BombObjectList)中實現:
首先,須要定義兩個對外的類的屬性,標記爲BombObjectList.fire_X和BombObjectList.fire_X,這兩個屬性默認初始化值爲-1,一旦玩家觸發了空節點,則分別賦值爲空節點在棋盤中的x、y座標。
其次,在類中定義一個定時器,隨時監聽上面兩個屬性值的變化,一旦值均不爲-1,表示玩家觸發了空節點的點擊事件,程序須要響應這個事件,咱們定義一個響應函數,標記爲CheckAroundBomb。
最後,遍歷該空節點,必要時進行遞歸,遞歸過程見上述描述,遞歸函數標記爲checkBomb。
類的對外屬性定義以下:
BombObjectList.fire_X = -1; //觸發空節點彈開時節點的x座標 BombObjectList.fire_Y = -1; //觸發空節點彈開時節點的y座標
定時器定義以下(黑色陰影部分爲空節點點擊事件監聽過程),50毫秒監聽一次:
1 //開啓對空節點的監聽 2 var ListenFire = null; 3 me.ListenKong = function() { 4 if (ListenFire === null || ListenFire === undefined) { 5 ListenFire = setInterval(function() { 6 if (BombObjectList.fireFlag === 2) { 7 //遊戲結束,關閉定時器 8 clearInterval(ListenFire); 9 ListenFire = null; 10 } 11 else if (BombObjectList.fireFlag === 1) { 12 //遊戲進行中,檢測空節點的點擊事件 13 if (BombObjectList.fire_X !== -1 && BombObjectList.fire_Y !== -1) { 14 //根據x、y座標找到該節點在節點集合中的對象 15 var tempObjEx = me.CheckObjItem(BombObjectList.fire_X, BombObjectList.fire_Y).obj; 16 if (tempObjEx !== null) { 17 me.CheckAroundBomb(tempObjEx); 18 } 19 } 20 //遊戲進行中,監測雙擊事件 21 if (BombObjectList.DC_X !== -1 && BombObjectList.DC_Y !== -1) { 22 //根據x、y座標找到該節點在節點集合中的對象 23 var tempObjEx = me.CheckObjItem(BombObjectList.DC_X, BombObjectList.DC_Y).obj; 24 if (tempObjEx !== null) { 25 if (BombImgObject.MouseType === 3) { 26 //雙擊按下事件 27 me.CheckAroundBombDC_Down(tempObjEx); 28 } 29 else { 30 //雙擊彈起事件 31 me.CheckAroundBombDC_Up(tempObjEx); 32 } 33 } 34 } 35 } 36 }, 50);
事件響應函數CheckAroundBomb和遞歸函數定義以下:
1 //遞歸函數 2 function checkBomb(obj) { 3 //對當前空區進行八方位踩點,遞歸查詢相連的全部空區,並所有打開 4 for (var i = 0; i < me.enmbVal.length; i++) { 5 var _Obj = eval("obj." + me.enmbVal[i]); 6 //判斷該方位是否存在,存在則展開節點 7 if (_Obj != null) { 8 var _X = _Obj.X; 9 var _Y = _Obj.Y; 10 var tempObjEx = me.CheckObjItem(_X, _Y).obj; 11 if (tempObjEx.DisplayNum === 0) { 12 //若是爲空,遞歸查詢空節點,並展開其8個方位的全部節點 13 tempObjEx.ImgObj.ShowNumImg(); 14 //將當前節點入棧,以避免後續再次將其做爲空節點進入遞歸流程 15 var isIn = (function() { 16 for (var s = 0; s < stackObj.length; s++) { 17 if (stackObj[s].Equals(tempObjEx)) { 18 return true; 19 } 20 } 21 return false; 22 } ()); 23 if (!isIn) { 24 stackObj.push(tempObjEx); 25 //進入遞歸流程 26 checkBomb(tempObjEx); 27 } 28 } 29 else { 30 tempObjEx.ImgObj.ShowNumImg(); 31 } 32 } 33 } 34 } 35 //存儲遍歷過的節點 36 var stackObj = []; 37 me.CheckAroundBomb = function(that) { 38 if (!(that instanceof BombObject) || that.constructor !== BombObject || that == null) { 39 throw new Error("the obj is not allowed."); 40 return; 41 } 42 else { 43 stackObj.push(that); 44 //進入遞歸調度,查找並展開8個方向上的節點 45 checkBomb(that); 46 //重置觸自動發展示時監聽的節點座標,準備監聽下一個 47 BombObjectList.fire_X = -1; 48 BombObjectList.fire_Y = -1; 49 //清空遍歷過的堆棧對象列表 50 stackObj.length = 0; 51 } 52 };
3、鼠標左鍵和右鍵一塊兒按下時的事件響應
這個過程是這三個裏面最繁瑣的一個,不過它的實現能夠大大加強遊戲的趣味性,在掃雷遊戲過程當中,這個功能是用的最頻繁的,對腳本程序的檢測和判斷及效率要求較高,整體來講,主要基於節點數值和其周圍雷點個數一直的原則進行判斷,分析得出存在如下幾種狀況:
1. 玩家雙擊的對象是未展開的節點,此時系統並不須要過多的判斷,由於玩家尚未標記出該點周圍的雷點,程序只需閃爍提示該點周圍的節點便可,鼠標事件後復原到按下前的狀態。
2. 玩家雙擊的對象是已展開的節點,此時有兩種狀況,好比改節點的數值爲N,而玩家在其周圍標記的雷點個數爲M:
若是N!=M,則提示用戶進行修改,閃爍提示同上一條;
若是M=N,接下來要判斷該點周圍的未標記節點的類型,記未標記雷點的個數爲C,若C>0,則用戶標記錯誤,鼠標按下後遊戲結束,提示用戶標記錯誤;若C=0,則用戶標記正確,展開全部周圍未標記節點,當這些節點中有空節點時,則須要遞歸展示全部相鄰的空節點,過程同上述處理過程。
上述幾種狀況的示例見下圖:
對於這一過程的實現,咱們分兩步進行:
第一步:響應鼠標左右鍵一塊兒按下事件,函數標記爲CheckAroundBombDC_Down,此過程記錄點擊節點周圍的遊戲上下文,如該點周圍已經標記的雷點個數markRoundNum,未標記節點中是否有雷點標記位hasBomb。
第二步:響應鼠標左右鍵一塊兒提起事件,函數標記爲CheckAroundBombDC_Up,若是該點未展開,則復原圖片對象,什麼也不作;若是該點已展開,若其周圍有雷點,則復原圖片,什麼也不作,不然展開其周圍的非雷點,必要時進行遞歸展示。
此外,棋盤控制類中須要定義個定時器,隨時監聽左右鍵一塊兒按下事件,並將事件觸發對象節點的x、y座標記錄到棋盤類的兩個對外屬性BombObjectList.DC_X和BombObjectList.DC_Y,就像監聽空節點點擊事件同樣。
屬性定義以下:
1 BombObjectList.DC_X = -1; //左右鍵一塊兒按下時節點的x座標 2 BombObjectList.DC_Y = -1; //左右鍵一塊兒按下時節點的y座標
定時器定義以下(黑色陰影部分爲空節點點擊事件監聽過程),50毫秒監聽一次:
1 //開啓對空節點的監聽 2 var ListenFire = null; 3 me.ListenKong = function() { 4 if (ListenFire === null || ListenFire === undefined) { 5 ListenFire = setInterval(function() { 6 if (BombObjectList.fireFlag === 2) { 7 //遊戲結束,關閉定時器 8 clearInterval(ListenFire); 9 ListenFire = null; 10 } 11 else if (BombObjectList.fireFlag === 1) { 12 //遊戲進行中,檢測空節點的點擊事件 13 if (BombObjectList.fire_X !== -1 && BombObjectList.fire_Y !== -1) { 14 //根據x、y座標找到該節點在節點集合中的對象 15 var tempObjEx = me.CheckObjItem(BombObjectList.fire_X, BombObjectList.fire_Y).obj; 16 if (tempObjEx !== null) { 17 me.CheckAroundBomb(tempObjEx); 18 } 19 } 20 //遊戲進行中,監測雙擊事件 21 if (BombObjectList.DC_X !== -1 && BombObjectList.DC_Y !== -1) { 22 //根據x、y座標找到該節點在節點集合中的對象 23 var tempObjEx = me.CheckObjItem(BombObjectList.DC_X, BombObjectList.DC_Y).obj; 24 if (tempObjEx !== null) { 25 if (BombImgObject.MouseType === 3) { 26 //雙擊按下事件 27 me.CheckAroundBombDC_Down(tempObjEx); 28 } 29 else { 30 //雙擊彈起事件 31 me.CheckAroundBombDC_Up(tempObjEx); 32 } 33 } 34 } 35 } 36 }, 50);
鼠標左右鍵一塊兒按下和彈起事件定義以下:
1 var tempAroundObj = []; //存儲雙擊時遍歷的8方位節點對象 2 var hasBomb = false; //標記上一變量存儲的對象是否包含雷點 3 //雙擊按下時事件處理程序 4 me.CheckAroundBombDC_Down = function(obj) { 5 if (!(obj instanceof BombObject) || obj.constructor !== BombObject || obj == null) { 6 throw new Error("the obj is not allowed."); 7 return; 8 } 9 else { 10 var markRoundNum = 0; 11 for (var i = 0; i < me.enmbVal.length; i++) { 12 var _Obj = eval("obj." + me.enmbVal[i]); 13 //判斷該方位是否存在,存在則展開節點 14 if (_Obj != null) { 15 var _X = _Obj.X; 16 var _Y = _Obj.Y; 17 var tempObjEx = me.CheckObjItem(_X, _Y).obj; 18 var tempImgPic = tempObjEx.ImgObj; 19 if (!tempImgPic.ImgObj.flag) { 20 //該節點還沒有展開,此時需更改該圖片 21 tempImgPic.ShowNumImg(1); 22 tempAroundObj.push(tempObjEx); 23 //若是是雷點,且未標記 24 if (tempObjEx.IsBomb && tempImgPic.ImgObj.src.indexOf("flag") < 0) hasBomb = true; 25 //統計核心點旁邊被標記的雷點個數 26 if (tempObjEx.IsBomb && tempImgPic.ImgObj.src.indexOf("flag") >= 0) markRoundNum++; 27 } 28 } 29 } 30 //判斷本身 31 if (!obj.ImgObj.ImgObj.flag) { 32 //該節點還沒有展開,此時需更改該圖片 33 obj.ImgObj.ShowNumImg(1); 34 tempAroundObj.push(obj); 35 } 36 else { 37 //若是核心點是展開的,則須要比較核心點的數值和其周圍已經標記處的雷點個數 38 if ((markRoundNum === parseInt(obj.DisplayNum)) && hasBomb) { 39 //標記錯誤,遊戲結束 40 hasBomb = false; 41 } 42 else { 43 //容許用戶修改,什麼也不作 44 } 45 } 46 } 47 } 48 //雙擊彈起時事件處理程序,檢測當前點周圍的節點,必要時進行遞歸 49 me.CheckAroundBombDC_Up = function(obj) { 50 if (!(obj instanceof BombObject) || obj.constructor !== BombObject || obj == null) { 51 throw new Error("the obj is not allowed."); 52 return; 53 } 54 else { 55 //若是核心節點是未展開的或者遍歷列表中包含雷點,則復原遍歷列表中對象對應的圖片對象 56 if (!obj.ImgObj.ImgObj.flag || hasBomb) { 57 for (var i = 0; i < tempAroundObj.length; i++) { 58 //復原節點圖片 59 tempAroundObj[i].ImgObj.ShowNumImg(2); 60 } 61 } 62 else { 63 //若是核心點是展開的,沒有雷點,則自動展開,空節點須要遞歸 64 for (var j = 0; j < tempAroundObj.length; j++) { 65 //展開節點 66 tempAroundObj[j].ImgObj.ShowNumImg(); 67 //若是節點爲空,則須要遞歸遍歷展開 68 if (tempAroundObj[j].DisplayNum === 0) { 69 checkBomb(tempAroundObj[j]); 70 } 71 } 72 } 73 //復原監聽變量 74 BombObjectList.DC_X = -1; 75 BombObjectList.DC_Y = -1; 76 //刪除遍歷的臨時節點對象列表 77 tempAroundObj.length = 0; 78 hasBomb = false; 79 } 80 }
至此,遊戲的三個難點基本解決,看到這裏,還有兩個東西沒有介紹,一個是雷點類(即節點信息類)BombObject,另外一個是節點對應的圖片對象類BombImgObject,下篇將揭開其神祕的面紗。
接下篇~~~~