~~~接上篇,遊戲的主體框架完成了,接下來咱們對遊戲中存在的兩個主要實體進行分析,一個是雷點類BombObject(節點對象),一個是節點對象對應的圖片對象BombImgObject,根據第一篇的介紹,這兩個類的對象配合棋盤類實現遊戲過程,這裏從新解釋下這兩個類的用處:javascript
1、雷點類(BombObject):一個承上啓下的對象類別。java
1. 此類的對象集合(棋盤類中的ObjList數組)組成了遊戲棋盤上的每個節點,節點的類型有空節點(數值爲0)、雷點(數值爲-1)、非雷非空節點(數值爲N,依據節點周圍的雷點個數而定)。編程
2. 每個對象須要標記其在棋盤中的座標位置(x、y),同時可以獲取其周圍8個方位上的節點座標(沒有即爲null),這8個方位節點的屬性標記爲North、NorthEast、East、SouthEast、South、SouthWest、West、NorthWest。windows
3. 節點對象包含一個與其一一對應的圖片對象(ImgObj),展示給玩家的棋盤最終以圖片對象列表的形式,圖片對象根據當前節點數值屬性(DisplayNum)的值顯示不一樣的圖片。數組
類的定義以下:框架
1 //棋盤節點對象類 2 function BombObject(x, y, num, xNum, yNum) { 3 this.X = x; //節點的x座標 4 this.Y = y; //節點的y座標 5 this.ImgObj = new BombImgObject(num, x, y); //節點對應的圖片對象 6 this.IsBomb = (num === -1) ? true : false; //節點是不是雷點 7 this.DisplayNum = (this.IsBomb) ? -1 : parseInt(num); //節點對應的數值 8 //北方節點對象 9 this.North = (function() { 10 if ((y - 1) < 0) { 11 return null; 12 } 13 else { 14 return { X: x, Y: y - 1 }; 15 } 16 } ()); 17 //東北方向節點對象 18 this.NorthEast = (function() { 19 if ((y - 1) < 0 || (x + 1) >= xNum) { 20 return null; 21 } 22 else { 23 return { X: x + 1, Y: y - 1 }; 24 } 25 } ()); 26 //東方的節點對象 27 this.East = (function() { 28 if ((x + 1) >= xNum) { 29 return null; 30 } 31 else { 32 return { X: x + 1, Y: y }; 33 } 34 } ()); 35 //東南方的節點對象 36 this.EastSouth = (function() { 37 if ((y + 1) >= yNum || (x + 1) >= xNum) { 38 return null; 39 } 40 else { 41 return { X: x + 1, Y: y + 1 }; 42 } 43 } ()); 44 //南方節點對象 45 this.South = (function() { 46 if ((y + 1) >= yNum) { 47 return null; 48 } 49 else { 50 return { X: x, Y: y + 1 }; 51 } 52 } ()); 53 //西南方節點對象 54 this.SouthWest = (function() { 55 if ((y + 1) >= yNum || (x - 1) < 0) { 56 return null; 57 } 58 else { 59 return { X: x - 1, Y: y + 1 }; 60 } 61 } ()); 62 //西方節點對象 63 this.West = (function() { 64 if ((x - 1) < 0) { 65 return null; 66 } 67 else { 68 return { X: x - 1, Y: y }; 69 } 70 } ()); 71 //西北方節點對象 72 this.WestNorth = (function() { 73 if ((x - 1) < 0 || (y - 1) < 0) { 74 return null; 75 } 76 else { 77 return { X: x - 1, Y: y - 1 }; 78 } 79 } ()); 80 } 81 //判斷兩個節點對象是否相等 82 BombObject.prototype.Equals = function(that) { 83 if (!(that instanceof BombObject) || that.constructor !== BombObject || that == null) { 84 throw new Error("the add obj is not allowed."); 85 return false; 86 } 87 else if (this.X == that.X && this.Y == that.Y) { 88 return true; 89 } 90 else { 91 return false; 92 } 93 }; 94 //判斷當前節點對象的座標值是否一致 95 BombObject.prototype.EqualsEx = function(x, y) { 96 if (this.X == x && this.Y == y) { 97 return true; 98 } 99 else { 100 return false; 101 } 102 };
能夠看到,節點對象類包含了一個圖片對象,同時也定義了兩個對象比較函數,可由節點類對象直接調用。下面來介紹這個圖片對象類:函數
2、圖片對象類(BombImgObject):定義與節點對象一一對應的圖片對象。學習
1. 此類的對象是節點對象的表現層,節點的有些屬性和操做均經過此對象實現,每一個節點對象是根據節點數值屬性定義的。this
2. 圖片類對象是展示給用戶的表現層,遊戲的功能及效果都是經過此層實現的,故須要在圖片對象上定義鼠標事件(左鍵、右鍵、按下、彈起、左右鍵一塊兒按、移入、移出等)。spa
3. 同時在響應鼠標事件的同時,須要觸發有些功能監聽事件,好比空節點點擊事件、數值判斷左右鍵同時按下事件等。
類的定義包括一個Img元素(展示給玩家用)標籤對象,對應的棋盤座標x、y,是否被標記標誌flag(左鍵、右鍵的響應視爲已經標記),類的定義以下:
1 //圖片對象集合共用的鼠標類型值,0爲無鼠標事件,1爲左鍵,2爲右鍵,3爲左右鍵,4爲滾輪 2 BombImgObject.MouseType = 0; 3 //定義各節點的圖片對象類(傳遞圖片在棋盤中的數值:0~八、-1,圖片在棋盤中的x、y座標) 4 function BombImgObject(altValue, xPos, yPos) { 5 //保存this指針,防止函數嵌套時指代不明確 6 var img = this; 7 8 img.ImgObj = null; //圖片實體對象 9 img.x = xPos; //圖片在棋盤中的x座標 10 img.y = yPos; //圖片在棋盤中的y座標 11 if (document.createElement) { 12 img.ImgObj = document.createElement("img"); 13 } 14 if (img.ImgObj) { 15 img.ImgObj.lang = altValue; //保存當前圖片對象對應的節點數值,0~八、-1 16 img.ImgObj.src = "img/small/normal.bmp"; //賦值默認圖片路徑 17 img.ImgObj.flag = false; //標記圖片處理標示,若是圖片處理過(標記、顯示等)則再也不響應其餘事件 18 img.ImgObj.id = "img" + img.x + "-" + img.y; //根據圖片對應棋盤中的座標定義圖片對象的id屬性 19 20 //定義圖片對象的鼠標按下事件 21 img.ImgObj.onmousedown = function() { 22 //若是沒有觸發雷點 23 if (BombObjectList.fireFlag !== 2) { 24 //判斷是何種鼠標事件 25 switch (window.event.button) { 26 case 1: 27 //左鍵 28 if (this.src.indexOf("normal") >= 0) { 29 this.src = "img/small/kong.bmp"; 30 } 31 //標記當前的鼠標操做類型 32 BombImgObject.MouseType = 1; 33 //圖片鼠標按下事件,標記遊戲開始 34 BombObjectList.fireFlag = 1; 35 break; 36 case 2: 37 //右鍵 38 if (this.src.indexOf("normal") >= 0) { 39 //標記圖片已處理(被標記) 40 this.flag = true; 41 this.src = "img/small/flag.bmp"; 42 BombObjectList.MarkedNum++; 43 } 44 else if (this.src.indexOf("flag") >= 0) { 45 //取消圖片已處理標記(還原默認) 46 this.flag = false; 47 this.src = "img/small/normal.bmp"; 48 BombObjectList.MarkedNum--; 49 } 50 //標記當前的鼠標操做類型 51 BombImgObject.MouseType = 2; 52 break; 53 case 3: 54 //左右鍵一塊兒 55 BombImgObject.MouseType = 3; 56 //以該點爲中心,在其8個方向上進行節點判斷,若是已經都展開,則什麼也不作,若是有未展開的節點,而這些節點裏面有雷點,則不展開,反之將遞歸展開全部 57 BombObjectList.DC_X = img.x; 58 BombObjectList.DC_Y = img.y; 59 break; 60 case 4: 61 //滑輪 62 BombImgObject.MouseType = 4; 63 break; 64 } 65 } 66 } 67 //定義圖片對象的右鍵處理程序,空返回 68 img.ImgObj.oncontextmenu = function() { 69 if (BombObjectList.fireFlag !== 2) { 70 return false; 71 } 72 } 73 //定義圖片對象的鼠標提起事件處理程序 74 img.ImgObj.onmouseup = function() { 75 //若是沒有觸發雷點 76 if (BombObjectList.fireFlag !== 2) { 77 if (this.src.indexOf("flag") >= 0 || this.src.indexOf("normal") >= 0) { 78 //若是圖片被標記處理或者未處理過,則直接返回 79 return; 80 } 81 else { 82 //若是不是右鍵事件,則進入處理 83 if (BombImgObject.MouseType !== 2) { 84 if (BombImgObject.MouseType !== 1) { 85 //雙擊時,不進行接下來的操做 86 BombImgObject.MouseType = 0; 87 return; 88 } 89 //標記圖片對象已被處理過 90 this.flag = true; 91 //根據圖片數值顯示對應的圖片信息 92 var caseValue = parseInt(this.lang); 93 switch (caseValue) { 94 case -1: //雷點 95 { 96 this.src = "img/small/fire.bmp"; 97 //觸雷,更行觸發標記,以便定時器捕獲 98 BombObjectList.fireFlag = 2; 99 break; 100 } 101 case 1: //該節點旁邊有一個雷點,標記數值1 102 { 103 this.src = "img/small/1.bmp"; 104 break; 105 } 106 case 2: //該節點旁邊有兩個雷點,標記數值2 107 { 108 this.src = "img/small/2.bmp"; 109 break; 110 } 111 case 3: //該節點旁邊有三個雷點,標記數值3 112 { 113 this.src = "img/small/3.bmp"; 114 break; 115 } 116 case 4: //該節點旁邊有四個雷點,標記數值4 117 { 118 this.src = "img/small/4.bmp"; 119 break; 120 } 121 case 5: //該節點旁邊有五個雷點,標記數值5 122 { 123 this.src = "img/small/5.bmp"; 124 break; 125 } 126 case 6: //該節點旁邊有六個雷點,標記數值6 127 { 128 this.src = "img/small/6.bmp"; 129 break; 130 } 131 case 7: //該節點旁邊有七個雷點,標記數值7 132 { 133 this.src = "img/small/7.bmp"; 134 break; 135 } 136 case 8: //該節點旁邊有八個雷點,標記數值8 137 { 138 this.src = "img/small/8.bmp"; 139 break; 140 } 141 case 0: 142 { 143 //空節點,須要遍歷相鄰的全部空節點,目前採用遞歸方法 144 this.src = "img/small/kong.bmp"; 145 //定義當前空節點的座標未知,以便定時器捕獲對該位置相連的全部節點進行遞歸處理,顯示遞歸中發現的全部空節點 146 BombObjectList.fire_X = img.x; 147 BombObjectList.fire_Y = img.y; 148 break; 149 } 150 } 151 } 152 } 153 } 154 //鼠標提起時,清空鼠標操做狀態。 155 BombImgObject.MouseType = 0; 156 } 157 //禁止圖片對象的鼠標拖動事件 158 img.ImgObj.ondrag = function() { 159 return false; 160 } 161 //定義圖片對象的鼠標移入事件 162 img.ImgObj.onmouseout = function() { 163 if (BombObjectList.fireFlag!==2 && !this.flag && BombObjectList.IsMouseDown && this.src.indexOf("kong") >= 0 && BombImgObject.MouseType != 2) { 164 this.src = "img/small/normal.bmp"; 165 } 166 } 167 img.ImgObj.onmouseover = function() { 168 if (BombObjectList.fireFlag !== 2 && !this.flag && BombObjectList.IsMouseDown && this.src.indexOf("normal") >= 0 && BombImgObject.MouseType != 2) { 169 this.src = "img/small/kong.bmp"; 170 } 171 } 172 } 173 //根據x、y座標顯示該圖片對象的圖像 174 img.ShowNumImg = function(tag) { 175 if (!img.ImgObj.flag) { 176 if (arguments.length === 0) { 177 if (parseInt(img.ImgObj.lang) == 0) { 178 //爲空時 179 document.getElementById("img" + img.x + "-" + img.y).src = "img/small/kong.bmp"; 180 } 181 else { 182 //數值時 183 document.getElementById("img" + img.x + "-" + img.y).src = "img/small/" + img.ImgObj.lang + ".bmp"; 184 } 185 //標記該圖像已經處理過,再也不響應後續的全部操做。 186 img.ImgObj.flag = true; 187 } 188 else { 189 //雙擊時 190 if (tag === 1) { 191 //按下 192 document.getElementById("img" + img.x + "-" + img.y).src = "img/small/kong.bmp"; 193 } 194 else { 195 //彈起 196 document.getElementById("img" + img.x + "-" + img.y).src = "img/small/normal.bmp"; 197 } 198 } 199 } 200 } 201 202 //返回節點的圖像對象 203 return img; 204 } 205 //設置圖片對象的數值 206 BombImgObject.prototype.SetImgNum = function(num) { 207 if (this !== null) { 208 this.ImgObj.lang = num; 209 } 210 }
因爲遊戲須要,從空間複雜度上考慮,須要定義兩個成員函數,一個是圖片節點的動態展開函數,標記爲ShowNumImg;另外一個是動態改變圖片對象對應的數值的函數,標記爲SetImgNum。
~~代碼中灰色底紋部分是爲了響應棋盤類監聽事件所寫的,在javascript中,類與類之間的相互做用沒有VC++中的消息機制,這裏在一個類中採用定時器監聽(讀)變量、另外一個類中根據觸發點設置變量的方法(寫)的方法,技術有限,歡迎你們指導學習!!
3、遊戲控制類
至此,遊戲功能部分所有完成,不過就展示給玩家的界面來講,還比較單調,僅僅一個期盼而已,爲了完善遊戲的輔助功能,增添遊戲的趣味性,還須要完成如下一系列工做:
1. 須要一個計時器,每秒鐘刷新一次,統計當前遊戲用時,而後反饋給玩家。
2. 須要一個計數器,顯示當前遊戲剩餘雷點水,一旦用戶標記出一個雷點,計數器減1,玩家誤標記而計數器爲零且遊戲未結束時,須要出現負數提示。
3. 遊戲能夠從新開始,模仿windows下的掃雷,設置一個從新開始按鈕,遊戲從新開始須要清除當前過程的全部臨時變量,以便爲接下來新的遊戲過程預留空間。
4. 剩餘雷點數很少時,須要自動識別剩下的節點是否全是雷點,若是是,則遊戲自動成功結束,以提升遊戲趣味性。
鑑於上述分析,咱們須要定義一個遊戲控制類,標記爲PlayBombGame,對遊戲的整個流程和狀態進行控制和監聽,對客戶機環境進行兼容性判斷,爲整個遊戲檢測提供一個正常的運行環境,類的定義以下:
1 //玩家操做接口類的定義 2 function PlayBombGame(TimerId, BombNumId, ContentId, TryAgainId, width, height, BombNum) { 3 //預留退路 4 if (ContentId === "" || BombNumId === "" || TryAgainId === "" || TimerId === "") return false; 5 if (!document.getElementById) return false; 6 if (!document.createDocumentFragment) return false; 7 if (!document.createElement) return false; 8 if (!document.getElementById(ContentId)) return false; 9 if (!document.getElementById(BombNumId)) return false; 10 if (!document.getElementById(TryAgainId)) return false; 11 if (!document.getElementById(TimerId)) return false; 12 13 //保存當前對象,以防函數嵌套時指代不清 14 var PBG = this; 15 16 PBG.GameInfo = new BombObjectList("ContentSection", width, height, BombNum); //遊戲操做對象 17 PBG.TimerID = TimerId; //計時器元素id 18 PBG.BombNumId = BombNumId; //雷點計數器元素id 19 PBG.TryAgainId = TryAgainId; //重置按鈕id 20 PBG.CurSecond = 0; //當前用戶用時(s) 21 PBG.CurBombNum = BombNum; //當前剩餘雷點個數 22 PBG.GameState = -1; //當前的遊戲狀態,-1爲結束(未開始),1爲進行中 23 24 var timer = null; 25 var ListenTimer = null; 26 //開始初始化遊戲 27 PBG.play = function() { 28 if (PBG.GameInfo != null || PBG.GameInfo != undefined) { 29 PBG.GameInfo.Initial().Display(); 30 } 31 } 32 //從新初始化遊戲 33 PBG.playAgain = function() { 34 if (PBG.GameInfo != null || PBG.GameInfo != undefined) { 35 PBG.GameInfo.TryAgain(); 36 BombObjectList.fireFlag = 0; 37 BombObjectList.MarkedNum = 0; 38 //關閉計時器 39 PBG.CurSecond = 0; 40 PBG.CurBombNum = BombNum; 41 PBG.GameState = -1; 42 //從新開始監測 43 clearInterval(ListenTimer); 44 clearInterval(timer); 45 timer = null; 46 ListenTimer = null; 47 PBG.TimerControl(); 48 } 49 } 50 //遊戲結束時的處理 51 PBG.GameOver = function(tag) { 52 //標記遊戲狀態結束 53 PBG.GameState = -1; 54 //結束時處理 55 if (arguments.length !== 0) { 56 //成功 57 PBG.GameInfo.GameOver(tag); 58 document.getElementById(PBG.TryAgainId).src = "img/face/over.bmp"; 59 } 60 else { 61 //失敗 62 PBG.GameInfo.GameOver(); 63 document.getElementById(PBG.TryAgainId).src = "img/face/fail.bmp"; 64 } 65 //關閉定時器 66 clearInterval(ListenTimer); 67 clearInterval(timer); 68 timer = null; 69 ListenTimer = null; 70 } 71 //監測遊戲狀態 72 PBG.TimerControl = function() { 73 if (ListenTimer === null || ListenTimer === undefined) { 74 ListenTimer = setInterval(function() { 75 if (BombObjectList.fireFlag === 2) { 76 PBG.GameOver(); 77 } 78 else if (BombObjectList.fireFlag === 1) { 79 //開啓計時器 80 if (timer == null) { 81 //標記遊戲開始 82 PBG.GameState = 1; 83 timer = setInterval(function() { 84 PBG.CurSecond++; 85 document.getElementById(PBG.TimerID).innerHTML = GetCount(PBG.CurSecond); 86 if (PBG.CurSecond === 999) { 87 //最長時間999秒,超出即結束 88 PBG.GameOver(); 89 } 90 }, 1000); 91 //開啓對空節點的監聽 92 PBG.GameInfo.ListenKong(); 93 } 94 } 95 else { 96 //未開始狀態下 97 if (PBG.GameState === -1) { 98 //若是在進行中點擊了從新開始 99 document.getElementById(PBG.TimerID).innerHTML = GetCount(PBG.CurSecond); 100 clearInterval(timer); 101 timer = null; 102 } 103 } 104 //監聽剩餘雷點 105 if (BombObjectList.fireFlag !== 2) { 106 //監聽玩家標記出的個數來展示當前剩餘的雷的個數 107 PBG.CurBombNum = BombNum - BombObjectList.MarkedNum; 108 document.getElementById(PBG.BombNumId).innerHTML = GetCount(PBG.CurBombNum); 109 if ($("#" + ContentId + " > IMG").length > 0) { 110 //若是剩餘雷點數爲0,且棋盤上剩餘未標記節點個數爲0,則遊戲結束,所有標記正確,遊戲成功 111 if (PBG.CurBombNum === 0 && $("#" + ContentId + " > img[flag='false']").length === 0) { 112 PBG.GameOver(1); 113 BombObjectList.fireFlag = 2; 114 } 115 //剩餘未標記的都是雷點,則遊戲結束,程序自動標記全部的雷點,遊戲成功 116 if (PBG.CurBombNum > 0 && $("#" + ContentId + " > img[flag='false']").not("lang='-1'").length === 0) { 117 PBG.GameOver(2); 118 BombObjectList.fireFlag = 2; 119 } 120 } 121 } 122 }, 50); 123 } 124 } 125 //啓動檢測 126 PBG.TimerControl(); 127 //根據數值獲取圖片數值展示對象 128 function GetCount(num) { 129 var numArr = num.toString().split(""); 130 for (var i = 0; i < numArr.length; i++) { 131 numArr[i] = (numArr[i] == "-") ? "line" : numArr[i]; 132 } 133 if (numArr.length === 1) { 134 return "<img src=\"img/num/0.bmp\" alt=\"bomb\" /><img src=\"img/num/0.bmp\" alt=\"bomb\" /><img src=\"img/num/" + numArr[0] + ".bmp\" alt=\"bomb\" />"; 135 } 136 else if (numArr.length === 2) { 137 return "<img src=\"img/num/0.bmp\" alt=\"bomb\" /><img src=\"img/num/" + numArr[0] + ".bmp\" alt=\"bomb\" /><img src=\"img/num/" + numArr[1] + ".bmp\" alt=\"bomb\" />"; 138 } 139 else { 140 return "<img src=\"img/num/" + numArr[0] + ".bmp\" alt=\"bomb\" /><img src=\"img/num/" + numArr[1] + ".bmp\" alt=\"bomb\" /><img src=\"img/num/" + numArr[2] + ".bmp\" alt=\"bomb\" />"; 141 } 142 } 143 //對文檔的全局事件進行定義 144 AddEvent(document, "mousedown", function(e) { 145 BombObjectList.IsMouseDown = true; 146 //冒泡捕獲圖片對象的按下事件 147 var event = window.event || e; 148 var targetE = (event.srcElement) ? event.srcElement : event.target; 149 if (BombObjectList.fireFlag !== 2 && targetE.getAttribute("flag") !== null && BombImgObject.MouseType===1) { 150 document.getElementById(PBG.TryAgainId).src = "img/face/down.bmp"; 151 } 152 }); 153 AddEvent(document, "mouseup", function(e) { 154 BombObjectList.IsMouseDown = false; 155 //冒泡捕獲圖片對象的按下事件 156 var event = window.event || e; 157 var targetE = (event.srcElement) ? event.srcElement : event.target; 158 if (BombObjectList.fireFlag !== 2 && targetE.getAttribute("flag") != null) { 159 document.getElementById(PBG.TryAgainId).src = "img/face/up.bmp"; 160 } 161 }); 162 AddEvent(document, "contextmenu", function() { return false; }); 163 AddEvent(document.getElementById(PBG.TryAgainId), "click", function() { PBG.playAgain(); }); 164 AddEvent(document.getElementById(PBG.TryAgainId), "mousedown", function() { this.src = "img/face/0.bmp"; }); 165 AddEvent(document.getElementById(PBG.TryAgainId), "mouseup", function() { this.src = "img/face/up.bmp"; }); 166 167 function AddEvent(target, eventType, callback) { 168 if (target.addEventListener) { 169 target.addEventListener(eventType, callback, false); 170 } 171 else { 172 target.attachEvent("on" + eventType, function(event) { return callback.call(target, event); }); 173 } 174 }; 175 }
完成上面類的定義,整個遊戲就完成了,展示給玩家的是篇章一所示的效果圖,嗯,界面比較粗糙哈~~
題外:
常常來博客園看技術大牛的分享,不知不覺就3年過去了,就在月初,忽然發現本身博客園的年齡都3年臨三個月了,說來慚愧,本身的博客園卻空空如野,什麼都沒有,做爲一個程序猿,熱愛編程的同時忽然很強烈想着在這個職業上留下點什麼,說作就作,因而就有了上述四個篇章,遊戲雖小,但也要五臟俱全,玩和作真的不同。
想是一回事,作是一回事,寫出來又是一回事,我的以爲這三步的難度是遞進關係的,哈哈,我的鄙見。此章節的完成,忽然發現興趣是最好的推進力,工做之餘,作本身想作的事,也對以前一段時間以來所學習的內容進行一個小結,美事一樁,第一次寫本身的博客,這只是個開端,接下來我將本身學習所得、所想、所作一一與你們分享,由於分享,因此快樂~~
~~Little Dream,在與你們互相交流中成長~~