手把手教你用 JavaScript 實現一個簡單的國際象棋 AI

本文做者: Lauri Hartikka

編譯:鬍子大哈 前端

翻譯原文:huziketang.com/blog/posts/

英文鏈接:A step-by-step guide to building a simple chess AIreact

轉載請註明出處,保留原文連接以及做者信息git

首先讓咱們先看幾個對開發簡單國際象棋 AI 頗有幫助的概念:github

  • 移動生成
  • 局面評估
  • 極大極小算法
  • α-β 剪枝

每一步中咱們都會對通過時間檢驗的國際象棋程序進行改進,我會展現不一樣算法風格所產生的影響。你也能夠在 GitHub 上看到最終的 AI 算法。算法

步驟 1:移動生成和棋盤可視化

使用 chess.js 庫來生成移動規則,使用 chessboard.js 來可視化棋盤。移動生成庫實現了全部國際象棋的規則,對於任意給定的棋盤狀態咱們均可以計算出下一步的合法的走棋方法。app

(移動生成函數的可視化版本。起始位置做爲輸入,輸出是全部可能的走法。)dom

使用這些庫可使咱們專一於咱們所感興趣的任務:開發最佳下棋的算法。咱們首先從建立以一個函數開始,在全部可能走法中返回一個隨機的結果。ide

var calculateBestMove =function(game) {
        //generate all the moves for a given position
        var newGameMoves = game.ugly_moves();
        return newGameMoves[Math.floor(Math.random() * newGameMoves.length)];
    };複製代碼

用這種方法,儘管它不是一個合格的棋手,可是起碼咱們能夠和它玩起來了。函數

步驟 2:位置評估

下面咱們試着讓它理解在一個肯定的位置上怎麼走比較好。實現這一功能最簡單的方法是計算棋盤上棋子的相對強度大小,用下面的對照表。post

經過評估函數,能夠選擇評估結果最佳的走法。

var calculateBestMove = function (game) {

        var newGameMoves = game.ugly_moves();
        var bestMove = null;
        //use any negative large number
        var bestValue = -9999;

        for (var i = 0; i < newGameMoves.length; i++) {
            var newGameMove = newGameMoves[i];
            game.ugly_move(newGameMove);

            //take the negative as AI plays as black
            var boardValue = -evaluateBoard(game.board())
            game.undo();
            if (boardValue > bestValue) {
                bestValue = boardValue;
                bestMove = newGameMove
            }
        }

        return bestMove;

    };複製代碼

這樣一來,一個切實的改善是,算法會吃掉它能夠吃掉的棋子。

(黑子使用了簡單評估算法,在這裏能夠看到:jsfiddle.net/lhartikk/m5…

步驟 3:用極大極小算法搜索樹

接下來咱們來建立一個搜索樹,經過它算法能夠選擇最佳走法,這裏須要用到極大極小算法

在這個算法中,會根據給定的樹深度對遞歸樹進行遍歷,所要評估的狀態就是樹的葉子節點。

這一步完成之後咱們把子節點中的最大或者最小值返回給父節點,這要依賴於白棋仍是黑棋來走這一步(這就是說在樹的每一層中都最大或者最小化輸出)。

(給定狀態的最大最小算法的可視化。白棋最好的走法是 b2-c3,由於能夠保證獲取一個狀態評估值是 -50)

var minimax = function (depth, game, isMaximisingPlayer) {
        if (depth === 0) {
            return -evaluateBoard(game.board());
        }
        var newGameMoves = game.ugly_moves();
        if (isMaximisingPlayer) {
            var bestMove = -9999;
            for (var i = 0; i < newGameMoves.length; i++) {
                game.ugly_move(newGameMoves[i]);
                bestMove = Math.max(bestMove, minimax(depth - 1, game, !isMaximisingPlayer));
                game.undo();
            }
            return bestMove;
        } else {
            var bestMove = 9999;
            for (var i = 0; i < newGameMoves.length; i++) {
                game.ugly_move(newGameMoves[i]);
                bestMove = Math.min(bestMove, minimax(depth - 1, game, !isMaximisingPlayer));
                game.undo();
            }
            return bestMove;
        }
    };複製代碼

有了最大最小步驟之後,咱們的算法能夠下出一些國際象棋的基本策略了。

極大極小算法的效率取決於搜索樹的深度,這就是咱們後面步驟要優化的地方。

步驟 4:α-β 剪枝

Alpha-beta 剪枝是極大極小算法的一種優化方法,能夠砍掉搜索樹中的某些分支。這能夠幫助咱們用一樣的資源的狀況下,儘量深地遍歷極大極小搜索樹。

α-β 剪枝的原理是在遍歷搜索樹的過程當中發現能夠終止遍歷的狀態,進而把整個分支剪掉的過程。這是由於發現下一步會致使比上一步更糟的結果,那麼就不用再遍歷下去了。

α-β 剪枝不影響極大極小算法的結果,僅僅是使極大極小算法運行的更快。假設遍歷時恰巧第一個狀態就是最佳走法,那麼 α-β 剪枝會更加有效。

有了 α-β,極大極小算法如虎添翼,能夠看下面的例子。

(本圖是給定的起始棋盤狀態,下面的數字是若是遍歷深度是 4 的話,須要評估的狀態總數。)

本連接是基於 α-β 算法優化的國際象棋 AI。

步驟 5:改善評估函數

初始的評估函數很是簡單,只是數了盤面上的數值而已。下面咱們來改善它,把棋子的位置因素也考慮到評估結果裏面去。例如在棋盤中間的馬會比在棋盤邊緣的馬位置更好(由於它的可選擇性更多,也更加活躍)。

咱們來稍微調整一下棋盤上棋子狀態的權重,這一圖表是在國際象棋程序維基百科中給出的。

(棋盤權值表的可視化。能夠根據棋子的位置增長或者減小相應位置的權重)

經過上面的一系列改進,咱們的算法能夠下出像樣的棋局了,起碼開始像一個業餘棋手這樣了。

(改進後的評估函數加上搜索樹深度設置成 3 的 α-β 算法,能夠在這個地址看到:jsfiddle.net/q76uzxwe/1/…

總結

這個簡單的國際象棋算法不會犯一些很傻的錯誤,可是它依然是缺少策略理解的。

經過我所介紹的這種方法,能夠開發一個國際象棋程序來實現一些基本的玩法。「AI 部分」(不包括移動生成)只有 200 行代碼,也就是說這裏只實現了基本的概念。你能夠在 GitHub 中獲取最終版本的代碼。

關於算法的一些更深層次的改善能夠見下面連接:

若是你想要了解更多,查看國際象棋程序維基百科,這裏介紹了不少有用的資源,本文只是演示了國際象棋 AI 算法實現的基本步驟。

Happy Coding!若是本文對你有幫助,歡迎關注個人專欄-前端大哈,按期發佈高質量前端文章。


我最近正在寫一本《React.js 小書》,對 React.js 感興趣的童鞋,歡迎指點

相關文章
相關標籤/搜索