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)
这就是代码的作用:
打开 firefox 终端并转到 lichess.org
进入棋局起始位置分析模式
等待人类玩家下棋
将 FEN 发送到 python 程序以进行移动
应用具有相应深度和位置值的minimax算法来评估位置并决定最佳着法
在 python 程序中执行此操作
获取当前仓位的FEN
通过将 FEN 粘贴到对 lichess 的分析中来发挥棋盘上的最佳着法
问题:
现在这只能让我玩白色棋子(计算机算法适用于黑色棋子)。我的问题,虽然看起来很基本,但如何做到这一点,以便在开始时我可以选择选择哪一边?似乎 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
动机:
我正在尝试制作一个可以与对手下棋的基本 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)
这就是代码的作用:
打开 firefox 终端并转到 lichess.org
进入棋局起始位置分析模式
等待人类玩家下棋
将 FEN 发送到 python 程序以进行移动
应用具有相应深度和位置值的minimax算法来评估位置并决定最佳着法
在 python 程序中执行此操作
获取当前仓位的FEN
通过将 FEN 粘贴到对 lichess 的分析中来发挥棋盘上的最佳着法
问题:
现在这只能让我玩白色棋子(计算机算法适用于黑色棋子)。我的问题,虽然看起来很基本,但如何做到这一点,以便在开始时我可以选择选择哪一边?似乎 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