Python 国际象棋极小极大算法-黑棋怎么玩(Bot有白棋)

Python chess minimax algorithm - How to play with black pieces (Bot has white)

动机:

我正在尝试制作一个可以与对手下棋的基本 AI 代理。目标是通过使用机器学习看看它能变得多好,并学习国际象棋中我们刚刚下棋时隐藏的精细细节,例如评估参数。


代码:

这是我目前的情况:

import chess, chess.pgn, time, math, io
import numpy as np 

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.ui import Select

piece_values = {'P': 10, 'N': 30, 'B': 30, 'R': 50, 'Q': 90, 'K': 100, 'p': -10, 'n': -30, 'b': -30, 'r': -50, 'q': -90, 'k': -100}

# These are all flipped
position_values = {
        'P' : np.array([ [0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0],
                        [5.0,  5.0,  5.0,  5.0,  5.0,  5.0,  5.0,  5.0],
                        [1.0,  1.0,  2.0,  3.0,  3.0,  2.0,  1.0,  1.0],
                        [0.5,  0.5,  1.0,  2.5,  2.5,  1.0,  0.5,  0.5],
                        [0.0,  0.0,  0.0,  2.0,  2.0,  0.0,  0.0,  0.0],
                        [0.5, -0.5, -1.0,  0.0,  0.0, -1.0, -0.5,  0.5],
                        [0.5,  1.0, 1.0,  -2.0, -2.0,  1.0,  1.0,  0.5],
                        [0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0] ]),

        'N' : np.array([[-5.0, -4.0, -3.0, -3.0, -3.0, -3.0, -4.0, -5.0],
                       [-4.0, -2.0,  0.0,  0.0,  0.0,  0.0, -2.0, -4.0],
                       [-3.0,  0.0,  1.0,  1.5,  1.5,  1.0,  0.0, -3.0],
                       [-3.0,  0.5,  1.5,  2.0,  2.0,  1.5,  0.5, -3.0],
                       [-3.0,  0.0,  1.5,  2.0,  2.0,  1.5,  0.0, -3.0],
                       [-3.0,  0.5,  1.0,  1.5,  1.5,  1.0,  0.5, -3.0],
                       [-4.0, -2.0,  0.0,  0.5,  0.5,  0.0, -2.0, -4.0],
                       [-5.0, -4.0, -3.0, -3.0, -3.0, -3.0, -4.0, -5.0] ]),

        'B' : np.array([[-2.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -2.0],
                       [-1.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0, -1.0],
                       [-1.0,  0.0,  0.5,  1.0,  1.0,  0.5,  0.0, -1.0],
                       [-1.0,  0.5,  0.5,  1.0,  1.0,  0.5,  0.5, -1.0],
                       [-1.0,  0.0,  1.0,  1.0,  1.0,  1.0,  0.0, -1.0],
                       [-1.0,  1.0,  1.0,  1.0,  1.0,  1.0,  1.0, -1.0],
                       [-1.0,  0.5,  0.0,  0.0,  0.0,  0.0,  0.5, -1.0],
                       [-2.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -2.0] ]),

        'R' : np.array([[ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,  0.0],
                       [ 0.5, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,  0.5],
                       [-0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5],
                       [-0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5],
                       [-0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5],
                       [-0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5],
                       [-0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5],
                       [ 0.0, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0,  0.0]]),

        'Q' : np.array([[-2.0, -1.0, -1.0, -0.5, -0.5, -1.0, -1.0, -2.0],
                       [-1.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0, -1.0],
                       [-1.0,  0.0,  0.5,  0.5,  0.5,  0.5,  0.0, -1.0],
                       [-0.5,  0.0,  0.5,  0.5,  0.5,  0.5,  0.0, -0.5],
                       [-0.5,  0.0,  0.5,  0.5,  0.5,  0.5,  0.0, -0.5],
                       [-1.0,  0.5,  0.5,  0.5,  0.5,  0.5,  0.0, -1.0],
                       [-1.0,  0.0,  0.5,  0.0,  0.0,  0.0,  0.0, -1.0],
                       [-2.0, -1.0, -1.0, -0.5, -0.5, -1.0, -1.0, -2.0]]),

        'K' : np.array([[ -3.0, -4.0, -4.0, -5.0, -5.0, -4.0, -4.0, -3.0],
                       [ -3.0, -4.0, -4.0, -5.0, -5.0, -4.0, -4.0, -3.0],
                       [ -3.0, -4.0, -4.0, -5.0, -5.0, -4.0, -4.0, -3.0],
                       [ -3.0, -4.0, -4.0, -5.0, -5.0, -4.0, -4.0, -3.0],
                       [ -2.0, -3.0, -3.0, -4.0, -4.0, -3.0, -3.0, -2.0],
                       [ -1.0, -2.0, -2.0, -2.0, -2.0, -2.0, -2.0, -1.0],
                       [  2.0,  2.0,  0.0,  0.0,  0.0,  0.0,  2.0,  2.0 ],
                       [  2.0,  3.0,  1.0,  0.0,  0.0,  1.0,  3.0,  2.0 ]])}

class LichessBot:
    def __init__(self, fen):
        self.fen = fen
        self.bot = webdriver.Firefox(executable_path=r'geckodriver.exe')

    def initialize(self):
        bot = self.bot
        bot.get('https://lichess.org/editor/rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR_w_KQkq_-')
        time.sleep(3)
        analysis = bot.find_element_by_css_selector(".actions > a:nth-child(2)").click()
        time.sleep(1)

    def gameSelect(self, fen):
        bot = self.bot

        fen_area = bot.find_element_by_class_name("analyse__underboard__fen")
        bot.execute_script('arguments[0].setAttribute("value", arguments[1]);', fen_area, fen)

        # Refresh the page to enter new fen number properly every time
        fen_new = bot.find_element_by_class_name("analyse__underboard__fen").get_attribute('value').replace(' ', '_')
        bot.get('https://lichess.org/analysis/standard/{}'.format(fen_new))

    def gameReturn(self):
        bot = self.bot

        fen_return = bot.find_element_by_class_name("analyse__underboard__fen").get_attribute('value')
        time.sleep(1)
        return fen_return

def positionEvaluation(position, piece_values=piece_values, position_values=position_values):
    # Position of pieces is not taken into account for their strength
    if position_values == 'None':
        total_eval = 0
        pieces = list(position.piece_map().values())

        for piece in pieces:
            total_eval += piece_values[str(piece)]

        return total_eval

    else:
        positionTotalEval = 0
        pieces = position.piece_map()

        for j in pieces:
            file = chess.square_file(j)
            rank = chess.square_rank(j)

            piece_type = str(pieces[j])
            positionArray = position_values[piece_type.upper()]

            if piece_type.isupper():
                flippedPositionArray = np.flip(positionArray, axis=0)
                positionTotalEval += piece_values[piece_type] + flippedPositionArray[rank, file]

            else:
                positionTotalEval += piece_values[piece_type] - positionArray[rank, file]

        return positionTotalEval

def minimax(position, depth, alpha, beta, maximizingPlayer, bestMove = 'h1h3'):
    if depth == 0 or position.is_game_over():
        return positionEvaluation(position, piece_values, position_values), bestMove

    if maximizingPlayer:
        maxEval = -np.inf
        for child in [str(i).replace("Move.from_uci(\'", '').replace('\')', '') for i in list(position.legal_moves)]:
            position.push(chess.Move.from_uci(child))
            eval_position = minimax(position, depth-1, alpha, beta, False)[0]
            position.pop()
            maxEval = np.maximum(maxEval, eval_position)
            alpha = np.maximum(alpha, eval_position)
            if beta <= alpha:
                break
        return maxEval

    else:
        minEval = np.inf
        minMove = np.inf
        for child in [str(i).replace("Move.from_uci(\'", '').replace('\')', '') for i in list(position.legal_moves)]:
            position.push(chess.Move.from_uci(child))
            eval_position = minimax(position, depth-1, alpha, beta, True)
            position.pop()
            minEval = np.minimum(minEval, eval_position)
            if minEval < minMove:
                minMove = minEval
                bestMin = child

            beta = np.minimum(beta, eval_position)
            if beta <= alpha:
                break

        return minEval, bestMin

# # To check evaluation
# board = chess.Board()
# print(positionEvaluation(board))
# quit()

# Initialize and set up position
lichess = LichessBot('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -')
lichess.initialize()

board = chess.Board()
fen = board.fen()
lichess.gameSelect(fen)

while not board.is_game_over():
    if board.turn == True:
        print('\n[INFO] Your Turn\n=========================')
        fen_new = fen
        while fen_new == fen:
            fen_new = lichess.gameReturn()
        board = chess.Board(fen_new)

    else:
        print('[INFO] AI\'s Turn\n')
        minimaxEval, bestMove = minimax(board, 4, -np.inf, np.inf, False)
        print("AI Evaluation: {}\nAI Best Move: {}".format(minimaxEval, bestMove))
        board.push(chess.Move.from_uci(bestMove))
        print("{}\n=========================".format(board))
        fen = board.fen()
        lichess.gameSelect(fen)

这就是代码的作用:


问题:

现在这只能让我玩白色棋子(计算机算法适用于黑色棋子)。我的问题,虽然看起来很基本,但如何做到这一点,以便在开始时我可以选择选择哪一边?似乎 minimax 算法是针对计算机玩黑色棋子的,我所做的任何调整都失败了。


输出:

这是游戏进行时控制台上的典型输出。 游戏结束时没有什么特别的事情发生,我打算稍后包括游戏摘要和结果。

可以看出,我确保在每次移动后通过在控制台输出中打印棋盘设置位置来仔细检查移动是否正确注册。


最后的注释:

我知道评估指标,甚至算法的效率可能不是最好的,但一旦所有细节(例如问题中发布的细节)得到回答,这些都会进行调整。

我发现以下有效:

def minimax(position, depth, alpha, beta, maximizingPlayer, bestMove = 'h1h3'):
    if depth == 0 or position.is_game_over():
        if (computer == "BLACK"):
            return positionEvaluation(position, piece_values, position_values), bestMove
        else:
            return -1*positionEvaluation(position, piece_values, position_values), bestMove