niklasf / python-chess

A chess library for Python, with move generation and validation, PGN parsing and writing, Polyglot opening book reading, Gaviota tablebase probing, Syzygy tablebase probing, and UCI/XBoard engine communication
https://python-chess.readthedocs.io/en/latest/
GNU General Public License v3.0
2.42k stars 529 forks source link

Question - Adding a variation [again]. #698

Closed CoderAryanAnand closed 3 years ago

CoderAryanAnand commented 3 years ago

Hi Niklas, This time my problem is that the variation adds a move too late. Here is the code:

def get_eval(fen):
    board = chess.Board(fen)
    info = engine.analyse(board, chess.engine.Limit(0.1))
    return int(str(info["score"].white().score(mate_score=10000))) / 100

def get_best_move(fen, uci=False):
    """
    Gets best move from uci in position
    """
    board = chess.Board(fen)
    result = engine.analyse(board, chess.engine.Limit(time=0.1))
    if uci:
        return result['pv'][0]
    else:
        return board.san(result['pv'][0])

def get_best_var(fen, uci=False):
    """
    Gets the best variation from uci in position
    """
    board = chess.Board(fen)
    result = engine.analyse(board, chess.engine.Limit(time=0.1))
    if uci:
        return result['pv']
    else:
        return board.variation_san(result['pv'])

pgn = chess.pgn.read_game(open(r'D:\game.pgn'))
board = chess.Board()
for node in list(pgn.mainline()):
    move = node.move
    board.push(move)
    eva = get_eval(board.fen())  # Get evaluation of the current position
    board.pop()
    actual_best_move = get_best_move(board.fen(), uci=True)
    board.push(actual_best_move)
    actual_best_eval = get_eval(board.fen())  # Get the actual best evaluation
    board.pop()
    board.push(move)
    delta = eva - actual_best_eval
    if delta > -0.3:  # Good move, but better is possible
        node.add_line(get_best_var(board.fen(), uci=True),
                      starting_comment='Good move, but better was: ',
                      comment=str(get_eval(board.fen()))
                      )
    # ...

game.pgn:

[Event "Live Chess"]
[Site "Chess.com"]
[Date "2020.10.17"]
[Round "?"]
[White "w"]
[Black "b"]
[Result "0-1"]
[ECO "D10"]
1. d4 d5 2. c4 c6 3. e3 Nf6 4. Nc3 e6 5. Nf3 Nbd7 6. Be2 dxc4 7. Bxc4 b5 8. Bd3
Bb7 9. O-O Be7 10. a3 O-O 11. Bd2 a6 12. h3 c5 13. Ne4 Nxe4 14. dxc5 Nxd2 15.
Qxd2 Nxc5 16. Ne5 Nb3 17. Qc2 Nxa1 18. Bxh7+ Kh8 19. Rxa1 Qd5 20. Be4 Qxe4 0-1

It returns:

[Event "Live Chess"]
[Site "Chess.com"]
[Date "2020.10.17"]
[Round "?"]
[White "w"]
[Black "b"]
[Result "0-1"]
[ECO "D10"]
1. d4 d5 ( { Good move, but better was: } 1... Nf6 2. c4 e6 3. Nc3 Bb4 4. Bg5 h6 5. Bh4 c5 6. dxc5 Bxc5 7. Nf3 Nc6 8. e3 { 0.25 } ) 2. c4 ( { Good move, but better was: } 2. c4 e6 3. Nf3 Nf6 4. Nc3 dxc4 5. Qa4+ Nc6 6. Bd2 Bd7 7. Qxc4 Na5 8. Qd3 c5 9. dxc5 { 0.24 } ) 2... c6 ( { Good move, but better was: } 2... e6 3. Nc3 Be7 4. Nf3 Nf6 5. Bg5 h6 6. Bh4 dxc4 7. e3 b6 8. Bxc4 Bb7 9. Qd3 Nbd7 10. e4 Bb4 11. e5 Bxc3+ 12. bxc3 { 0.26 } ) 3. e3 ( { Good move, but better was: } 3. Nf3 Nf6 4. e3 Bf5 5. Nc3 e6 6. Nh4 Be4 7. f3 Bg6 8. Nxg6 hxg6 9. Qb3 Qc7 10. Bd2 Be7 11. O-O-O Nbd7 12. cxd5 exd5 13. Kb1 { 0.29 } ) 3... Nf6 ( { Good move, but better was: } 3... Bf5 4. Nc3 e6 5. Nf3 Nd7 6. Bd3 Bxd3 7. Qxd3 Ngf6 8. O-O Be7 9. Re1 O-O 10. e4 dxe4 11. Nxe4 Nxe4 12. Qxe4 h6 { 0.18 } ) 4. Nc3 ( { Good move, but better was: } 4. Nc3 e6 5. Nf3 Be7 6. Bd3 dxc4 7. Bxc4 c5 8. O-O cxd4 9. exd4 O-O 10. Qe2 Nc6 11. Rd1 Qc7 12. Bb3 Na5 13. Qe5 Qxe5 14. dxe5 Nxb3 { 0.4 } ) 4... e6 ( { Good move, but better was: } 4... e6 5. Nf3 Be7 6. Bd3 dxc4 7. Bxc4 c5 8. O-O cxd4 9. exd4 O-O 10. d5 exd5 11. Bxd5 Nc6 12. Re1 Nxd5 13. Nxd5 Bd6 { 0.47 } ) 5. Nf3 ( { Good move, but better was: } 5. Nf3 Be7 { 0.43 } ) 5... Nbd7 ( { Good move, but better was: } 5... Be7 6. Bd3 dxc4 7. Bxc4 c5 8. O-O cxd4 9. exd4 O-O 10. d5 exd5 11. Nxd5 Nxd5 12. Bxd5 Nc6 13. Be3 Nb4 14. Be4 f5 15. Bc2 Qxd1 16. Bxd1 Be6 { 0.38 } ) 6. Be2 ( { Good move, but better was: } 6. Bd3 dxc4 { 0.38 } ) 6... dxc4 ( { Good move, but better was: } 6... b6 7. O-O Bb7 8. b3 Rc8 9. Bb2 Bb4 10. a3 Be7 11. Qc2 O-O 12. Rac1 dxc4 13. bxc4 c5 14. d5 exd5 15. cxd5 { 0.18 } ) 7. Bxc4 ( { Good move, but better was: } 7. Bxc4 { 0.4 } ) 7... b5 ( { Good move, but better was: } 7... b5 8. Bd3 { 0.28 } ) 8. Bd3 ( { Good move, but better was: } 8. Bd3 a6 9. e4 c5 10. d5 c4 11. dxe6 fxe6 12. Bc2 Qc7 13. O-O Bb7 14. Qe2 Bd6 15. Re1 { 0.65 } ) 8... Bb7 ( { Good move, but better was: } 8... a6 9. e4 { 0.2 } ) 9. O-O ( { Good move, but better was: } 9. O-O b4 { 0.59 } ) 9... Be7 ( { Good move, but better was: } 9... a6 10. e4 { 0.46 } ) 10. a3 ( { Good move, but better was: } 10. e4 b4 { 0.43 } ) 10... O-O ( { Good move, but better was: } 10... O-O 11. Qe2 { 0.16 } ) 11. Bd2 ( { Good move, but better was: } 11. b4 a5 { 0.29 } ) 11... a6 12. h3 ( { Good move, but better was: } 12. Qe2 c5 { -0.42 } ) 12... c5 ( { Good move, but better was: } 12... c5 13. Qe2 Rc8 14. Rfd1 cxd4 15. exd4 Nb6 16. Be1 Nfd5 17. Nxd5 Qxd5 18. Bd2 Nc4 19. Bb4 Bxb4 20. axb4 h6 21. Rac1 { -0.58 } ) 13. Ne4 ( { Good move, but better was: } 13. Qe2 Rc8 14. Rfd1 cxd4 15. exd4 Nb6 16. Bc1 h6 17. Ne5 Bd6 18. Be3 Qe7 19. Rac1 Rfd8 20. Bf4 Ba8 21. Ne4 Bxe4 22. Bxe4 Nxe4 { -0.49 } ) 13... Nxe4 14. dxc5 Nxd2 ( { Good move, but better was: } 14... Nxd2 15. Nxd2 Nxc5 16. Bc2 Bf6 17. Rb1 Qd5 18. Nf3 Rfd8 19. Qxd5 Bxd5 20. Nd4 Bxd4 21. exd4 Nb3 22. Bxb3 Bxb3 23. Rfc1 Rxd4 24. Rc7 { -5.97 } ) 15. Qxd2 ( { Good move, but better was: } 15. Nxd2 Nxc5 { -6.01 } ) 15... Nxc5 16. Ne5 ( { Good move, but better was: } 16. Rad1 { -5.63 } ) 16... Nb3 17. Qc2 ( { Good move, but better was: } 17. Bxh7+ Kh8 18. Qxd8 Raxd8 19. Bg6 Nxa1 20. Nxf7+ Rxf7 21. Bxf7 Nb3 22. Bxe6 Nc5 23. Bg4 Na4 24. b3 Bxa3 25. Ra1 Bb2 26. Rxa4 bxa4 27. bxa4 Kh7 28. Bf3 Bxf3 29. gxf3 { -6.62 } ) 17... Nxa1 18. Bxh7+ ( { Good move, but better was: } 18. Rxa1 Rc8 { -7.55 } ) 18... Kh8 19. Rxa1 ( { Good move, but better was: } 19. Rxa1 Qd5 20. Nf3 Rac8 21. Qd3 f5 22. Qxd5 Bxd5 23. Bxf5 Rxf5 24. Ne1 Rf7 25. Rd1 g5 26. Nd3 Rc2 27. f3 Kg8 { -9.15 } ) 19... Qd5 ( { Good move, but better was: } 19... Qd5 20. Nf3 Rac8 21. Qd3 f5 22. Qxd5 Bxd5 23. Bxf5 Rxf5 24. Ne1 g6 25. Nd3 Kg7 26. f3 Rg5 27. e4 Bc4 28. Rc1 Rd8 { -9.71 } ) 20. Be4 ( { Good move, but better was: } 20. Nf3 Rac8 { -9.2 } ) 20... Qxe4 { Good move, but better was: } 21. Qxe4 { -10.55 } 0-1

I'm using Stockfish 12, and even if I add all the others(if delta > ...), it always evaluates the position, but adds the move too late. Example of what I mean: 1. d4[[It evaluates fom move 0 to d4 but makes the variation after d4]] d5 ( { Good move, but better was: } 1... Nf6 2. c4 e6 3. Nc3 Bb4 4. Bg5 h6 5. Bh4 c5 6. dxc5 Bxc5 7. Nf3 Nc6 8. e3 { 0.25 } ) Would you know how I could fix this (so that it adds the variation in the same position it evaluates)?

PS: If I try:

for node in list(pgn.mainline()):
    move = node.move
    board.push(move)
    eva = get_eval(board.fen())  # Get evaluation of the current position
    board.pop()
    actual_best_move = get_best_move(board.fen(), uci=True)
    board.push(actual_best_move)
    actual_best_eval = get_eval(board.fen())  # Get the actual best evaluation
    board.pop()
    delta = eva - actual_best_eval
    if delta > -0.3:  # Good move, but better is possible
        node.add_line(get_best_var(board.fen(), uci=True),
                      starting_comment='Good move, but better was: ',
                      comment=str(get_eval(board.fen()))
                      )
    # ...
    board.push(move)

It returns:

Traceback (most recent call last):
  File "<input>", line 20, in <module>
  File "C:\Users\Aryan\anaconda3\envs\Chess-Graphical-Evaluation\lib\site-packages\chess\pgn.py", line 680, in __str__
    return self.accept(StringExporter(columns=None))
  File "C:\Users\Aryan\anaconda3\envs\Chess-Graphical-Evaluation\lib\site-packages\chess\pgn.py", line 782, in accept
    self.variations[0]._accept(board, visitor)
  File "C:\Users\Aryan\anaconda3\envs\Chess-Graphical-Evaluation\lib\site-packages\chess\pgn.py", line 616, in _accept
    top.node._accept_node(parent_board, visitor)
  File "C:\Users\Aryan\anaconda3\envs\Chess-Graphical-Evaluation\lib\site-packages\chess\pgn.py", line 593, in _accept_node
    visitor.visit_move(parent_board, self.move)
  File "C:\Users\Aryan\anaconda3\envs\Chess-Graphical-Evaluation\lib\site-packages\chess\pgn.py", line 1324, in visit_move
    self.write_token(board.san(move) + " ")
  File "C:\Users\Aryan\anaconda3\envs\Chess-Graphical-Evaluation\lib\site-packages\chess\__init__.py", line 2773, in san
    return self._algebraic(move)
  File "C:\Users\Aryan\anaconda3\envs\Chess-Graphical-Evaluation\lib\site-packages\chess\__init__.py", line 2786, in _algebraic
    san = self._algebraic_and_push(move, long=long)
  File "C:\Users\Aryan\anaconda3\envs\Chess-Graphical-Evaluation\lib\site-packages\chess\__init__.py", line 2791, in _algebraic_and_push
    san = self._algebraic_without_suffix(move, long=long)
  File "C:\Users\Aryan\anaconda3\envs\Chess-Graphical-Evaluation\lib\site-packages\chess\__init__.py", line 2827, in _algebraic_without_suffix
    assert piece_type, f"san() and lan() expect move to be legal or null, but got {move} in {self.fen()}"
AssertionError: san() and lan() expect move to be legal or null, but got d7d5 in rnbqkbnr/ppp1pppp/8/3p4/3P4/8/PPP1PPPP/RNBQKBNR w KQkq - 0 2
fsmosca commented 3 years ago

@CoderAryanAnand To add variation step back by 1 node, use parent to go to the previous node.

for node in game.mainline():
    move = node.move
    board = node.board()  # The move is already pushed here.

    # Use the previous node when using mainline()
    parent_node = node.parent
    parent_board = parent_node.board()  # The move is not yet pushed.

    parent_node.add_line(get_best_var(parent_board.fen(), uci=True))

print(game)

Also quit the engine this way.

def get_best_var(fen, uci=False):
    """
    Gets the best variation from uci in position
    """
    engine = chess.engine.SimpleEngine.popen_uci(engine_file)

    board = chess.Board(fen)
    result = engine.analyse(board, chess.engine.Limit(time=0.1))

    engine.quit()

    if uci:
        return result['pv']
    else:
        return board.variation_san(result['pv'])
CoderAryanAnand commented 3 years ago

@fsmosca Thank you very much (for both answers)! It worked for me. Have a nice day!