HTML5遊戲開發-掃雷及其演算法研究
呂蒙曰:士隔三月【1】,當刮目相看。所以,在下在這三月中發奮圖強,花了約莫8節資訊課的時間研究掃雷。嗚呼,由於在下才能尚且不足,所以也就只能勉強打過中級難度的吧。不過,一邊玩的同時,我還一邊對掃雷這個遊戲的製做方法構思了一下。所以說,本文中的演算法完全是憑藉自己對掃雷遊戲規則的總結而自行研發出來的,倘若和MS的掃雷玩法有些出入,還望各位看官見諒。
【1】出自《孫權勸學》,原文為“士別三日”,由於在下這三個月來都不曾發表部落格,所以引申到“士隔三月”,各位看官休怪休怪
以下是本次開發的遊戲截圖:
演算法研究
掃雷雖然屬於遊戲開發初級學者研究的範疇,但是由於我是那種好高騖遠的傢伙,所以,雖然接觸遊戲開發約2年了,可是到現在才來研究掃雷,實在是慚愧慚愧。
首先,我想我們需要對掃雷遊戲的演算法進行一系列的研究。
佈雷演算法
這個演算法嘛,顧名思義,就是說如何在場景中佈雷。這個演算法其實很簡單,就是單一地使用隨機取位來完成。不過值得注意的是,可能有些朋友和我一樣,一開始認為是先把數字標好再去佈雷,其實應該是先佈雷,再根據佈雷情況給每個格子標數字。
智慧幫掃演算法
其實這個所謂的“智慧幫掃演算法”是我隨便給這個演算法取的一個名字,可能俗氣了點,看官莫怪~什麼是“智慧幫掃演算法”呢?首先我來對其下個定義:
當玩家掃開到某個方塊後,如果四周沒有雷,或者這個方塊四周的雷全部被掃除了,那麼這時候就需要幫助玩家把四周的方塊全部掃出。這樣的演算法叫做智慧幫掃演算法
如果實現了這個演算法,那麼點選一個方塊,掃開一大片的效果就可以實現了。某些看官可能不理解為什麼,我可以在這裡羅嗦一下:根據定義我們可以看出,這個演算法只是幫助掃出四周的方塊,不過這是一個鏈環的過程,畢竟四周的方塊被掃開後,如果這些方塊滿足執行幫掃演算法的條件,那麼幫掃演算法就會在被演算法掃開的方塊上生效,如此漸進下去,知道被掃開的方塊不滿足條件為止。如果用流程圖表示就是:
如果流程圖還不能理解的話,就舉個Y某我吃巧克力的例子吧。Y某有一天收到M君送來的一盒巧克力,我首先拆開盒子掰下一塊巧克力,放進嘴裡,發現是苦瓜味的,於是我就下定決心,如果我再吃到苦瓜味的,就把周圍的8塊都吃了。不想我接下來吃到一塊就是苦瓜味的,沒有辦法,只有把周圍八塊都吃了,結果剛吃到其中一塊,發現又是苦瓜味的,於是繼續吃……於是就達到了吃開一片的效果。這裡的吃到苦瓜味的巧克力就相當於“四周被標記的雷數等於相應數目”。這個故事的結局和掃雷不同,由於遊戲中的雷數>=1,所以無論如何都會因遇到雷停下來,可是萬惡的M君送來的巧克力盡然全是苦瓜味的(T_T)
要實現這個演算法,說難也不算難。只需把對演算法的定義翻譯成程式碼即可。
實現過程
既然是遊戲,所以要實現的不僅是演算法,還有就是使用者介面之類的。
由於純canvas做遊戲很麻煩,所以我這次就直接使用lufylegend遊戲引擎實現介面。
引擎地址:http://lufylegend.com/lufylegend
API地址:http://lufylegend.com/lufylegend/api
本文中可能多次出現某些類和函式,或者某些函式和類很重要,所以我把它們的API地址放在下面,這樣以來可以方便大家查詢:
在下面的講解中,我只講一些關鍵的地方,其他地方就交給看官慢慢啃吧。文末會給出完整的程式碼下載。
HTML中的程式碼
HTML5遊戲嘛,肯定要有html程式碼:
<!DOCTYPE html>
<html>
<head>
<title>Minesweeper</title>
<meta charset="utf-8" />
<script type="text/javascript" src="./lib/lufylegend-1.9.9.simple.min.js"></script>
<script type="text/javascript" src="./lib/lufylegend.LoadingSample1-0.1.0.min.js"></script>
<script type="text/javascript" src="./js/Main.js"></script>
</head>
<body oncontextmenu="return false;">
<div id="mylegend"></div>
</body>
</html>
Main.js
LInit(1000 / 30, "mylegend", 540, 640, main);
var dataList = {};
var stage;
var blockXNum = blockYNum = 10, mineNum = 12;
function main () {
var loadData = [
{path : "./js/InfoLayer.js"},
{path : "./js/ButtonTemplate.js"},
{path : "./js/MineLayer.js"},
{path : "./js/StageLayer.js"},
{name : "bg", path : "./images/bg.jpg"},
{name : "button_sheet", path : "./images/button_sheet.png"},
{name : "face_happy", path : "./images/face_happy.png"},
{name : "face_sad", path : "./images/face_sad.png"},
{name : "face_smile", path : "./images/face_smile.png"},
{name : "face_surprise", path : "./images/face_surprise.png"},
{name : "flag", path : "./images/flag.png"},
{name : "mine", path : "./images/mine.png"}
];
var loadingLayer = new LoadingSample1();
addChild(loadingLayer);
LLoadManage.load(
loadData,
function (p) {
loadingLayer.setProgress(p);
},
function (r) {
dataList = r;
loadingLayer.remove();
initGame();
}
);
}
function initGame () {
stage = new StageLayer();
addChild(stage);
}
在Main.js中,我們初始化了介面,載入了資源,以及加入舞臺類(StageLayer),主要用的是lufylegend中的一些API,不熟悉的同學可以參考前面給出的API文件。不過得注意幾個變數:
- blockXNum:代表遊戲中的方塊每行有幾個
- blockYNum:代表遊戲中的方塊每列有幾個
- mineNum:雷數
StageLayer.js
這個類也比較簡單,先把程式碼貼出來:
function StageLayer () {
var s = this;
LExtends(s, LSprite, []);
var bgBmp = new LBitmap(new LBitmapData(dataList["bg"]));
bgBmp.scaleX = LGlobal.width / bgBmp.getWidth();
bgBmp.scaleY = LGlobal.height / bgBmp.getHeight();
s.addChild(bgBmp);
s.infoLayer = new InfoLayer();
s.infoLayer.x = (LGlobal.width - s.infoLayer.getWidth()) / 2;
s.infoLayer.y = 40;
s.addChild(s.infoLayer);
s.mineLayer = null;
s.createMineLayer();
}
StageLayer.prototype.createMineLayer = function () {
var s = this;
if (s.mineLayer) {
s.mineLayer.remove();
}
s.mineLayer = new MineLayer();
s.mineLayer.x = (LGlobal.width - s.mineLayer.getWidth()) / 2;
s.mineLayer.y = s.infoLayer.y + s.infoLayer.getHeight() + 30;
s.addChild(s.mineLayer);
};
這個類是舞臺類,既然是舞臺,那裝些顯示物件就是他的義務囉~看了前面的截圖大家可以發現,這個遊戲中主要由“剩餘雷數”,“帶有face的按鈕”,“用去的時間”,“掃雷區”構成。這些部件我大致分了一下類:“剩餘雷數”,“帶有face的按鈕”,“用去的時間”屬於資訊層,“掃雷區”屬於地雷層。這樣一來就又誕生了兩個類:InfoLayer,MineLayer。
InfoLayer.js
這個類正如上面所說,用於放置“剩餘雷數”,“帶有face的按鈕”,“用去的時間”這些部件。具體程式碼如下:
function InfoLayer () {
var s = this;
LExtends(s, LSprite, []);
s.mineLeftNumTxt = null;
s.button = null;
s.timeUsedTxt = null;
s.timeUsedNum = 0;
s.preTime = 0;
s.mineLeftNum = mineNum;
s.isStart = false;
s.addMineLeftNumLayer();
s.addButton();
s.addTimeUsedLayer();
s.addEventListener(LEvent.ENTER_FRAME, function () {
if (!s.isStart) {
return;
}
s.refreshTimeUsedNumTxt();
});
}
InfoLayer.prototype.addMineLeftNumLayer = function () {
var s = this;
var mineLeftNumLayer = new LSprite();
s.addChild(mineLeftNumLayer);
s.mineLeftNumTxt = new LTextField();
s.mineLeftNumTxt.text = 10000000;
s.mineLeftNumTxt.color = "white";
s.mineLeftNumTxt.size = 30;
mineLeftNumLayer.addChild(s.mineLeftNumTxt);
mineLeftNumLayer.graphics.drawRoundRect(
2, "white",
[
-5, -5,
s.mineLeftNumTxt.getWidth() + 10,
s.mineLeftNumTxt.getHeight() + 10,
3
],
true, "black"
);
s.mineLeftNumTxt.text = s.mineLeftNum;
};
InfoLayer.prototype.addButton = function () {
var s = this, btnBmp = new LBitmap(new LBitmapData(dataList["face_smile"]));
s.button = new ButtonTemplate(btnBmp, 1.2);
s.button.x = s.getWidth() + 50;
s.button.y = -15;
s.addChild(s.button);
s.button.addEventListener(LMouseEvent.MOUSE_UP, function () {
s.timeUsedNum = 0;
s.preTime = new Date().getTime();
s.mineLeftNum = mineNum;
s.isStart = false;
s.parent.createMineLayer();
s.refreshMineLeftNumTxt();
s.refreshTimeUsedNumTxt();
s.changeFace("smile");
})
};
InfoLayer.prototype.addTimeUsedLayer = function () {
var s = this;
var timeUsedLayer = new LSprite();
timeUsedLayer.x = s.getWidth() + 50;
s.addChild(timeUsedLayer);
s.timeUsedTxt = new LTextField();
s.timeUsedTxt.text = 10000000;
s.timeUsedTxt.color = "white";
s.timeUsedTxt.size = 30;
timeUsedLayer.addChild(s.timeUsedTxt);
timeUsedLayer.graphics.drawRoundRect(
2, "white",
[
-5, -5,
s.timeUsedTxt.getWidth() + 10,
s.timeUsedTxt.getHeight() + 10,
3
],
true, "black"
);
s.timeUsedTxt.text = s.timeUsedNum;
};
InfoLayer.prototype.changeFace = function (name) {
this.button.setContent(new LBitmap(new LBitmapData(dataList["face_" + name])));
};
InfoLayer.prototype.refreshMineLeftNumTxt = function () {
this.mineLeftNumTxt.text = this.mineLeftNum;
};
InfoLayer.prototype.refreshTimeUsedNumTxt = function (e) {
var s = this, nowTime = new Date().getTime();
s.timeUsedNum += (nowTime - s.preTime) / 1000;
s.preTime = nowTime;
s.timeUsedTxt.text = parseInt(s.timeUsedNum);
};
玩過windows xp掃雷的都知道,在“掃雷區”中點選一下滑鼠,那按鈕上的face就會改變,所以為了在MineLayer和InfoLayer進行互動,我在InfoLayer上加了一些用於改變剩餘雷數以及更改按鈕上face的函式(refreshMineLeftNumTxt和changeFace)。
這個類中用到了ButtonTemplate這個類,這個類是一個按鈕類,在遊戲中我們就用到了一種按鈕,所以就用ButtonTemplate把這些按鈕的功能統一起來。
ButtonTemplate.js
這個按鈕類出現在前面講的InfoLayer中,還會出現在下面要講的MineLayer中,作為方塊。
我把按鈕主要分為兩個部分:按鈕背景,按鈕內容。
我們在按鈕中要用到的功能主要有如下幾個:
- 設定按鈕上的內容,如上面說的face
- 在雷被掃除後,按鈕背景會消失,所以需要提供刪除按鈕背景的方法
- 刪除按鈕上的內容
- 掃雷中,如果滑鼠的左右兩鍵同時按下,會把四周的方塊突出一下,這裡我用到的是把這些方塊的按鈕背景設定為禁用(STATE_DISABLE)的狀態,所以需要用到設定按鈕狀態的功能。其實設定為禁用狀態是種dirty way,但是lufylegend的LButton中就只提供了ENABLE和DISABLE這兩種狀態,所以,就這樣用好了……
Ok,該上程式碼了:
function ButtonTemplate (img, btnBmpScale) {
var s = this;
LExtends(s, LSprite, []);
var btnImg = dataList["button_sheet"];
var normalBmp = new LBitmap(new LBitmapData(btnImg, 0, 0, 48, 48));
var overBmp = new LBitmap(new LBitmapData(btnImg, 0, 48, 48, 48));
var downBmp = new LBitmap(new LBitmapData(btnImg, 0, 96, 48, 48));
s.button = new LButton(normalBmp, overBmp, downBmp.clone(), downBmp.clone());
s.button.scaleX = s.button.scaleY = btnBmpScale || 1;
s.button.staticMode = true;
s.addChild(s.button);
s.content = null;
if (typeof img == UNDEFINED || !img) {
return;
}
s.setContent(img)
}
ButtonTemplate.prototype.setContent = function(content) {
var s = this;
s.removeContent();
s.content = content;
s.content.x = (s.button.getWidth() - s.content.getWidth()) / 2;
s.content.y = (s.button.getHeight() - s.content.getHeight()) / 2;
s.addChild(s.content);
};
ButtonTemplate.prototype.removeContent = function() {
var s = this;
if (s.content) {
s.content.remove();
s.content = null;
}
};
ButtonTemplate.prototype.removeButton = function() {
var s = this;
if (s.button) {
s.button.remove();
}
};
ButtonTemplate.prototype.setIntoNormalState = function () {
this.button.setState(LButton.STATE_ENABLE);
};
ButtonTemplate.prototype.setIntoOverState = function () {
this.button.setState(LButton.STATE_DISABLE);
};
上面說的按鈕背景就是button屬性,內容就是content。
MineLayer.js
這個類是非常重要,所以需要好好的解釋其中的一些程式碼。先看構造器:
function MineLayer () {
var s = this;
LExtends(s, LSprite, []);
s.map = new Array();
s.waitingTime = 1;
s.startTimer = false;
s.timerIndex = 0;
s.onUpCallback = null;
s.preMouseButton = null;
s.doubleDown = false;
s.completeNum = 0;
s.create();
s.addEventListener(LEvent.ENTER_FRAME, s.loop);
}
介紹一下其中的屬性,
- map:一個數組,用於存放佈雷後的資料,-1表示此位置是雷,>-1表示該位置附近有多少雷,例如:
[
[-1, 2, 1],
[1, 2, -1],
[0, 1, 1]
]
- waitingTime:這個屬性用於實現滑鼠左右兩鍵同時按下的事件
- timeIndex:同上
- startTimer:同上
- onUpCallBack:滑鼠鬆開後執行的函式。這個函式在滑鼠按下時設定
- preMouseButton:同waitingTime
- doubleDown:判斷是否滑鼠左右兩鍵同時按下
- completeNum:標記正確的雷的數量
接下來來看看create函式:
MineLayer.prototype.create = function () {
var s = this, positionList = new Array();
for (var i = 0; i < blockYNum; i++) {
var row = new Array();
s.map.push(row);
for (var j = 0; j < blockXNum; j++) {
var btn = new ButtonTemplate();
btn.x = j * 48;
btn.y = i * 48;
btn.positionInMap = {x : j, y : i};
btn.isFlag = false;
btn.isSwept = false;
s.addChild(btn);
btn.addEventListener(LMouseEvent.MOUSE_DOWN, function (e) {
s.onDown(e.currentTarget, e.button);
});
btn.addEventListener(LMouseEvent.MOUSE_UP, function (e) {
s.onUp(e.currentTarget, e.button);
});
row.push(0);
positionList.push({x : j, y : i});
}
}
for (var k = 0; k < mineNum; k++) {
var mineIndex = Math.floor(Math.random() * positionList.length),
o = positionList[mineIndex];
s.map[o.y][o.x] = -1;
positionList.splice(mineIndex, 1);
}
for (var m = 0; m < blockYNum; m++) {
var row = s.map[m];
for (var n = 0; n < blockXNum; n++) {
var count = 0,
list = null;
if (row[n] == -1) {
continue;
}
list = s.findBlockAround(n, m);
for (var f = 0, ll = list.length; f < ll; f++) {
if (list[f].v == -1) {
count++;
}
}
s.map[m][n] = count;
}
}
};
create函式致力於佈雷以及把每個方塊標上數字,這個數字就是這個方塊四周有的雷數。
這裡我用到了一個很重要的函式——findBlockAround:
MineLayer.prototype.findBlockAround = function (x, y) {
var s = this,
l = blockYNum,
t = blockXNum,
di = y + 1,
ti = y - 1,
ri = x + 1,
li = x - 1,
cr = null,
rl = new Array();
if (di < l) {
cr = s.map[di];
rl.push({x : x, y : di, v : cr[x]});
if (li >= 0) {
rl.push({x : li, y : di, v : cr[li]});
}
if (ri < t) {
rl.push({x : ri, y : di, v : cr[ri]});
}
}
if (ti >= 0) {
cr = s.map[ti];
rl.push({x : x, y : ti, v : cr[x]});
if (li >= 0) {
rl.push({x : li, y : ti, v : cr[li]});
}
if (ri < t) {
rl.push({x : ri, y : ti, v : cr[ri]});
}
}
if (li >= 0) {
cr = s.map[y];
rl.push({x : li, y : y, v : cr[li]});
}
if (ri < t) {
cr = s.map[y];
rl.push({x : ri, y : y, v : cr[ri]});
}
return rl;
};
這個函式是幹啥的呢?噢~原來是用來尋找某個方塊附近一圈的方塊。也就是左上,正上,右上,正左,左下,正下,右下,正右這幾個位置的方塊。由於這個功能很多地方要用,所以我把它單獨封裝進一個函式。
再來看滑鼠事件實現部分,主要由onDown,onUp,loop這個三個函式一起合作來完成:
MineLayer.prototype.onDown = function (btn, mouseButton) {
var s = this;
s.parent.infoLayer.changeFace("surprise");
if (
s.startTimer
&& (mouseButton == 0 || mouseButton == 2)
&& mouseButton != s.preMouseButton
&& !btn.isFlag
&& btn.isSwept
) {
s.startTimer = false;
s.timerIndex = 0;
s.doubleDown = true;
s.preMouseButton = mouseButton;
if (!s.isMineAroundHasBeenSwept(btn)) {
var p = btn.positionInMap,
list = s.findBlockAround(p.x, p.y);
for (var i = 0, l = list.length; i < l; i++) {
var o = list[i], b = s.getChildAt(o.y * blockXNum + o.x)
if (!b.isFlag) {
b.setIntoOverState();
}
}
}
return;
}
s.startTimer = true;
if (mouseButton == 0) {
s.onUpCallback = function () {
s.sweepThis(btn, true);
}
} else if (mouseButton == 2) {
s.onUpCallback = function () {
s.setFlagTo(btn);
}
}
};
MineLayer.prototype.onUp = function (btn, mouseButton) {
var s = this, infoLayer = s.parent.infoLayer;
infoLayer.changeFace("smile");
if (s.doubleDown) {
var p = btn.positionInMap,
list = s.findBlockAround(p.x, p.y);
s.doubleDown = false;
s.startTimer = false;
s.preMouseButton = null;
if (s.isMineAroundHasBeenSwept(btn)) {
s.sweepBlocksAround(btn, false);
} else {
for (var i = 0, l = list.length; i < l; i++) {
var o = list[i], b = s.getChildAt(o.y * blockXNum + o.x);
if (!b.isFlag) {
b.setIntoNormalState();
}
}
}
return;
}
if (typeof s.onUpCallback == "function") {
if (!infoLayer.isStart) {
infoLayer.isStart = true;
infoLayer.preTime = new Date().getTime();
}
s.onUpCallback();
s.onUpCallback = null;
}
};
MineLayer.prototype.loop = function (e) {
var s = e.currentTarget;
if (!s.startTimer) {
return;
}
if (s.timerIndex++ > s.waitingTime) {
s.timerIndex = 0;
s.startTimer = false;
}
};
主要來講講實現左右兩鍵同時按下事件的實現:
首先我們得想象一下我們左右兩鍵同時按下時的操作,大致可以簡化為兩個按鍵中其中以個按下後,在短暫時間後,另一個按鍵也按下,如果其中任意一個鬆開,那就執行同時按下對應的程式碼;如果超出了短暫時間才按下另一個按鍵,那麼我們就把滑鼠鬆開後要執行的函式設定為最後按下的那個鍵對應的程式碼;如果壓根就沒第二次按下,那就直接執行第一次按下對應的程式碼。想到這裡後,我們要做的就很明確了。短暫時間的計時是交給loop函式來完成,滑鼠按下和鬆開就分別交給了onDown和onUp。
接下來是sweepThis和sweepBlocksAround這兩個函式:
MineLayer.prototype.sweepBlocksAround = function (btn) {
var s = this,
p = btn.positionInMap,
list = s.findBlockAround(p.x, p.y);
for (var i = 0, l = list.length; i < l; i++) {
var o = list[i], b = s.getChildAt(o.y * blockXNum + o.x);
if (o.v >= 0 && !b.isSwept) {
s.sweepThis(b);
} else if (o.v == -1 && !b.isFlag) {
s.sweepThis(b);
}
}
};
MineLayer.prototype.sweepThis = function (btn) {
var s = this, p = btn.positionInMap, value = s.map[p.y][p.x];
if (btn.isSwept) {
return;
}
if (btn.isFlag) {
s.setFlagTo(btn);
}
if (value == -1) {
s.gameOver("lose");
return;
}
var contentLayer = new LSprite();
contentLayer.filters = [new LDropShadowFilter()];
contentLayer.graphics.drawRect(2, "white", [0, 0, btn.getWidth(), btn.getHeight()], true, "lightgray");
var txt = new LTextField();
txt.text = (value == 0) ? "" : value;
txt.x = (contentLayer.getWidth() - txt.getWidth()) / 2;
txt.y = (contentLayer.getHeight() - txt.getHeight()) / 2;
txt.weight = "bold";
txt.color = "white";
txt.lineColor = "#0088FF";
txt.stroke = true;
txt.lineWidth = 3;
txt.size = 18;
contentLayer.addChild(txt);
btn.isSwept = true;
btn.removeButton();
btn.setContent(contentLayer);
if (s.isMineAroundHasBeenSwept(btn)) {
s.sweepBlocksAround(btn);
}
};
sweepThis的主要功能就是把某個方塊(及引數btn)給掃開。然後判斷這個方塊四周被標記的方塊數是不是等於該方塊四周的雷數,如果判斷通過,就通過sweepBlockAround執行“智慧幫掃演算法”。這個用來判斷四周被標記的方塊數是不是等於該方塊四周的雷數的函式就是isMineAroundHasBeenSwept:
MineLayer.prototype.isMineAroundHasBeenSwept = function (btn) {
var s = this,
p = btn.positionInMap,
count = 0,
value = s.map[p.y][p.x],
list = null;
if (value == 0) {
return true;
}
list = s.findBlockAround(p.x, p.y);
for (var i = 0, l = list.length; i < l; i++) {
var o = list[i];
if (s.getChildAt(o.y * blockXNum + o.x).isFlag) {
count++;
}
}
if (count == value) {
return true;
}
return false;
};
在sweepBlockAround中,我們要接受一個引數,這個是告訴sweepBlockAround“幫掃演算法”對哪個方塊起作用,及以誰為中心,向四周掃開其他方塊。在這個函式中,我們首先把四周的方塊獲得,並放入一個數組,然後遍歷這個陣列,如果遍歷到的按鈕是>=0的,並且沒有被掃開,就把它掃開;如果是=-1,及代表雷,並且又沒被標記就也把它掃開,因為sweepBlockAround都是在通過isMineAroundHasBeenSwept後才呼叫的,所以說如果出現上面的情況,說明玩家判斷失誤了。
最後再來看剩餘的及個膚淺易懂的函式:
MineLayer.prototype.setFlagTo = function (btn) {
var s = this,
p = btn.positionInMap;
flagBmp = null,
infoLayer = null;
if (btn.isSwept) {
return;
}
flagBmp = new LBitmap(new LBitmapData(dataList["flag"]));
infoLayer = s.parent.infoLayer;
if (btn.isFlag) {
btn.isFlag = false;
infoLayer.mineLeftNum++;
if (s.map[p.y][p.x] == -1) {
s.completeNum--;
}
btn.removeContent();
} else {
btn.isFlag = true;
infoLayer.mineLeftNum--;
if (s.map[p.y][p.x] == -1) {
s.completeNum++;
}
btn.setContent(flagBmp);
}
infoLayer.refreshMineLeftNumTxt();
if (s.completeNum == mineNum && infoLayer.mineLeftNum == 0) {
for (var i = 0; i < blockYNum; i++) {
for (var j = 0; j < blockXNum; j++) {
var b = s.getChildAt(i * blockXNum + j);
if (!b.isSwept && !b.isFlag) {
s.sweepThis(b);
}
}
}
s.gameOver("win");
}
};
MineLayer.prototype.gameOver = function (r) {
var s = this, infoLayer = s.parent.infoLayer;
for (var i = 0; i < blockYNum; i++) {
var row = s.map[i];
for (var j = 0; j < blockXNum; j++) {
var v = row[j], b = s.getChildAt(i * blockXNum + j);
b.mouseEnabled = false;
b.mouseChildren = false;
if (r == "lose" && v == -1) {
b.setContent(new LBitmap(new LBitmapData(dataList["mine"])));
infoLayer.changeFace("sad");
}
}
}
if (r == "win") {
infoLayer.changeFace("happy");
}
infoLayer.isStart = false;
};
setFlagTo就是右鍵標小旗功能。gameOver就是遊戲結束時呼叫的。這兩個函式都涉及了和InfoLayer的互動。
執行程式碼,就得到了一款掃雷遊戲。
最近掃上癮了,所以就再來幾把吧!!
原始碼下載
相關推薦
HTML5遊戲開發-掃雷及其演算法研究
呂蒙曰:士隔三月【1】,當刮目相看。所以,在下在這三月中發奮圖強,花了約莫8節資訊課的時間研究掃雷。嗚呼,由於在下才能尚且不足,所以也就只能勉強打過中級難度的吧。不過,一邊玩的同時,我還一邊對掃雷這個遊戲的製做方法構思了一下。所以說,本文中的演算法完全是憑藉自己
html5 遊戲開發
簡單的 刷新 element 向量 簡單 load alt 碰撞 最終 近來想做html5遊戲開發些小東西玩一下,因為手邊就是筆記本,想怎麽玩就怎麽玩了,今年可以說是非常重要特殊的一年,感覺有些倒黴,不過,心態最重要,該怎麽做的時候就去怎麽做吧,日子的24小時是不會變的,不
HTML5遊戲開發pdf
ffffff pan htm one asc div 滿足 技術 應用 下載地址:網盤下載 本書共10 章,通過10 個具體的遊戲示例詳細介紹HTML5 的用法。每章都先列出相關的技術特性並給出了應用的描述,然後討論了實現這個應用的關鍵需求,接著強調了滿足這些需求的HTM
HTML5遊戲開發高級教程 | Lynda教程 中文字幕
編程語言 圖形 們的 eve 通過 fir 添加 應用 學習java HTML5遊戲開發高級教程 | Lynda教程 中文字幕Advanced HTML5 Game Development課程ID: 597988時長: 2.3小時所屬類別:Html 全部遊戲開發課程了解如何
HTML5遊戲開發(五):飛機大戰之讓所有元素動起來
《HTML5遊戲開發》系列文章的目的有:一、以最小的成本去入門egret小專案開發,官方的教程一直都是面向中重型;二、egret可以非常輕量;三、egret相比PIXI.js和spritejs文件更成熟、友好;四、學習從0打造高效的開發工作流。 HTML5遊戲開發(一):3分鐘建立一個hello wo
PhaserJS 3 螢幕適配時的小坑 -- JavaScript Html5 遊戲開發
PhaserJS 巨坑:在config內不要把 width 設為 window.innnerWidth在config內不要把 width 設為 window.innnerWidth在config內不要把 width 設為 window.i
HTML5遊戲開發(四)載入
var mp3Support, oggSupport; var audio&nb
輕裝上陣Html5遊戲開發,JEESJS(四)
下面我將通過完善Demo的形式,來演示下用法。首先在html中匯入需要的庫,我定義了一個index.html用來作為演示的入口: index.html: <!DOCTYPE html> <html> <head> <title><
輕裝上陣Html5遊戲開發,JEESJS(三)
這裡介紹下UI的基本類,構建形式參考了createjs,比較清楚的實現了繼承。 Widget裡目前分為了大致2種類型,一個是容器型別,一個是非容器型別。區別在於可新增子控制元件。 基礎類 Widget :https://github.com/aiyoyoyo/jeesjs/blob/ma
HTML5遊戲開發進階指南讀書筆記
建立遊戲圖層#gamecanvas(遊戲圖層)、#scorescreen(得分顯示圖層)、#gamestartscreen(遊戲開始圖層)、#levelselectscreen(關卡選擇圖層)、#loadingscreen(資源載入圖層)、#endingscreen(遊戲結束圖層)所有圖層共用.gamelay
html5遊戲開發-彈幕+仿雷電小遊戲demo
本遊戲使用的是html5的canvas,運行遊戲需要瀏覽器支援html5。本篇文章詳細講解如何用html5來開發一款射擊遊戲,雷電可以說是射擊遊戲中的經典,下面就來模仿一下。先看一下游戲截圖演示地址遊戲開發,需要用到開源引擎:lufylegend.jslufylegend.j
【Canvas】HTML5遊戲開發的基本流程+P2.js物理引擎實戰開發
《HTML5遊戲開發的基本流程》 * 1. HTML5的簡述 * 2. HTML5遊戲開發所需的環境與工具 * 2.1. 開發環境 * 2.1.1. 瀏覽器 * 2.1.2. 開發語言 *
主流HTML5遊戲開發引擎的分析和對比
本文主要選取了Construct2、ImactJS、LimeJS、GameMaker、CreateJS、lycheeJS、Crafty、three.js、melonJS、Turbulenz、Quintus、Cocos2d-html5等進行了簡要介紹和對比,主要是根據網上的
HTML5遊戲開發進階 11:WebSocket與多人對戰模式
我們將使用HTML5 WebSocket API向我們的RTS遊戲加入多人對戰支援 11.1 使用Node.js操作WebSocket API 之前瀏覽器與伺服器之間通訊的唯一方式就是通過逐個request序列,對伺服器進行輪詢或長輪詢。雖然這些方式確實有效,
html5遊戲開發教程實戰:五子棋、四子棋、圍棋、翻轉棋四種對弈遊戲,僅僅100行程式碼
本文是一個非常具有挑戰性的程式設計,因為100行程式碼,約莫10000個字元左右,將實現圍棋、五子棋、四子棋和翻轉棋四種雙人對弈遊戲。請注意,這四個對弈遊戲不是初級程式設計者的習作,而是有著棋盤、立體棋子、事件、走棋規則判斷、輸贏判斷的完整對弈遊戲,並且可以離線儲存到 iPad、Android 平板中,
HTML5遊戲開發工具實踐(一)
在介紹Demo遊戲製作之前,先簡單介紹一下游戲製作使用的工具Orion2,這是一個圖形化的HTML5編輯工具,目標是用來開發小遊戲、互動電子書/雜誌等方面的H5應用。 Orion2工具主要模組包括: 場景編輯模組,可編輯單元均為外掛模組,可方便通過外掛SDK
HTML5遊戲的開發設定
技術支持 google 小遊戲 運營商 第三方 一個行業從零到成熟,開發者生態也是對應的,我們今年看到很多大公司,包括像微軟和Google,也參與到了H5開發者生態的建設當中。HTML5遊戲是處於一個螺旋上升的發展歷程,可能會經歷幾個時期,最初還是廣告、小遊戲,到後來隨著網絡速度的提升和運
javascript+HTMl5遊戲下載,開發一個都能月薪上萬!舅服你
lock itl 五子棋 opera sta 你是 http store 進階學習 HTML5時代已經到來許久了,你是否已經掌握了那麽一點呢?今天小編給大家講講h5的折疊多設備、跨平臺特性, 即用HTML5制作遊戲。相比flash,HTML5更加靈活方便,隨著瀏覽器技術的
微信紅包牛牛掃雷遊戲開發制作
軟件開發 紅包遊戲 龍火科技已開發多種紅包遊戲平臺,你可以在這裏找到你??想要的紅包遊戲。主營:微信紅包遊戲、紅包牛牛遊戲、掃雷遊戲。二8杠遊戲、五人大戰等等。開發咨詢聯系電話李先生18300041725(同微信)QQ:1244948540 系統亮點: 1、後臺可自由設置紅包類型
【Unity3d遊戲開發】遊戲中的貝塞爾曲線以及其在Unity中的實現
轉載收藏:原文連結https://www.cnblogs.com/msxh/p/6270468.html 閱讀目錄 一、簡介 二、公式 三、實現與應用 RT,馬三最近在參與一款足球遊戲的開發,其中涉及到足球的各種運動軌跡和路徑,比如射門的軌跡,高吊球