Coder Social home page Coder Social logo

rpdelaney-archive / python-chess-annotator Goto Github PK

View Code? Open in Web Editor NEW
58.0 58.0 29.0 218 KB

Reads chess games in PGN format and adds annotations using an engine

License: GNU General Public License v3.0

Python 100.00%
chess game python python-chess python-chess-annotator

python-chess-annotator's People

Contributors

clicketyclack avatar ddugovic avatar franklinchen avatar rpdelaney avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

python-chess-annotator's Issues

Division by zero if ply_count = 0

I experienced the case that ply_count can be zero, resulting in two separate division by zero errors. My quick fix was just to ensure that division by zero is not possible (see 9a311a0 )

Other solutions might be better or preferred.

Probable failure when analyzing an error-free game

3ec8e9f reworked needs_annotation() to take a judgment rather than a pre-computed delta of the played move evaluation vs the best move evaluation. This was done to prepare for introducing improved logic for needs_annotation() that would require access to the full judgment to operate correctly.

Previously, during the first pass chess-annotator would iterate over all the nodes in the game, setting the node.comment to the pre-computed delta, which would then be referenced in the second pass and fed into needs_annotation() to determine which moves would get deeper analysis. Since needs_annotation() now takes a dictionary, not a string, the node.comment is now set to the full judgment (L552). That way, the refactoring necessary to the second pass was minimal.

Further, if no errors are detected on the first pass, chess-annotator deeply analyzes all the moves in the game. To achieve this, after the first pass, if no errors were detected, it sets the node.comment of every move to an arbitrary high integer value, which ensures that needs_annotation() will always return true during the second pass (L603).

However, critically, this behavior was not updated to reflect the new parameters required by needs_annotation(). Therefore, a fatal condition will likely be reached when analyzing an error-free game.

To avoid this condition, when the first pass has not detected any errors, chess-annotator should set node.comment to a pre-fabricated dictionary with a big delta between the played move and the (fictional) best move to ensure that all the moves in the game are analyzed on the second pass.

Parse commandline arguments of engines

When taking an engine as argument the program should be able to parse the options given by the user. This is required by some engines. Gnuchess only starts in UCI mode with the -u (--uci) argument.

Test coverage needs improvement

In general, the test coverage is not where I would like it to be. This is partly for two reasons:

  1. It's hard to do lots of functional tests for stuff that requires engine execution, since engine operations take so long to finish, and engine behavior can be non-deterministic. (It might be fruitful to investigate whether UCI engine behavior can be mocked in the testing environment.)

  2. Overall the functions in python-chess-annotator are too long and complex (especially analyze_game()), which does not lend itself well to unit testing. (This is a bigger problem than just testing, since over-long functions make the application's behavior harder to read, harder follow during debugging, and make the project harder to maintain in general.)

I'm going to leave this issue here to prod myself to improve this situation, and also to invite help from others on building in more tests.

Input file processing should continue if an individual game cannot be parsed

Background
python-chess is generally rather lax about enforcing consistency in the objects it provides. Illegal inputs are typically accepted without raising an exception, even if the call corrupts the working board, GameNode, or Game. (My understanding is that this is for a combination of reasons, including performance and general flexibility.)

Its PGN parsing behavior follows this philosophy: if python-chess fails to parse a PGN game, it will still attempt to create and return a Game, even if that Game includes illegal headers, illegal moves, or other garbage. However, it will also attempt to store a list of parsing errors, which can be retrieved in Game.errors.

python-chess-annotator loops through the games in a PGN file. Because python-chess does not raise exceptions on PGN parsing errors, and to avoid burning up CPU time on corrupted games (which often leads to an engine crash or other nasty stuff), it performs a check for parsing errors using checkgame(). This function checks for Game.errors and raises a RuntimeError if any are found. Additionally, it checks that Game.end().parent is not None (a common corruption state even when Game.errors is None), and raises another RuntimeError if that check fails.

Note that I don't consider this an exhaustive list of methods for verifying that a Game is clean, as I don't fully understand all the ways that a Game can be dirtied up. These are just the two most common that I've found and they seem to cover the great majority of cases.

Problem
The intended behavior of python-chess-annotator is to analyze all the games in a given PGN file, returning annotated games one by one. However, if any one of the games in the file cannot be parsed, python-chess-annotator will halt with a RuntimeError when it is encountered. That may frustrate a user who sets up analysis of a large PGN file only to return later and find that the job was partially completed.

Preferably, python-chess-annotator should skip over the unparseable game and continue to the next one. Ideally, it would print some debugging info via logger.critical so that the user is notified that there is a problem with their PGN file.

Solution
I think the way to achieve this is to define a custom PgnParseError exception, and refactor checkgame() to rase that instead of a RuntimeError. Then the the try/except block in the main() function would be refactored to catch the PgnParseError, print the debugging info, and continue the loop.

ZeroDivisionError: float division by zero -- seems to be a different one from reported previously

This happened for the game downloaded from lichess, it had a proper PGN header, but the game was "1. e4 e6 0-1" -- that's it, white resigned.

An unhandled exception occurred: <class 'ZeroDivisionError'>
Traceback (most recent call last):
  File "/usr/lib/python3.6/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/usr/lib/python3.6/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/usr/local/lib/python3.6/dist-packages/annotator/__main__.py", line 811, in <module>
    main()
  File "/usr/local/lib/python3.6/dist-packages/annotator/__main__.py", line 801, in main
    raise e
  File "/usr/local/lib/python3.6/dist-packages/annotator/__main__.py", line 794, in main
    engine, args.threads)
  File "/usr/local/lib/python3.6/dist-packages/annotator/__main__.py", line 655, in analyze_game
    time_per_move = get_time_per_move(pass1_budget, ply_count)
  File "/usr/local/lib/python3.6/dist-packages/annotator/__main__.py", line 543, in get_time_per_move
    return float(pass_budget) / float(ply_count)
ZeroDivisionError: float division by zero

Close the eco json file

In the eco classification step (and also the tests for this step) we open the eco json file and then forget to close it. Oops.

Negative ACPL values

python-chess-annotator sometimes returns games where one player has negative ACPL. Anecdotally it seems to happen more often when on a tight time budget (1 minute). Cause unknown.

I considered checking for negative values and enforcing a floor of 0, but that would just be masking the bug.

Always annotate the final position

Sometimes it's not obvious why a player resigned or a draw was agreed, especially when one's elo is much lower than the players in the game one is analyzing. Also, if one uses chess-annotator to blunder check one's own games, it would be useful to know if someone resigned in a position that was actually winning or drawn, or accepted a draw in a position that was actually winning.

To achieve this, chess-annotator should add a numeric evaluation to the last node in the game regardless of whether the last played move was a mistake or not. Additionally, it would add the engine's PV in the final position as a variation to that node.

Now, it will be easy to introduce a step to add the engine's numeric evaluation of the final position to the last node in the game. But I'm not sure if it's legal PGN, as well as technically possible in python-chess, to add a variation to an end node. Some investigation and testing into that will be necessary.

Setting ACPL headers on a root node fails due to new validation in python-chess

Running the annotator now gives an exception:

$ python3 -m annotator -f Olafsson.pgn -g 0.01

An unhandled exception occurred: <class 'TypeError'>
Traceback (most recent call last):
  File "/usr/lib/python3.6/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/usr/lib/python3.6/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/home/shifty/github/cc-python-chess-annotator/annotator/__main__.py", line 759, in <module>
    main()
  File "/home/shifty/github/cc-python-chess-annotator/annotator/__main__.py", line 749, in main
    raise e
  File "/home/shifty/github/cc-python-chess-annotator/annotator/__main__.py", line 743, in main
    analyzed_game = analyze_game(game, args.gametime, args.engine, args.threads)
  File "/home/shifty/github/cc-python-chess-annotator/annotator/__main__.py", line 642, in analyze_game
    game = add_acpl(game, root_node)
  File "/home/shifty/github/cc-python-chess-annotator/annotator/__main__.py", line 490, in add_acpl
    node.root().headers["WhiteACPL"] = round(acpl(white_cpl))
  File "/home/shifty/github/cc-python-chess-annotator/lib/python3.6/site-packages/chess/pgn.py", line 532, in __setitem__
    elif "\n" in value or "\r" in value:
TypeError: argument of type 'int' is not iterable
$ 

Seems to be due to value validation niklasf/python-chess@c19acb9

Logging messages should not use string formatting

Very minor issue but I need to note this down so I don't forget to correct it later. I've just learned that while string formatting is recommended in many (most?) contexts, it is not a good idea in logging messages. Some explanation is here. The relevant pylint messages are:

  Line: 440
    pylint: logging-format-interpolation / Use % formatting in logging functions and pass the % parameters as arguments (col 20)
  Line: 443
    pylint: logging-format-interpolation / Use % formatting in logging functions and pass the % parameters as arguments (col 16)
  Line: 599
    pylint: logging-format-interpolation / Use % formatting in logging functions and pass the % parameters as arguments (col 17)
  Line: 748
    pylint: logging-format-interpolation / Use % formatting in logging functions and pass the % parameters as arguments (col 36)

Insert human readable commentary

Lichess adds human readable comments after inaccuracies (dubious you called them), mistakes and blunders, and suggests alternative move-sequences. For example the following pgn got annotated like this:

[Result "1-0"]

1. e4 e5 2. Nf3 Nc6 3. Bb5 Nd4 4. Bc4 Nf6
5. c3 Nc6 6. d3 d5 7. Bb5 dxe4 8. Nxe5 Qd5
9. Bxc6+ bxc6 10. Bf4 Bd6 11. d4 Bg4 12. c4 Qa5+
13. Qd2 Qxd2+ 14. Nxd2 Nh5 15. g3 Rb8 16. f3 exf3
17. Ndxf3 Rxb2 18. O-O c5 19. Nxg4 Nxf4 20. gxf4 Bxf4
21. Rfe1+ Kd7 22. dxc5 f6 23. Red1+ Kc6 24. Rd5 h5
25. Nd4+ Kb7 26. c6+ Kc8 27. Nf2 Be3 28. Rf1 g5
29. h3 g4 30. hxg4 hxg4 31. Rf5 g3 32. Kg2 gxf2
33. Nb5 Rg8+ 34. Kf3 Bd4 35. Nxd4 Rg1 36. Rxf2 1-0

Annotated:

[Site "https://lichess.org/sugtc1iC"]
[Round "1"]
[Result "1-0"]
[Variant "Standard"]
[TimeControl "-"]
[ECO "C61"]
[Opening "Ruy Lopez: Bird Variation"]
[Termination "Normal"]
[Annotator "lichess.org"]

1. e4 e5 2. Nf3 Nc6 3. Bb5 Nd4 { C61 Ruy Lopez: Bird Variation } 4. Bc4 Nf6 5. c3 Nc6?! { (0.08 → 0.97) Inaccuracy. Best move was Nxf3+. } (5... Nxf3+ 6. Qxf3 c6 7. O-O d5 8. exd5 cxd5 9. Bb5+ Bd7 10. Bxd7+) 
6. d3?! { (0.97 → 0.45) Inaccuracy. Best move was Ng5. } (6. Ng5 d5 7. exd5 Nxd5 8. d4 Be6 9. O-O Be7 10. Nxe6 fxe6) 6... d5?! { (0.45 → 1.08) Inaccuracy. Best move was Be7. } (6... Be7 7. O-O O-O 8. Re1 d6 9. b4 a6 10. a4 d5 11. exd5) 7. Bb5? { (1.08 → -0.73) Mistake. Best move was exd5. } (7. exd5 Nxd5 8. Qb3 Na5 9. Qa4+ Nc6 10. Nxe5 Qe7 11. Bxd5 Qxe5+ 12. Qe4 Bd6 13. d4 Qxe4+) 7... dxe4 8. Nxe5 Qd5 9. Bxc6+ bxc6 10. Bf4 Bd6 
11. d4 Bg4? { (-0.89 → 0.53) Mistake. Best move was Ba6. } (11... Ba6) 12. c4? { (0.53 → -1.02) Mistake. Best move was Nxg4. } (12. Nxg4 Bxf4 13. Nxf6+ gxf6 14. O-O f5 15. Qe2 O-O 16. Nd2 c5 17. Nb3 cxd4 18. Nxd4 c5) 12... Qa5+? { (-1.02 → 0.60) Mistake. Best move was Bxd1. } (12... Bxd1 13. cxd5 Nxd5 14. Bd2 f6 15. Nc3 Nxc3 16. Bxc3 Ba4 17. b3 Bb5 18. Nc4 Kf7 19. O-O) 13. Qd2?! { (0.60 → -0.02) Inaccuracy. Best move was Bd2. } (13. Bd2 Bxd1 14. Bxa5 Bc2 15. Nxc6 Kd7 16. Na3 Bd3 17. Nb4 Bxb4+ 18. Bxb4 c6 19. Bc3 Rhe8) 13... Qxd2+ 14. Nxd2 Nh5? { (0.04 → 1.07) Mistake. Best move was O-O. } (14... O-O 15. O-O Rfe8 16. Rae1 Bf5 17. c5 Bxe5 18. Bxe5 Nd5 19. Nc4 f6 20. Bg3 Rac8 21. Ne3) 
15. g3? { (1.07 → -0.11) Mistake. Best move was Be3. } (15. Be3 Be6) 15... Rb8? { (-0.11 → 2.74) Mistake. Best move was Nxf4. } (15... Nxf4 16. Nxg4 f5 17. Ne3 Nd3+ 18. Ke2 O-O 19. f3 c5 20. fxe4 cxd4 21. Nxf5 Nxb2 22. Nxd6) 
16. f3?? { (2.74 → -0.35) Blunder. Best move was Nxg4. } (16. Nxg4 Nxf4 17. gxf4 Rxb2 18. f5 O-O 19. Nxe4 Re8 20. f3 Bf4 21. Ne5 Bxe5 22. dxe5 Rxe5) 16... exf3? { (-0.35 → 1.93) Mistake. Best move was Nxf4. } (16... Nxf4 17. Nxg4 Ne6 18. fxe4 Nxd4 19. O-O-O O-O 20. Rhf1 Rfe8 21. Nf2 a5 22. Nd3 a4 23. Kb1) 
17. Ndxf3?? { (1.93 → -1.90) Blunder. Best move was Nxg4. } (17. Nxg4 Nxf4 18. gxf4 Rxb2 19. Nxf3 f6 20. O-O Kd7 21. Nf2 Rb4 22. Rfc1 Bxf4 23. Rc3 Ra4) 17... Rxb2?? { (-1.90 → 1.51) Blunder. Best move was Bxf3. } (17... Bxf3 18. Nxf3 Nxf4 19. gxf4 Rxb2 20. Ne5 Bxe5 21. dxe5 Ke7 22. O-O g6 23. Rab1 Rhb8 24. Rxb2) 
18. O-O? { (1.51 → -0.58) Mistake. Best move was Nxg4. } (18. Nxg4 Bxf4 19. gxf4 O-O 20. Nge5 Nxf4 21. O-O c5 22. Nd7 Re8 23. Nxc5 Ree2 24. Kh1 f6) 
18... c5?? { (-0.58 → 2.51) Blunder. Best move was Bxf3. } (18... Bxf3 19. Rxf3 Nxf4 20. gxf4 Bxe5 21. fxe5 Rf8 22. Rh3 h6 23. Rg3 Rd2 24. Rxg7 Rxd4 25. Re1) 19. Nxg4?! { (2.51 → 1.92) Inaccuracy. Best move was Bc1. } (19. Bc1 Rb4 20. Nxg4 O-O 21. dxc5 Bxc5+ 22. Be3 Bd6 23. Nd2 f5 24. Nf2 Ra4 25. Nd3 Re8) 19... Nxf4 20. gxf4 Bxf4? { (2.02 → 4.21) Mistake. Best move was O-O. } (20... O-O 21. Nge5) 21. Rfe1+? { (4.21 → 2.62) Mistake. Best move was Rae1+. } (21. Rae1+) 21... Kd7?! { (2.62 → 3.48) Inaccuracy. Best move was Kf8. } (21... Kf8) 22. dxc5?! { (3.48 → 2.92) Inaccuracy. Best move was Re4. } (22. Re4) 22... f6?! { (2.92 → 3.69) Inaccuracy. Best move was Kc8. } (22... Kc8 23. Re7 h5 24. Nge5 Be3+ 25. Kh1 Bxc5 
26. Rxf7 Bd6 27. Rxg7 Re8 28. Rg5 Bxe5 29. Nxe5) 
23. Red1+? { (3.69 → 2.45) Mistake. Best move was Re4. } (23. Re4 g5) 23... Kc6 24. Rd5? { (2.65 → 1.42) Mistake. Best move was Nd4+. } (24. Nd4+ Kd7 25. Rab1 Rxb1 26. Rxb1 h5 27. Nf2 Re8 28. Kg2 Be5 29. c6+ Kd6 30. Ne4+ Ke7) 24... h5 25. Nd4+ Kb7 26. c6+ Kc8? { (1.52 → 2.71) Mistake. Best move was Kb6. } (26... Kb6) 27. Nf2? { (2.71 → 1.41) Mistake. Best move was Rf1. } (27. Rf1 Bd6 28. Nf2 Rd8 29. Ne6 Re8 30. Rxh5 g6 31. Rh6 Rxa2 32. c5 Be7 33. Rxg6 Re2) 
27... Be3 28. Rf1 g5?? { (1.44 → 4.67) Blunder. Best move was g6. } (28... g6 29. Rd7 Re8 30. Nb5 Bb6 31. Nd4 Rxa2 32. Rd1 Bxd4 33. R1xd4 Ra1+ 34. Kg2 Ra6 35. Rg7) 29. h3? { (4.67 → 1.74) Mistake. Best move was Nf5. } (29. Nf5 Bxf2+ 30. Rxf2 Rb1+ 31. Kg2 Kb8 32. Ne7 h4 33. Rd7 f5 34. Rxf5 a6 35. Rxg5 h3+) 
29... g4? { (1.74 → 4.30) Mistake. Best move was Rd8. } (29... Rd8 30. Nf5 Rxd5 31. cxd5 Bc5 32. a4 Kd8 33. Kg2 Bb6 34. a5 Bxa5 35. Ng7 Ke7 36. Kf3) 
30. hxg4? { (4.30 → 2.67) Mistake. Best move was Nf5. } (30. Nf5 Re2 31. Nxe3 Rxe3 32. Rb1 a6 33. Ra5 Rhe8 34. hxg4 Re1+ 35. Rxe1 Rxe1+ 36. Kg2 h4) 30... hxg4?! { (2.67 → 3.55) Inaccuracy. Best move was Bxd4. } (30... Bxd4 31. Rxd4 Rb6 32. Ne4 Rxc6 33. Nxf6 hxg4 34. Rxg4 Rd6 35. Rf2 Rf8 36. Ne4 Rd1+ 37. Kg2) 31. Rf5?? { (3.55 → -5.45) Blunder. Best move was Nf5. } (31. Nf5) 31... g3 32. Kg2 gxf2?? { (-4.71 → -0.80) Blunder. Best move was Bxf2. } (32... Bxf2 33. Ne6 Rg8 34. Rb5 Rc2 35. Kf3 g2 36. Rfb1 Rg3+ 37. Ke4 Bb6 38. c5 Re2+ 39. Kf4) 
33. Nb5? { (-0.80 → -3.54) Mistake. Best move was Rd5. } (33. Rd5 Bxd4 34. Rxd4 Rxa2 35. Rf4 Rg8+ 36. Kf3 Rf8 37. Rxf2 Ra6 38. Ke3 Rxc6 39. Kd4 Kd7) 33... Rg8+? { (-3.54 → -2.28) Mistake. Best move was Rc2. } (33... Rc2 34. Kf3 Re8 35. Rxf6 a6 36. Na3 Rxa2 37. Nb1 Rc2 38. Rg6 Rxc4 39. Kg2 a5 40. Rg3) 34. Kf3 Bd4?? { (-1.89 → 3.90) Blunder. Best move was Re8. } (34... Re8) 35. Nxd4 Rg1?? { (3.29 → 6.55) Blunder. Best move was Rd8. } (35... Rd8 36. Rf4) 36. Rxf2 { Black resigns. } 1-0

Intermittent TypeError in var_end_comment() on board = node.board()

Intermittently, annotator.py will throw an exception at board = node.board() when using stockfish at very low time budgets. Example stacktrace:

Traceback (most recent call last):
  File "./annotator.py", line 640, in <module>
    main()
  File "./annotator.py", line 634, in main
    analyzed_game = analyze_game(game, args.time, args.engine)
  File "./annotator.py", line 575, in analyze_game
    add_annotation(node, judgment)
  File "./annotator.py", line 251, in add_annotation
    var_node.comment = var_end_comment(var_node, judgment["bestcomment"])
  File "./annotator.py", line 213, in var_end_comment
    board = node.board()
  File "/home/ryan/.local/lib/python3.5/site-packages/chess/pgn.py", line 116, in board
    board = self.parent.board(_cache=False)
  File "/home/ryan/.local/lib/python3.5/site-packages/chess/pgn.py", line 116, in board
    board = self.parent.board(_cache=False)
  File "/home/ryan/.local/lib/python3.5/site-packages/chess/pgn.py", line 116, in board
    board = self.parent.board(_cache=False)
  File "/home/ryan/.local/lib/python3.5/site-packages/chess/pgn.py", line 116, in board
    board = self.parent.board(_cache=False)
  File "/home/ryan/.local/lib/python3.5/site-packages/chess/pgn.py", line 116, in board
    board = self.parent.board(_cache=False)
  File "/home/ryan/.local/lib/python3.5/site-packages/chess/pgn.py", line 116, in board
    board = self.parent.board(_cache=False)
  File "/home/ryan/.local/lib/python3.5/site-packages/chess/pgn.py", line 116, in board
    board = self.parent.board(_cache=False)
  File "/home/ryan/.local/lib/python3.5/site-packages/chess/pgn.py", line 117, in board
    board.push(self.move)
  File "/home/ryan/.local/lib/python3.5/site-packages/chess/__init__.py", line 2088, in push
    self._set_piece_at(move.to_square, piece_type, self.turn)
  File "/home/ryan/.local/lib/python3.5/site-packages/chess/__init__.py", line 975, in _set_piece_at
    piece_index = (piece_type - 1) * 2
TypeError: unsupported operand type(s) for -: 'NoneType' and 'int'

The behavior is not exhibited with other engines tested (Toga II and senpai).

This bug seems to be indirectly caused by a bug in stockfish described here[1], where the UCI bestmove is sometimes different from the pv's first move. This can cause annotator.py to attempt to add illegal moves as an annotation, corrupting the working GameNode object and ultimately preventing python-chess from rendering a board on assignment.

A few questions about how this works

Hello and first of all thanks a lot for making this. I've tried several other chess analyzer tools and I managed to get none of them to work on OSX and yours is the first that works and was simple to set up.

I've tried it once and fed it a PGN file and it generated this basically

1. d4 e5 2. d5 { A40 Englund Gambit Complex Declined } 2... Bc5 3. c4 c6 4. Nc3 d6 5. e4 cxd5 6. cxd5 Nf6 7. Bb5+ Bd7 8. Bxd7+ Qxd7 9. Nf3 a6 10. O-O O-O 11. Be3 $6 { -0.62 } ( 11. Nh4 Qd8 12. Nf5 Nbd7 13. Qf3 g6 14. Bh6 Re8 15. Na4 a5 { 0.79/19 } ) 11... Bxe3 12. fxe3 b5 13. Rc1 b4 $6 { 0.71 } ( 13... Qa7 14. Nh4 { -0.63/20 } ) 14. Nb1 $6 { -0.51 } ( 14. Na4 { 0.78/20 } ) 14... Nxe4 15. a3 $6 { -1.49 } ( 15. Nbd2 Nxd2 { -0.08/20 } ) 15... bxa3 16. Nxa3 Ra7 17. Nc4 Rc7 18. Na5 $2 { -2.67 } ( 18. Qc2 f5 19. Nfd2 Nxd2 20. Qxd2 Qe7 21. Qb4 Rc5 22. Qa3 Rfc8 { -0.60/21 } ) 18... Qb5 $4 { 2.66 } ( 18... Rxc1 19. Qxc1 { -2.23/20 } ) 19. Rxc7 Qxa5 20. Qc2 Nc5 $4 { 7.04 } ( 20... Qb6 21. Kh1 { 0.87/20 } ) 21. Rb1 $4 { -5.75 } ( 21. Ng5 e4 { 7.01/22 } ) 21... Qxc7 22. b4 Qb7 $4 { -1.49 } ( 22... Ne6 23. Qc1 Rc8 24. Qxc7 Nxc7 25. Rc1 Nd7 26. Rc6 Kf8 27. Rxd6 { -5.44/23 } ) 23. bxc5 Qxd5 24. cxd6 Qxd6 25. Qe4 $6 { -3.63 } ( 25. h3 h6 { -2.42/19 } ) 25... f6 $6 { -2.09 } ( 25... Nd7 26. h3 Rc8 27. Qg4 Rc7 28. Nh4 Qd3 29. Re1 Qd2 30. Rf1 { -3.55/20 } ) 26. Qc4+ Kh8 27. Kf2 Rd8 $2 { -1.26 } ( 27... Nd7 { -3.42/20 } ) 28. Re1 $2 { -4.19 } ( 28. Rb7 a5 { -1.33/21 } ) 28... Nc6 $2 { -1.82 } ( 28... Nd7 29. Re2 { -4.01/19 } ) 29. Qxa6 Qc5 30. Qe2 $6 { -2.84 } ( 30. Qa4 Ne7 { -1.59/19 } ) 30... Rb8 $6 { -1.45 } ( 30... e4 31. Nd2 Ne5 32. Kg1 Qc2 33. Nf1 Qxe2 34. Rxe2 g6 35. h3 { -2.69/21 } ) 31. Rd1 h6 32. Ne1 $2 { -3.72 } ( 32. Qd3 Rd8 { -1.58/19 } ) 32... Qc3 33. Rd3 Qc5 34. Rd2 $2 { -3.56 } ( 34. Rd7 f5 { -2.00/19 } ) 34... Ne7 $6 { -1.97 } ( 34... e4 35. h3 { -3.18/19 } ) 35. Nd3 Qc7 36. Nb2 Nf5 37. Nc4 Qc5 38. Nb2 $2 { -2.88 } ( 38. Qd3 Ne7 { -1.19/19 } ) 38... Rb3 39. Nd1 Qc3 $4 { 12.09 } ( 39... e4 40. Rc2 { -3.82/19 } ) 40. g4 $4 { 0.38 } ( 40. Nxc3 Rb8 41. Qh5 Nd4 42. exd4 Rd8 43. Ne4 exd4 44. Qg6 f5 { 11.25/23 } ) 40... Qc5 $4 { 3.32 } ( 40... Qc8 { 0.23/19 } ) 41. gxf5 Qb4 $4 { Mate in 7 } ( 41... Rb8 42. Kg2 { 3.30/19 } ) 42. e4 $4 { 2.94 } ( 42. Rd8+ Kh7 43. Qh5 Qh4+ 44. Qxh4 Rd3 45. Rxd3 e4 46. Rd8 g5 47. Qh5 g4 48. Qg6# ) 42... Rb2 $4 { 13.23 } ( 42... Qb5 43. Qxb5 { 3.41/20 } ) 43. Rxb2 1-0

I'm trying to make sense of what I'm seeing here. I intend to consume this data programatically and not just copy/pasting it into a PGN reader

The first part I'm confused about is.. what does do exactly? It seems like it goes through the whole game and makes note of missed opportunities.. on both sides?

Is that right? What if I only want to see missed opportunities from one side's perspective.. such as from white's perspective.. is there an option for that?

Then I note that there are $2, $4 and $6

Are those suppose to represent inaccuracies, blunders.. and something else? If so.. is there a mapping somewhere that corresponds to the different possible symbols that can be used to signify something like that?

Lastly, is it possible to pass it an additional option that would return the score after every single move?

Many thanks for taking the time to educate me, I hope to use your software very soon!

Leftover gnuchess process

After calling the annotator with -e "gnuchess -u" everything seems to work, but a gnuchess process is left running and eating up all my battery.

I don't know if this is a bug with the annotator, or python-chess, or gnuchess.

How to save the result?

Hello. The process managed to start using the command: annotator -f 123.pgn -E stockfish21040618 -g 1 -t 3. In the console, 100 games were processed at a time. How to save the result?

Zero division error in ACPL calculations

Classifying the opening for non-variant chess game...
Total budget is 60.0 seconds
Pass 1 budget is 6 seconds, with 6.000000 seconds per move
Performing first pass...
r . b . k b n r
p p q p . p p p
. . n . p . . .
. . . . . . . .
. . . N P . . .
. . N . . . . .
P P P . B P P P
R . B Q K . . R
r1b1kbnr/ppqp1ppp/2n1p3/8/3NP3/2N5/PPP1BPPP/R1BQK2R b KQkq - 4 6
Played move: Be2
Best move: Be3
Best eval: 85
Best comment: 0.85
PV: 6. Be3 Nf6 7. Qf3 a6 8. O-O-O Bb4 9. Nxc6 Bxc3 10. bxc3 Qxc6 11. Bd4 Qxe4 12. Qxe4 Nxe4 13. c4 d6 14. Bxg7 Rg8 15. Bd4 e5 16. f3 exd4 17. fxe4 Bd7 18. Rxd4
Played eval: 15
Played comment: 0.15
Delta: 70
Depth: 19
Nodes: 5883684
Needs annotation: False


An unhandled exception occurred: <class 'ZeroDivisionError'>
Traceback (most recent call last):
  File "/usr/lib64/python3.7/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/usr/lib64/python3.7/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/home/ryan/src/python-chess-annotator/annotator/__main__.py", line 808, in <module>
    main()
  File "/home/ryan/src/python-chess-annotator/annotator/__main__.py", line 798, in main
    raise e
  File "/home/ryan/src/python-chess-annotator/annotator/__main__.py", line 791, in main
    engine, args.threads)
  File "/home/ryan/src/python-chess-annotator/annotator/__main__.py", line 684, in analyze_game
    game = add_acpl(game, root_node)
  File "/home/ryan/src/python-chess-annotator/annotator/__main__.py", line 522, in add_acpl
    node.root().headers["BlackACPL"] = str(round(acpl(black_cpl)))
  File "/home/ryan/src/python-chess-annotator/annotator/__main__.py", line 403, in acpl
    return sum(cpl_list) / len(cpl_list)
ZeroDivisionError: division by zero

How to install?

I'm interested in running this as a user, not hacking around on it, so I figured it would be available in python repos.

I tried:

sudo pip install python-chess-annotator

and got "No matching distribution found for chess-annotator".

Is there a way to install this with pip?

add ?!, ? and ??

Now the annotator only inserts the numeric codes. In addition it could annotate the widely used ?! (Inaccuracy), ? (Mistake) and ?? (Blunder)

#14 has an example of an annotated game that uses those symbols.

-h does not work

Starting the script with -h as argument gives the following traceback:
(I have anonymized the paths with ...)

Traceback (most recent call last):
  File ".../python-chess-annotator/annotator/__main__.py", line 759, in <module>
    main()
  File ".../python-chess-annotator/annotator/__main__.py", line 735, in main
    args = parse_args()
  File ".../python-chess-annotator/annotator/__main__.py", line 57, in parse_args
    return parser.parse_args()
  File "/usr/lib/python3.5/argparse.py", line 1726, in parse_args
    args, argv = self.parse_known_args(args, namespace)
  File "/usr/lib/python3.5/argparse.py", line 1758, in parse_known_args
    namespace, args = self._parse_known_args(args, namespace)
  File "/usr/lib/python3.5/argparse.py", line 1964, in _parse_known_args
    start_index = consume_optional(start_index)
  File "/usr/lib/python3.5/argparse.py", line 1904, in consume_optional
    take_action(action, args, option_string)
  File "/usr/lib/python3.5/argparse.py", line 1832, in take_action
    action(self, namespace, argument_values, option_string)
  File "/usr/lib/python3.5/argparse.py", line 1016, in __call__
    parser.print_help()
  File "/usr/lib/python3.5/argparse.py", line 2358, in print_help
    self._print_message(self.format_help(), file)
  File "/usr/lib/python3.5/argparse.py", line 2342, in format_help
    return formatter.format_help()
  File "/usr/lib/python3.5/argparse.py", line 278, in format_help
    help = self._root_section.format_help()
  File "/usr/lib/python3.5/argparse.py", line 208, in format_help
    func(*args)
  File "/usr/lib/python3.5/argparse.py", line 208, in format_help
    func(*args)
  File "/usr/lib/python3.5/argparse.py", line 515, in _format_action
    help_text = self._expand_help(action)
  File "/usr/lib/python3.5/argparse.py", line 602, in _expand_help
    return self._get_help_string(action) % params
ValueError: unsupported format character ')' (0x29) at index 50

the other parsing options work though.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.