简体   繁体   English

Python 国际象棋 minimax 算法 - 如何玩黑色棋子(Bot 有白色棋子)

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

Motivation:动机:

I am trying to make a basic AI agent that can play chess against an opponent.我正在尝试制作一个可以与对手下棋的基本 AI 代理。 The goal is to see how good it can become through the use of machine learning later on and also learn a the fine details in chess that are hidden from us when we just play it, such as evaluation parameters.目标是通过稍后使用机器学习来了解它可以变得多好,并了解我们在下棋时隐藏的国际象棋细节,例如评估参数。


Code:代码:

Here is what I have so far:这是我到目前为止所拥有的:

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)

This is what the code does:这是代码的作用:

  • Open firefox terminal and go to lichess.org打开 Firefox 终端并访问 lichess.org

  • Enter the analysis mode for a starting chess position进入起始棋位的分析模式

  • Wait for human player to make a move等待人类玩家采取行动

  • Send the FEN to the python program to make that move将 FEN 发送到 python 程序以进行移动

  • Apply minimax algorithm with corresponding depth and position values to evaluate the position and decide the best move应用具有相应深度和位置值的极小极大算法来评估位置并决定最佳移动

  • Make this move in the python program在 python 程序中进行此操作

  • Get the FEN of the current position获取当前位置的FEN

  • Play the best move on the board by pasting FEN into the analysis on lichess通过将FEN粘贴到巫妖分析中来发挥棋盘上的最佳动作


Question:题:

Right now this only lets me play as the white pieces (computer algorithm works on the black pieces).现在这只能让我扮演白色棋子(计算机算法适用于黑色棋子)。 My question, although it seems basic, is how to make it so that at the start I have the choice of which side to choose?我的问题虽然看起来很基本,但如何做到这一点,以便在开始时我可以选择选择哪一边? It seems like the minimax algorithm is baised towards computer playing with the black pieces and any attempt I make to adjust this failed to work.似乎 minimax 算法是基于计算机玩黑色棋子的,我尝试调整它的任何尝试都失败了。


Output:输出:

Here is what a typical output on the console would look like while the game is going on.以下是游戏进行时控制台上的典型输出。 Nothing special happens when the game ends, I plan to include a summary of the game and outcome later on.比赛结束时没有什么特别的事情发生,我计划稍后包括比赛和结果的摘要。

游戏中的控制台输出

As can be seen, I make sure to double check that the moves are correctly registered by printing the board setup position in the console output after every move.可以看出,我确保在每次移动后通过在控制台输出中打印板设置位置来仔细检查移动是否正确注册。


Final Note:最后说明:

I am aware the evaluation metric and maybe even the efficiency of the algorithm might not be the best but these will be adjusted once all the fine details, such as the one posted in the question, are answered.我知道评估指标,甚至算法的效率可能不是最好的,但是一旦所有细节(例如问题中发布的细节)都得到回答,这些将进行调整。

I found out that the following works:我发现以下方法有效:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM