三消遊戲核心邏輯的一種實現
前段時間很喜歡玩"開心消消樂"這個休閒小遊戲, 剛好大學的一位同學也在玩,後來就想著如果讓自己來寫這個邏輯要怎麼寫, 經過一天的構思, 找到一種利用深度優先搜尋思想來實現的方式.
三消遊戲的規則
- 一個m x n 的棋盤內, 初始狀態下, 填滿若干種不同型別的… 暫且叫Hero吧, 型別的數量根據遊戲難度不同而不同, 一般有4~5種.
- 同種型別的Hero連著 >= 3時消除, 橫著豎著都可以消, 斜著的不能消.
本文約定以下5種不同型別的Hero, 對應的值分別為1,2,3,4,5.
下面模擬一個5x5的棋盤, 說明一下規則,
消除前的棋盤:
1 1 2 4 5
5 1 1 1 4
5 2 1 5 4
4 3 3 3 4
消除後的棋盤:
1 1 2 4 5
5 0 0 0 0
5 2 1 5 0
4 0 0 0 0
演算法實現
實現語言最早用的是C++, 後來因為要用Html繪製介面, 所以把程式碼編譯成JavaScript的了, 而且因為現在過年回家了, C++的程式碼在公司, 只有Js的程式碼可以Po出來了.
演算法其實很簡單, 寫一個函式ScanPos(pos), 這個函式用來掃描pos點的左、上、右、下四個鄰近的點,如果某個鄰近的點和當前掃描的點pos的型別一樣,則遞迴呼叫ScanPos(newpos), 繼續遞迴呼叫ScanPos, 直到所有方向的點都不匹配或者到達邊界為止。
用上面那個例子說明一下掃描的過程:
----> x
|
| 座標方向, 為了直觀,原點為1, 1
V
y
假如我們初始掃描的點是(3, 2), HeroType = 1
1 1 2 4 5
5 1 1 1 4
5 2 1 5 4
4 3 3 3 4
Step.1
1 1 2 4 5
5 1 1 1 4
5 2 1 5 4
4 3 3 3 4
Step.2
1 1 2 4 5
5 1 1 1 4
5 2 1 5 4
4 3 3 3 4
Step.3
1 1 2 4 5
5 1 1 1 4
5 2 1 5 4
4 3 3 3 4
這裡掃到5後發現5不匹配, 於是開始往上Scan
Step.4
1 1 2 4 5
5 1 1 1 4
5 2 1 5 4
4 3 3 3 4
Step.5
1 1 2 4 5
5 1 1 1 4
5 2 1 5 4
4 3 3 3 4
Scan到1,1這個點後,左、上、右、下都無效,左上到達邊界,右邊是來時的地方, 所以也不匹配,下邊是5,也不匹配, 所以回退到1,2這個點的ScanPos函式,這個點的上、右也都不匹配, 所以繼續往回退,…… 中間過程類似,不再描述。最後掃描後的棋盤狀態:
1 1 2 4 5
5 1 1 1 4
5 2 1 5 4
4 3 3 3 4
可以發現這樣掃描後,第一行和第三行的3個點,並不符合三消規則,但是也都被掃描到了。這就需要把這些點排除掉。
排除不符合的點
這一步做的比較粗暴,直接兩層for迴圈遍歷整個棋盤,在內層迴圈內對每一行Hero進行遍歷時, 記錄相鄰的相同點的個數,然後把每個點的Match次數儲存起來,最後只有Match >=3 的才會消除。
以上就是核心演算法了,當然還有很多細節需要注意,比如抽象出Cell類,每個Cell有它的狀態以及Match次數, 在每次ScanPos時,都要實時更新Cell的狀態,等等。
JavaScript程式碼實現
function Grid (cols, rows) {
this.cols = cols;
this.rows = rows;
this.board = new Array();
this.lastClickPosX = -1;
this.lastClickPosY = -1;
this.score = 0;
}
Grid.prototype.scanPos = function(scanPosX, scanPosY, parentPosX, parentPosY) {
scanPosX = parseInt(scanPosX);
scanPosY = parseInt(scanPosY);
parentPosX = parseInt(parentPosX);
parentPosY = parseInt(parentPosY);
//console.log("scanPos1: %d %d %d %d", scanPosX, scanPosY, parentPosX, parentPosY);
var parentCell = this.getCellByPos(parentPosX, parentPosY);
if (parentCell == null) {
console.log("parentCell is null");
return;
}
//left
var leftPosX = scanPosX - 1;
var leftPosY = scanPosY;
//console.log("(%d %d) (%d %d)", leftPosX, leftPosY, parentPosX, parentPosY);
var leftCell = this.getCellByPos(leftPosX, leftPosY);
//console.log("(%d %d) (%d %d)", leftPosX, leftPosY, parentPosX, parentPosY);
//console.log("leftCell:", leftCell);
if (leftCell != null
&& (leftPosX != parentPosX || leftPosY != parentPosY)
&& leftCell.getState() == CELL_STATE.CS_NORMAL
&& leftCell.getHeroType() == parentCell.getHeroType()
)
{
//console.log("left fuck!");
leftCell.updateState(CELL_STATE.CS_SCANED);
this.scanPos1(leftPosX, leftPosY, scanPosX, scanPosY);
}
//console.log("-----scan pos: scanPosX=%d, scanPosY=%d", scanPosX, scanPosY);
//top
var topPosX = scanPosX;
var topPosY = scanPosY - 1;
//console.log("(%d %d) (%d %d)", topPosX, topPosY, parentPosX, parentPosY);
var topCell = this.getCellByPos(topPosX, topPosY);
//console.log("(%d %d) (%d %d)", topPosX, topPosY, parentPosX, parentPosY);
//console.log("topCell:", topCell);
if (topCell != null
&& (topPosX != parentPosX || topPosY != parentPosY)
&& topCell.getState() == CELL_STATE.CS_NORMAL
&& topCell.getHeroType() == parentCell.getHeroType()
)
{
//console.log("top fuck!");
topCell.updateState(CELL_STATE.CS_SCANED);
this.scanPos1(topPosX, topPosY, scanPosX, scanPosY);
}
//console.log("-----scan pos: scanPosX=%d, scanPosY=%d", scanPosX, scanPosY);
//right
var rightPosX = scanPosX + 1;
var rightPosY = scanPosY;
//console.log("(%d %d) (%d %d)", rightPosX, rightPosY, parentPosX, parentPosY);
var rightCell = this.getCellByPos(rightPosX, rightPosY);
//console.log("(%d %d) (%d %d)", rightPosX, rightPosY, parentPosX, parentPosY);
//console.log("rightCell:", rightCell);
if (rightCell != null
&& (rightPosX != parentPosX || rightPosY != parentPosY)
&& rightCell.getState() == CELL_STATE.CS_NORMAL
&& rightCell.getHeroType() == parentCell.getHeroType()
)
{
//console.log("right fuck!");
rightCell.updateState(CELL_STATE.CS_SCANED);
this.scanPos1(rightPosX, rightPosY, scanPosX, scanPosY);
}
//console.log("-----scan pos: scanPosX=%d, scanPosY=%d", scanPosX, scanPosY);
//down
var downPosX = scanPosX;
var downPosY = scanPosY + 1;
//console.log("(%d %d) (%d %d)", downPosX, downPosY, parentPosX, parentPosY);
var downCell = this.getCellByPos(downPosX, downPosY);
//console.log("(%d %d) (%d %d)", downPosX, downPosY, parentPosX, parentPosY);
//console.log("downCell:", downCell);
if (downCell != null
&& (downPosX != parentPosX || downPosY != parentPosY)
&& downCell.getState() == CELL_STATE.CS_NORMAL
&& downCell.getHeroType() == parentCell.getHeroType()
)
{
//console.log("down fuck!");
downCell.updateState(CELL_STATE.CS_SCANED);
this.scanPos1(downPosX, downPosY, scanPosX, scanPosY);
}
//console.log("-----scan pos: scanPosX=%d, scanPosY=%d", scanPosX, scanPosY);
};
function scan_match_three_xsame(xList) {
for (var itor = 0; itor < xList.length; itor++) {
var vecCell = xList[itor];
if (vecCell == null)
continue;
if (vecCell.length < 3)
continue;
var start = 0;
var matchCount = 1;
var i;
for (i = 1; i < vecCell.length; i++) {
var prevCell = vecCell[i-1];
var currCell = vecCell[i];
//console.assert(prevCell && currCell, "fatal error");
if ( prevCell == null || currCell == null
|| prevCell.getPosX() != currCell.getPosX()
)
{
console.assert(0, "fatal error");
return;
}
if (currCell.getPosY() == prevCell.getPosY() + 1) {
matchCount++;//continuous
}
else if(currCell.getPosY() > prevCell.getPosY()+ 1) {
//interrupt
for (var j = start; j < i; j++) {
console.assert(vecCell[j].getRefXSame() == 0);
vecCell[j].setRefXSame(matchCount);
}
start = i;
matchCount = 1;
}
else {
console.log("x-wtf?!: ", prevCell, currCell);
console.assert(0, "vec's data is not sorted!");
}
}
//loop end, update cell's ref
for (var j = start; j < i; j++) {
console.assert(vecCell[j].getRefXSame() == 0);
vecCell[j].setRefXSame(matchCount);
}
}
}
因為程式碼太多,只貼出部分程式碼, 如果需要完整程式碼,請在下面留郵箱。
PS. 家裡好冷, 寫這篇文章手都快dong掉了
2016.11.16 更新
之前我在cocos2dx的應用商店裡(是這麼叫吧?還是程式碼庫,反正是cocos2d的一個官方工具內, 類似u3d的asset store), 看到過一完成度更高的三消遊戲, 那個是用cocos2dx(c++)寫的, 資源都有, 邏輯也很完整, 編譯完就可以直接玩的, 具體叫什麼名字忘了, 你們要以去搜下, 就搜”三消”就行.
Github上可能也有吧, 也可以找找看
我的這個版本比較Low, 完成度很低, 如果是直接拿來做單估計還要花不少時間完善和改BUG, 如果是C++的專案,強烈推薦上面說的cocos2dx的版本