Python 国际象棋引擎。 Alphabeta 函数超出深度调用错误

Python chess engine. Alphabeta function exceeding depth call error

我最近一直在做一个项目,目标是制作一个国际象棋引擎,它可以评估某个位置并下出最佳棋步。我使用 this post 作为这个项目的基础,但后来意识到这不是我想要的。 源代码:github

我的代码:

def do_move(depth):

try:
    move = chess.polyglot.MemoryMappedReader("C:/Users/Bruno/Desktop/pws/books/human.bin").weighted_choice(board).move
    move = chess.polyglot.MemoryMappedReader("C:/Users/Bruno/Desktop/pws/books/computer.bin").weighted_choice(board).move
    move = chess.polyglot.MemoryMappedReader("C:/Users/Bruno/Desktop/pws/books/pecg_book.bin").weighted_choice(board).move
    return move
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

def alphabeta(depthleft, alpha, beta):
    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

def quiesce(alpha, beta):
    stand_pat = evaluation()
    if (stand_pat >= beta):
        return beta
    if (alpha < stand_pat):
        alpha = stand_pat
    for 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 

目前,由于过度调用 do_move() 函数,代码在崩溃前可以执行大约 20 步左右的移动。

我该如何解决这个问题并让它玩完整个游戏?

示例代码基于您的 github link 并进行了修改。用户可以输入位置并让引擎搜索bestmove。注意时间以毫秒为单位。请参阅示例输出。它使用 python chess library.

代码

import time

import chess
import chess.pgn


INF = 100000
MATE_SCORE = INF-100
MAX_DEPTH = 16
MAX_TIME_MS = 120000  # 120s or 2m


# Evaluating the board
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]


def is_time_limit_reached():
    global start_time
    global timems

    return 1000 * (time.perf_counter() - start_time) >= timems


def evaluate_board(board):
    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 = 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)])

    eval = material + pawnsq + knightsq + bishopsq + rooksq + queensq + kingsq

    if board.turn:
        return eval
    return -eval


def value_to_mate(value):
    if (value < -MATE_SCORE):
        return -(INF + value) // 2
    
    elif (value > MATE_SCORE):
        return (INF - value + 1) // 2

    return 0


# Searching the best move using minimax and alphabeta algorithm with negamax implementation
def alphabeta(board, alpha, beta, depth):
    global timems
    global start_time
    global ply

    bestscore = -INF

    if is_time_limit_reached():
        return 0

    if board.can_claim_draw() or board.is_insufficient_material():
        return 0

    incheck = board.is_check()
    if incheck:
        depth += 1

    if (depth < 1):
        return quiesce(board, alpha, beta)

    legal = 0
    for move in board.legal_moves:
        board.push(move)
        ply += 1
        legal += 1

        score = -alphabeta(board, -beta, -alpha, depth - 1)
        board.pop()
        ply -= 1

        if (score >= beta):
            return score

        if (score > bestscore):
            bestscore = score

        if (score > alpha):
            alpha = score

    if legal == 0:
        if incheck:
            return -INF + ply
        else:
            return 0

    return bestscore


def quiesce(board, alpha, beta):
    if is_time_limit_reached():
        return 0

    if board.is_insufficient_material():
        return 0

    stand_pat = evaluate_board(board)
    if (stand_pat >= beta):
        return beta
    if (alpha < stand_pat):
        alpha = stand_pat

    for move in board.legal_moves:
        if board.is_capture(move):
            board.push(move)

            score = -quiesce(board, -beta, -alpha)
            board.pop()

            if (score >= beta):
                return beta

            if (score > alpha):
                alpha = score

    return alpha


def selectmove(board, depth):
    global ply 

    bestMove = chess.Move.null()
    bestValue = -INF
    alpha = -INF
    beta = INF

    for move in board.legal_moves:
        board.push(move)
        ply += 1

        boardValue = -alphabeta(board, -beta, -alpha, depth - 1)
        if boardValue > bestValue:
            bestValue = boardValue
            bestMove = move

        if (boardValue > alpha):
            alpha = boardValue

        board.pop()
        ply -= 1

        if is_time_limit_reached():
            break

    return bestMove, bestValue


if __name__ == '__main__':
    global timems
    global start_time
    global ply

    fen = chess.STARTING_FEN
    # fen = "1k6/7Q/1K6/8/8/8/8/8 w - - 0 1"  # mate in 1
    # fen = "kbK5/pp6/1P6/8/8/8/8/R7 w - - 0 1"  # mate in 2
    # fen = "rnbqkb1r/1p2pppp/p2p1n2/8/3NP3/2N5/PPP2PPP/R1BQKB1R w KQkq - 0 6"  # sicilian
    
    depth = MAX_DEPTH
    timems = MAX_TIME_MS

    board = chess.Board(fen)

    while True:
        userinput = input('> ')
        line = userinput.rstrip()

        if line == 'go':
            bestmove, bestscore = None, None
            start_time = time.perf_counter()
            ply = 0

            if not board.is_game_over():
                for d in range(1, depth+1):
                    move, score = selectmove(board, d)

                    elapse = 1000 * (time.perf_counter() - start_time)

                    if elapse >= timems:
                        break  # Don't overwrite our bestmove from the time-interrupted search.

                    bestmove = move
                    bestscore = score

                    if abs(bestscore) > MATE_SCORE:
                        mate = value_to_mate(bestscore)
                        print(f'info depth {d} score mate {mate} time {int(elapse)} pv {bestmove}')  # time is in millisec
                    else:
                        print(f'info depth {d} score {bestscore} time {int(elapse)} pv {bestmove}')  # time is in millisec

                board.push(bestmove)  # update board with the bestmove
                print(f'info time {int(1000*(time.perf_counter() - start_time))}')
                print(f'bestmove {bestmove}')
            else:
                print('Game is over!')

        elif line == 'new':
            board = chess.Board()
            depth = MAX_DEPTH
            timems = MAX_TIME_MS
            print(board)
            print(f'max depth: {depth}, max time ms: {timems}')

        elif 'position fen ' in line:
            fen = ' '.join(line.split()[2:7])
            board = chess.Board(fen)

        elif line == 'board':
            print(board)

        elif line == 'fen':
            print(board.fen())

        elif line == 'epd':
            print(board.epd())

        elif 'time' in line:
            timems = int(line.split()[1])

        elif 'depth' in line:
            depth = int(line.split()[1])

        elif line == 'game':
            game = chess.pgn.Game()
            print(game.from_board(board))

        elif line == 'quit':
            break

        else:
            # assume user has entered a move
            try:
                board.push(chess.Move.from_uci(line))
            except ValueError:
                print(f'illegal move {line}')


输出

正在玩游戏

> new
r n b q k b n r
p p p p p p p p
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
P P P P P P P P
R N B Q K B N R
max depth: 16, max time ms: 120000
> time 5000
> e2e4
> go
info depth 1 score 10 time 18 pv g8f6
info depth 2 score -40 time 140 pv g8f6
info depth 3 score 10 time 973 pv g8f6
info time 5001
bestmove g8f6
> board
r n b q k b . r
p p p p p p p p
. . . . . n . .
. . . . . . . .
. . . . P . . .
. . . . . . . .
P P P P . P P P
R N B Q K B N R
> b1c3
> go
info depth 1 score 10 time 19 pv b8c6
info depth 2 score -20 time 421 pv d7d5
info depth 3 score 10 time 2865 pv d7d5
info time 5000
bestmove d7d5
> board
r n b q k b . r
p p p . p p p p
. . . . . n . .
. . . p . . . .
. . . . P . . .
. . N . . . . .
P P P P . P P P
R . B Q K B N R
> game
[Event "?"]
[Site "?"]
[Date "????.??.??"]
[Round "?"]
[White "?"]
[Black "?"]
[Result "*"]

1. e4 Nf6 2. Nc3 d5 *
> quit

设置位置

> position fen kbK5/pp6/1P6/8/8/8/8/R7 w - - 0 1
> board
k b K . . . . .
p p . . . . . .
. P . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
R . . . . . . .
> time 5000
> go
info depth 1 score 10 time 10 pv a1a5
info depth 2 score 10 time 92 pv a1a5
info depth 3 score mate 2 time 362 pv a1a6
info depth 4 score mate 2 time 2151 pv a1a6
info time 5001
bestmove a1a6
> board
k b K . . . . .
p p . . . . . .
R P . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
> go
info depth 1 score 480 time 5 pv b7a6
info depth 2 score mate -1 time 32 pv b8c7
info depth 3 score mate -1 time 184 pv b8c7
info depth 4 score mate -1 time 1314 pv b8c7
info time 5000
bestmove b8c7
> go
info depth 1 score mate 1 time 4 pv a6a7
info depth 2 score mate 1 time 37 pv a6a7
info depth 3 score mate 1 time 143 pv a6a7
info depth 4 score mate 1 time 878 pv a6a7
info depth 5 score mate 1 time 4305 pv a6a7
info time 5001
bestmove a6a7
> go
Game is over!
>