使用原生 JS 寫五子棋

weixin_33670713發表於2017-06-12

作者:Tumars
源自:http://www.ferecord.com/create-gobang-with-javascriot.html

前言

聽說這是一道騰訊的面試題,可能網上已經有不少答案了,晚上沒事看到這道題就自己做了下。邏輯很簡單,考慮到是面試題,使用了 ES6 的語法。本文介紹下核心邏輯跟部分程式碼。

6237353-940f940e0456efc7.png

目錄

  1. 前言
  2. 目錄
  3. 思路
  4. 儲存已下的棋
  5. 判斷落子後是否勝利
  6. 對外 API 與 UI 繫結
  7. 結語

思路

核心邏輯大致如下:

  • 棋盤上的點就是x, y的座標值;
  • 每次落子就給該點賦顏色值;
  • 落子後判斷該點為中心的 4 條軸,每條軸上的 9 個點,含中心點是否能連續 5 個點顏色相同;
  • 是的話就結束遊戲並判斷為該顏色值的持方勝利,否的話就改變顏色值繼續遊戲。

儲存已下的棋

我們建立個物件儲存已下的棋,該物件的結構形為{x6y9: 1, x3y8: 2, x20y1: 2 ...},初始為空,每次落子都給它新增屬性,屬性名錶示座標,屬性值表示棋子顏色,1為白色,2為黑色,未配置的屬性查詢返回0。同時設兩個方法用來操作該物件。

var board = new Proxy({},{
    get: function(target, property) {
        if (property in target) {
            return target[property];
        } else {
            return 0
        }
    }
})
 
//--------//
 
getProps(x,y) {
    return 'x'+x+'y'+y
}
 
getColor(board, x, y) {
    return board[this.getProps(x,y)]
}

判斷落子後是否勝利

首先獲取 4 條軸上的座標,我們設落子點的座標為[x, y],那該點左上角的左邊為 [x – 1, y – 1],右下角為 [x +1, y +1],所以每個方向都是 x,y 與 1, -1, 0 三個數兩兩形成的陣列加 4 次。如我們設落點座標為[0,0],則左上角的4個位置就是:

var 
var lt = []
for(var i = 1;i<5;i++) {
    lt.push([0-i, 0-i])
}
 
console.log(lt)     // [[-1,-1], [-2, -2], [-3, -3], [-4, -4]]

可以看出8個方向的相加值分別為[-1,-1], [-1,0], [1, -1], [-1, 0],以及它們的相反值。

var roundDirect = [[-1,-1], [-1,0], [1, -1], [-1, 0]]

接下來寫個函式checkRoundDirect用來判斷是否勝利,接收roundDirect陣列作為引數:

 // 判斷 4 個軸中是否有一個成立
checkRoundDirect(x, y, board, roundDirect) {
    return roundDirect.some(direct => this.checkSingleDirect(x, y, board, direct))
}

這裡把每條軸的判斷交給checkSingleDirect函式判斷:

// 判斷單個軸(兩個方向)是否成立
checkSingleDirect(x, y, board, direct) {
    var leftDirect = direct
    var rightDirect = direct.map(v=>-v)
    var getNum = this.getDirectSameColorNum.bind(this, x, y, board)
 
    return (getNum(leftDirect) + 1 + getNum(rightDirect)) >= 5  
}

判斷單軸是否成立的邏輯是以落子為起點,它左側與右側的連續相同顏色的點棋子數目相加大於 4,再加上落子本身,就是形成了 5 子。

中心點與一側的連續相同顏色棋子的數量通過getDirectSameColorNum函式獲取:

// 返回每個方向的顏色值相同的棋子數
getDirectSameColorNum(x, y, board, direct) {
    var result = 0
    var bindGetcolor = this.getColor.bind(this, board)
    var activeColor = bindGetcolor(x,y)
 
    for (var i = 1; i < 5; i++) {
        var nextColor = bindGetcolor(x + i * direct[0], y + i * direct[1])
        if (activeColor == nextColor) { result++ } else {break}
    }
    return result
}

這樣通過呼叫checkRoundDirect函式並傳入當前點的x, y引數以及已知的board, roundDirect引數,就可以得到落子後是否勝利連成 5 子。

對外 API 與 UI 繫結

每個棋盤例項對外輸出以下屬性與介面。

屬性:

  • board物件,每個屬性為已下棋子的座標,屬性值為該座標的顏色值;
  • palyChess(x, y, colorNumber)方法,是執行下棋的呼叫方法,引數分別為 x,y座標值與要下棋子的顏色值;

介面:

  • onEnd(color)onKeep(color),兩個回撥函式,每次落子後遊戲勝利結束及遊戲繼續的回撥事件,color是下次落子的顏色值。

同時構建一個棋盤的 UI 物件並返回一個配置函式用來初始化棋盤例項,這裡我就不寫 UI 物件的具體實現了,各位有興趣的可以檢視原始碼,UI 物件的主要目的是繫結棋盤例項、渲染頁面、繫結事件。

棋盤 UI 物件主要接收以下引數:

  • num,棋盤大小,會輸出num * num大小的棋盤

棋盤例項與 UI 繫結後共同輸出一個函式用來初始化棋盤,按如下方式呼叫:

// 初始化遊戲,(棋盤大小,{結束事件,落子事件})
Game.start(15,{
    onEnd(color) {
        alert(`遊戲結束, ${color === 1 ? '白子' : '黑子'}勝利`)
    },
    onKeep(color) {
        document.getElementById('info').innerHTML = `${color !== 1 ? '白子' : '黑子'}回合`
    }
})

結語

這個五子棋很簡單,只要有基本的 js 基礎跟清晰的思路就能很快做出來。原始碼中涉及到了作用域、原型、閉包、多型、柯里化等基礎知識,以及對物件、函式、陣列等方法的運用,同時使用了 ES6 的語法。

相關文章