如何在 python-chess 中获得白棋的获胜机会

How to get the winning chances of white in python-chess

所以我开始制作一款多人国际象棋游戏。现在,因为它是一个GUI,所以我在左边有棋盘,在右边,有一个分析区。

现在,我希望我的分析区域显示用户的获胜机会。

假设有一个方形块,里面有两个矩形。现在,它们都代表了 50-50 的机会。每次移动后,我想用当前的机会更新它。那么我怎样才能得到一个呢?

我可以返回 Cp class,但它显示了机会的准确性(我猜)。

如果有人玩过chess.com,我想你应该明白我想得到什么吧:D

(1) 对于支持 wdl 信息的引擎,您可以试试这个。

"""
* Install python
* Install python-chess
    pip install chess
"""


import chess
import chess.engine


def analyze(engine_file, threads, hash_mb, fen, movetime_sec, max_depth):
    """
    Analyze position fen with the engine_file and stream the winning chances of both sides.
    The engine should support the wdl info.
    """
    engine = chess.engine.SimpleEngine.popen_uci(engine_file)

    # Set threads and hash engine options.
    engine.configure({'Threads': threads})
    engine.configure({'Hash': hash_mb})

    limit = chess.engine.Limit(time=movetime_sec, depth=max_depth)
    board = chess.Board(fen, chess960=False)
    stm = board.turn  # stm is Side To Move

    # Get engine analysis info while it is analyzing the position.
    with engine.analysis(board, limit=limit) as analysis:
        for info in analysis:
            eng_score = info.get("score")

            if eng_score is not None:
                wdl = eng_score.wdl()  # win/draw/loss info point of view is stm
                wins, draws, losses = wdl[0], wdl[1], wdl[2]

                score = wins + draws/2
                total = wins + draws + losses

                score_rate = score / total
                win_rate = wins / total
                draw_rate = draws / total
                loss_rate = losses / total
                
                white_winning_chances = win_rate if stm==chess.WHITE else loss_rate
                black_winning_chances = win_rate if stm==chess.BLACK else loss_rate

                white_score_rate = score_rate if stm==chess.WHITE else 1 - score_rate
                black_score_rate = score_rate if stm==chess.BLACK else 1 - score_rate

                # Show info.
                print(f'white_winning_chances: {100 * white_winning_chances:0.2f}%, white_score_rate: {100 * white_score_rate:0.2f}%, white_draw_rate: {100 * draw_rate:0.2f}%')
                print(f'black_winning_chances: {100 * black_winning_chances:0.2f}%, black_score_rate: {100 * black_score_rate:0.2f}%, black_draw_rate: {100 * draw_rate:0.2f}%')

    engine.quit()


def main():
    engine_file = 'F:/Chess/Engines/stockfish/sf14/sf14.exe'
    fen = 'rnbqkb1r/1p2pppp/p2p1n2/8/3NP3/2N5/PPP2PPP/R1BQKB1R w KQkq - 0 6'  # sicilian opening
    movetime_sec = 5
    max_depth = 24
    threads = 1
    hash_mb = 64
    
    analyze(engine_file, threads, hash_mb, fen, movetime_sec, max_depth)


if __name__ == "__main__":
    main()

输出:

...

white_winning_chances: 18.20%, white_score_rate: 57.25%, white_draw_rate: 78.10%
black_winning_chances: 3.70%, black_score_rate: 42.75%, black_draw_rate: 78.10%
white_winning_chances: 20.60%, white_score_rate: 58.70%, white_draw_rate: 76.20%
black_winning_chances: 3.20%, black_score_rate: 41.30%, black_draw_rate: 76.20%
white_winning_chances: 19.10%, white_score_rate: 57.80%, white_draw_rate: 77.40%
black_winning_chances: 3.50%, black_score_rate: 42.20%, black_draw_rate: 77.40%
white_winning_chances: 19.10%, white_score_rate: 57.80%, white_draw_rate: 77.40%
black_winning_chances: 3.50%, black_score_rate: 42.20%, black_draw_rate: 77.40%

(2)对于不支持wdl的引擎或者你想用分数来计算其获胜概率。

import chess
import chess.engine


MATE_SCORE = 32000


def winning_probability(cp):
    """
    Use a sigmoid or logistic function to map the engine score cp into [0 to 1] winning probability.
    Technically winning probability is like score rate because the game of chess has draw results.
    So winning probability is like (num_wins + num_draw/2)/total_games.

    cp is a score in centipawn unit.

    Ref: https://www.chessprogramming.org/Pawn_Advantage,_Win_Percentage,_and_Elo
    """
    # K is a factor to scale the winning probability, if K is low the winning probability is high.
    # Stronger engine generally has a lower K value that is if stronger engine has an advantage of
    # 75 centipawn, while a weaker engine has an advantage of 75 centipawn too, it is the stronger
    # engine that has a higher chance to win the game.
    K = 4  

    p = cp/100  # convert centipawn to pawn unit

    return 1 / (1 + 10 ** (-p/K))


def analyze(engine_file, threads, hash_mb, fen, movetime_sec, max_depth):
    """
    Analyze position fen with the engine_file and stream the score and rate.
    """
    engine = chess.engine.SimpleEngine.popen_uci(engine_file)

    # Set threads and hash engine options.
    engine.configure({'Threads': threads})
    engine.configure({'Hash': hash_mb})

    limit = chess.engine.Limit(time=movetime_sec, depth=max_depth)
    board = chess.Board(fen, chess960=False)

    # Get engine analysis info while it is analyzing the position.
    with engine.analysis(board, limit=limit) as analysis:
        for info in analysis:
            eng_score = info.get("score")

            if eng_score is not None:
                score = eng_score.relative.score(mate_score=MATE_SCORE)  # score is in centipawn with side POV(Point Of View)
                print(f'score: {score}, side to move score rate: {100 * winning_probability(score):0.2f}%')

    engine.quit()


def main():
    engine_file = 'F:/Chess/Engines/stockfish/sf14/sf14.exe'
    fen = 'rnbqkb1r/1p2pppp/p2p1n2/8/3NP3/2N5/PPP2PPP/R1BQKB1R w KQkq - 0 6'  # sicilian opening
    movetime_sec = 5
    max_depth = 24
    threads = 1
    hash_mb = 64
    
    analyze(engine_file, threads, hash_mb, fen, movetime_sec, max_depth)


if __name__ == "__main__":
    main()

输出:

score: 41, side to move score rate: 55.87%
score: 45, side to move score rate: 56.44%
score: 53, side to move score rate: 57.57%
score: 48, side to move score rate: 56.86%
score: 48, side to move score rate: 56.86%