手把手教你用 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 感兴趣的童鞋,欢迎指点

相关文章
相关标签/搜索