今天我们来教AI下国际象棋_棋子_函数
国际象棋可以说是最棒的棋盘游戏之一,它是计策战术和纯技能的完美领悟。每位玩家开局时各有 16 枚棋子:一王、一后、两车、两马、两象和八兵,各具不同功能与走法。真人对弈可以凭借玩家的履历,稳扎稳打。那么,对付一个机器——打算机,你该如何教会它下棋?近日,有人在 medium 上揭橥了一篇文章,详细阐明了如何教打算机玩国际象棋。
本文将从 5 个方面进行先容:
Board 表示;Board 评估;移动选择;测试 AI;接口测试。在开始之前,你只须要提前安装 Python3。
Board 表示
首先,你须要对棋子背后的逻辑进行编码,即为每个棋子分配每一次可能的合法移动。
python-chess 库为我们供应了棋子的移动天生和验证,简化了事情,安装办法如下:
!pipinstall python-chess
python-chess 库安装好后,导入 chess 模块并进行初始化:
import chessboard = chess.Board()board
在 notebook 中的输出如下所示:
board 工具是一个完全的 board 表示,该工具为我们供应了一些主要的函数,例如,board.is_checkmate() 函数检讨是否存在将杀(checkmate),board.push() 函数附加一个移动,board.pop() 函数撤销末了一次移动等。阅读完全的文档请参阅:https://python-chess.readthedocs.io/en/latest/
Board 评估
为了对 board 进行初步评估,必须考虑一位大师在各自比赛中的想法。
我们该当想到的一些要点是:
避免用一个小棋子换三个兵;象总是成对涌现;避免用两个小棋子换一辆车和一个兵。将上述要点以方程形式进行表达:
象 > 3 个兵 & 马 > 3 个兵;象 > 马;象 + 马 > 车 + 兵。通过化简上述方程,可以得到:象 > 马 > 3 个兵。同样,第三个方程可以改写成:象 + 马 = 车 + 1.5 个兵,由于两个小棋子相称于一个车和两个兵。
利用 piece square table 来评估棋子,在 8x8 的矩阵中设置值,例如在国际象棋中,在有利的位置设置较高的值,在不利的位置设置较低的值。
例如,白色国王超越中线的概率将小于 20%,因此我们将在该矩阵中将数值设置为负值。
再举一个例子,假设皇后希望自己被放在中间位置,由于这样可以掌握更多的位置,因此我们将在中央设置更高的值,其他棋子也一样,由于国际象棋都是为了保卫国王和掌握中央。
理论就讲这些,现在我们来初始化 piece square table:
pawntable = [ 0, 0, 0, 0, 0, 0, 0, 0, 5, 10, 10, -20, -20, 10, 10, 5, 5, -5, -10, 0, 0, -10, -5, 5, 0, 0, 0, 20, 20, 0, 0, 0, 5, 5, 10, 25, 25, 10, 5, 5, 10, 10, 20, 30, 30, 20, 10, 10, 50, 50, 50, 50, 50, 50, 50, 50, 0, 0, 0, 0, 0, 0, 0, 0]knightstable = [ -50, -40, -30, -30, -30, -30, -40, -50, -40, -20, 0, 5, 5, 0, -20, -40, -30, 5, 10, 15, 15, 10, 5, -30, -30, 0, 15, 20, 20, 15, 0, -30, -30, 5, 15, 20, 20, 15, 5, -30, -30, 0, 10, 15, 15, 10, 0, -30, -40, -20, 0, 0, 0, 0, -20, -40, -50, -40, -30, -30, -30, -30, -40, -50] bishopstable = [ -20, -10, -10, -10, -10, -10, -10, -20, -10, 5, 0, 0, 0, 0, 5, -10, -10, 10, 10, 10, 10, 10, 10, -10, -10, 0, 10, 10, 10, 10, 0, -10, -10, 5, 5, 10, 10, 5, 5, -10, -10, 0, 5, 10, 10, 5, 0, -10, -10, 0, 0, 0, 0, 0, 0, -10, -20, -10, -10, -10, -10, -10, -10, -20] rookstable = [ 0, 0, 0, 5, 5, 0, 0, 0, -5, 0, 0, 0, 0, 0, 0, -5, -5, 0, 0, 0, 0, 0, 0, -5, -5, 0, 0, 0, 0, 0, 0, -5, -5, 0, 0, 0, 0, 0, 0, -5, -5, 0, 0, 0, 0, 0, 0, -5, 5, 10, 10, 10, 10, 10, 10, 5, 0, 0, 0, 0, 0, 0, 0, 0] queenstable = [ -20, -10, -10, -5, -5, -10, -10, -20, -10, 0, 0, 0, 0, 0, 0, -10, -10, 5, 5, 5, 5, 5, 0, -10, 0, 0, 5, 5, 5, 5, 0, -5, -5, 0, 5, 5, 5, 5, 0, -5, -10, 0, 5, 5, 5, 5, 0, -10, -10, 0, 0, 0, 0, 0, 0, -10, -20, -10, -10, -5, -5, -10, -10, -20] kingstable = [ 20, 30, 10, 0, 0, 10, 30, 20, 20, 20, 0, 0, 0, 0, 20, 20, -10, -20, -20, -20, -20, -20, -20, -10, -20, -30, -30, -40, -40, -30, -30, -20, -30, -40, -40, -50, -50, -40, -40, -30, -30, -40, -40, -50, -50, -40, -40, -30, -30, -40, -40, -50, -50, -40, -40, -30, -30, -40, -40, -50, -50, -40, -40, -30]
通过以下四种方法得到评估函数:
第一步检讨游戏是否还在连续。
这个阶段的背后编码逻辑是:如果它在 checkmate 时返回 true,程序将会检讨轮到哪方移动。如果当前轮到白方移动,返回值为 - 9999,即上次一定是黑方移动,玄色得胜;否则返回值为 + 9999,表示白色得胜。对付僵局或比赛材料不敷,返回值为 0 以表示平局。
代码实现办法:
if board.is_checkmate(): if board.turn: return -9999 else: return 9999if board.is_stalemate(): return 0if board.is_insufficient_material(): return 0
第二步,打算总的棋子数,并把棋子总数通报给 material 函数。
wp = len(board.pieces(chess.PAWN, chess.WHITE))bp = len(board.pieces(chess.PAWN, chess.BLACK))wn = len(board.pieces(chess.KNIGHT, chess.WHITE))bn = len(board.pieces(chess.KNIGHT, chess.BLACK))wb = len(board.pieces(chess.BISHOP, chess.WHITE))bb = len(board.pieces(chess.BISHOP, chess.BLACK))wr = len(board.pieces(chess.ROOK, chess.WHITE))br = len(board.pieces(chess.ROOK, chess.BLACK))wq = len(board.pieces(chess.QUEEN, chess.WHITE))bq = len(board.pieces(chess.QUEEN, chess.BLACK))
第三步,打算得分。material 函数得分的打算方法是:用各种棋子的权重乘以该棋子黑白两方个数之差,然后求这些结果之和。而每种棋子的得分打算方法是:该棋子在该游戏实例中所处位置的 piece-square 值的总和。
material = 100 (wp - bp) + 320 (wn - bn) + 330 (wb - bb) + 500 (wr - br) + 900 (wq - bq)pawnsq = sum([pawntable[i] for i in board.pieces(chess.PAWN, chess.WHITE)])pawnsq = pawnsq + sum([-pawntable[chess.square_mirror(i)] for i in board.pieces(chess.PAWN, chess.BLACK)])knightsq = sum([knightstable[i] for i in board.pieces(chess.KNIGHT, chess.WHITE)])knightsq = knightsq + sum([-knightstable[chess.square_mirror(i)] for i in board.pieces(chess.KNIGHT, chess.BLACK)])bishopsq = sum([bishopstable[i] for i in board.pieces(chess.BISHOP, chess.WHITE)])bishopsq = bishopsq + sum([-bishopstable[chess.square_mirror(i)] for i in board.pieces(chess.BISHOP, chess.BLACK)])rooksq = sum([rookstable[i] for i in board.pieces(chess.ROOK, chess.WHITE)])rooksq = rooksq + sum([-rookstable[chess.square_mirror(i)] for i in board.pieces(chess.ROOK, chess.BLACK)])queensq = sum([queenstable[i] for i in board.pieces(chess.QUEEN, chess.WHITE)])queensq = queensq + sum([-queenstable[chess.square_mirror(i)] for i in board.pieces(chess.QUEEN, chess.BLACK)])kingsq = sum([kingstable[i] for i in board.pieces(chess.KING, chess.WHITE)])kingsq = kingsq + sum([-kingstable[chess.square_mirror(i)] for i in board.pieces(chess.KING, chess.BLACK)])
第四步,打算评价函数,此时将会返回白棋的 material 得分和各棋子单独得分之和。
eval = material + pawnsq + knightsq + bishopsq + rooksq + queensq + kingsqif board.turn: return evalelse: return -eval
评价函数流程图
移动选择
算法的末了一步是用 Minimax 算法中的 Negamax 实现进行移动选择,Minimax 算法是双人游戏(如跳棋等)中的常用算法。之后利用 Alpha-Beta 剪枝进行优化,这样可以减少实行的韶光。
现在让我们深入研究一下 minimax 算法。该算法被广泛运用在棋类游戏中,用来找出失落败的最大可能性中的最小值。该算法广泛运用于人工智能、决策论、博弈论、统计和哲学,力争在最坏的情形下将丢失降到最低。大略来说,在游戏的每一步,假设玩家 A 试图最大化得胜几率,而不才一步中,玩家 B 试图最小化玩家 A 得胜的几率。
为了更好地理解 minimax 算法,请看下图:
维基百科中 minimax 树举例
为了得到更好的结果,利用 minimax 变体 negamax,由于我们只须要一个最大化两位玩家效用的函数。不同点在于,一个玩家的丢失即是另一个玩家的收成,反之亦然。
就游戏而言,给第一个玩家的位置值和给第二个玩家的位置值符号是相反的。
negamax 示例
首先,我们将 alpha 设为负无穷大,beta 设为正无穷大,这样两位玩家都能以尽可能差的分数开始比赛,代码如下:
except: bestMove = chess.Move.null() bestValue = -99999 alpha = -100000 beta = 100000 for move in board.legal_moves: board.push(move) boardValue = -alphabeta(-beta, -alpha, depth - 1) if boardValue > bestValue: bestValue = boardValue bestMove = move if (boardValue > alpha): alpha = boardValue board.pop() return bestMove
下面让我们以流程图的办法来阐明:
search 函数的流程图
下一步是进行 alpha-beta 的剪枝来优化实行速率。
来自维基百科的 alpha-beta 剪枝解释
代码如下:
def alphabeta(alpha, beta, depthleft): bestscore = -9999 if (depthleft == 0): return quiesce(alpha, beta) for move in board.legal_moves: board.push(move) score = -alphabeta(-beta, -alpha, depthleft - 1) board.pop() if (score >= beta): return score if (score > bestscore): bestscore = score if (score > alpha): alpha = score return bestscore
现在,让我们用下面给出的流程图来调度 alphabeta 函数:
现在是静态搜索,这种搜索旨在仅评估静态位置,即不存在致胜战术移动的位置。该搜索须要避免由搜索算法的深度限定所引起的水平线效应(horizon effect)。
代码如下:
def quiesce(alpha, beta): stand_pat = evaluate_board() if (stand_pat >= beta): return beta if (alpha < stand_pat): alpha = stand_patfor move in board.legal_moves: if board.is_capture(move): board.push(move) score = -quiesce(-beta, -alpha) board.pop()if (score >= beta): return beta if (score > alpha): alpha = score return alpha
大略总结一下 quiesce 函数:
quiesce 函数流程图。
测试 AI
开始测试前,须要导入一些库:
测试有 3 项:
AI 对弈人类;AI 对弈 AI;AI 对弈 Stockfish。1. AI 对弈人类:
AI 选择从 g1 到 f3,这是一个很明智的选择。
2. AI 对弈 AI:
3. AI 对弈 Stockfish:
可以得出:AI 还不足智能,不敷以打败 stockfish 12,但仍旧坚持走了 20 步。
接口测试
上述测试办法看起来代码很多,你也可以写一个接口测试 AI。
然后实行:
终极输出
本文系作者个人观点,不代表本站立场,转载请注明出处!