Closed ghost closed 4 years ago
I think it is a good idea but also there should be a way to avoid the processing of the comments (I don't know if it is already implemented in python-chess) If you just want the moves of the game, this new feature will increase the processing time. For the headers, I don't thing it is a big issue.
GameModelCreator already stores pgn comments in game nodes. See https://github.com/niklasf/python-chess/blob/master/chess/pgn.py#L118 One solution can be to add some specific methods to GameNode class. They can return parsed node comment values for %clk, %emt. %eval etc.
Another solution can be a new visitor, something like in this ExtPgnVisitor.py :
import io
import re
import chess.pgn
COMMENT_COMMAND_REGEX = re.compile(r"\[%(\w+)\s+([-+/,.:#\"\w\s]+)\]")
class ExtPgnVisitor(chess.pgn.GameModelCreator):
def begin_game(self):
chess.pgn.GameModelCreator.begin_game(self)
self.command_comments = {}
def end_game(self):
print(self.game.headers)
for command in self.command_comments:
print("---", command)
for movecount, san, text in self.command_comments[command]:
print(movecount, san, text)
def visit_comment(self, comment):
chess.pgn.GameModelCreator.visit_comment(self, comment)
node = self.variation_stack[-1]
board = node.board()
for match in re.finditer(COMMENT_COMMAND_REGEX, comment):
movecount = board.fullmove_number - 1 if board.turn else board.fullmove_number
command, text = match.groups()
if command not in self.command_comments:
self.command_comments[command] = []
self.command_comments[command].append((movecount, node.san(), text))
PGN = """
[Event "Rated Racing Kings game"]
[Site "https://lichess.org/4XkV5mmo"]
[Date "2018.12.06"]
[Round "-"]
[White "PyChessBot"]
[Black "FalconPower"]
[Result "0-1"]
[UTCDate "2018.12.06"]
[UTCTime "01:32:46"]
[WhiteElo "2051"]
[BlackElo "2265"]
[WhiteRatingDiff "-5"]
[BlackRatingDiff "+3"]
[WhiteTitle "BOT"]
[Variant "Racing Kings"]
[TimeControl "180+0"]
[ECO "?"]
[Opening "?"]
[Termination "Normal"]
[FEN "8/8/8/8/8/8/krbnNBRK/qrbnNBRQ w - - 0 1"]
[SetUp "1"]
[Annotator "lichess.org"]
1. Kg3 { [%eval 0.49] [%clk 0:03:00] } 1... Kb3 { [%eval 0.0] [%clk 0:03:00] }
2. Kf4 { [%eval 0.0] [%clk 0:03:00] } 2... Kc4 { [%eval 0.0] [%clk 0:02:59] }
3. Ke5 { [%eval 0.0] [%clk 0:03:00] } 3... Qa6 { [%eval 0.0] [%clk 0:02:58] }
4. Nxc2 { [%eval 0.0] [%clk 0:03:00] } 4... Kb5 { [%eval 0.0] [%clk 0:02:57] }
5. Rg6 { [%eval 0.0] [%clk 0:03:00] } 5... Nxf2 { [%eval 0.0] [%clk 0:02:56] }
6. Re6 { [%eval 0.0] [%clk 0:03:00] } 6... Nde4 { [%eval 0.0] [%clk 0:02:55] }
7. Kf5?? { (0.00 → -6.16) Blunder. Best move was Bg2. } { [%eval -6.16] [%clk
0:03:00] } (7. Bg2 Rxc2 8. Bxe4 Nxe4 9. Qxe4 Rc6 10. Kf6 Kb6 11. Kf7 Ka7) 7...
Rxc2?? { (-6.16 → 0.00) Blunder. Best move was Nxh1. } { [%eval 0.0] [%clk
0:02:35] } (7... Nxh1 8. Kxe4 Rxc2 9. Ke5 Bg5 10. Rxg5 Rxf1 11. Rf5 Rf2 12.
Rxf2 Nxf2 13. Kf6 Rc7) 8. Qh7 { [%eval 0.0] [%clk 0:03:00] } 8... Qb7 { [%eval
0.0] [%clk 0:02:28] } 9. Qf7 { [%eval 0.0] [%clk 0:03:00] } 9... Nc5 { [%eval
0.0] [%clk 0:02:20] } 10. Kg6?? { (0.00 → -18.40) Blunder. Best move was Re7. }
{ [%eval -18.4] [%clk 0:02:57] } (10. Re7 Qxe7 11. Qxe7 Ka6 12. Kf6 Rb8 13. Rg8
Rxg8 14. Qf8 Ka7 15. Qxg8 Rb2 16. Kg7 Rb8) 10... Nxe6 { [%eval -18.32] [%clk
0:02:14] } 11. Kh7 { [%eval -16.33] [%clk 0:02:55] } 11... Rc8 { [%eval -16.85]
[%clk 0:02:10] } 12. Rg8 { [%eval -15.18] [%clk 0:02:53] } 12... Bb2 { [%eval
-12.6] [%clk 0:02:04] } 13. Rxc8 { [%eval -32.23] [%clk 0:02:49] } 13... Qxc8 {
[%eval -20.08] [%clk 0:01:59] } 14. Bg2 { [%eval -29.38] [%clk 0:02:47] } 14...
Kb6 { [%eval -28.14] [%clk 0:01:47] } 15. Qd7 { [%eval -27.36] [%clk 0:02:43] }
15... Qf8 { [%eval -24.77] [%clk 0:01:41] } 16. Ng3?! { (-24.77 → Mate in 8)
Checkmate is now unavoidable. Best move was Qe7. } { [%eval #-8] [%clk 0:02:40]
} (16. Qe7 Qb8) 16... Nc7 { [%eval #-7] [%clk 0:01:34] } 17. Qe7 { [%eval #-7]
[%clk 0:02:35] } 17... Qb8 { [%eval #-6] [%clk 0:01:29] } 18. Nh1 { [%eval #-5]
[%clk 0:02:31] } 18... Ka7 { [%eval #-4] [%clk 0:01:24] } 19. Nxf2 { [%eval #-4]
[%clk 0:02:27] } 19... Qc8 { [%eval #-3] [%clk 0:01:15] } 20. Qb4 { [%eval #-3]
[%clk 0:02:21] } 20... Bd4 { [%eval #-2] [%clk 0:01:14] } 21. Nd1 { [%eval
#-2] [%clk 0:02:16] } 21... Bb6 { [%eval #-1] [%clk 0:01:12] } 22. Bd5 {
[%eval #-1] [%clk 0:02:16] } 22... Kb8# { [%clk 0:01:10] } { Game ends by
variant rule. } 0-1
"""
game = chess.pgn.read_game(io.StringIO(PGN), Visitor=ExtPgnVisitor)
tamas@tami:~$ python3 ./ExtPgnVisitor.py
Headers(Event='Rated Racing Kings game', Site='https://lichess.org/4XkV5mmo', Date='2018.12.06', Round='-', White='PyChessBot', Black='FalconPower', Result='0-1', Annotator='lichess.org', BlackElo='2265', BlackRatingDiff='+3', ECO='?', FEN='8/8/8/8/8/8/krbnNBRK/qrbnNBRQ w - - 0 1', Opening='?', SetUp='1', Termination='Normal', TimeControl='180+0', UTCDate='2018.12.06', UTCTime='01:32:46', Variant='Racing Kings', WhiteElo='2051', WhiteRatingDiff='-5', WhiteTitle='BOT')
--- eval
1 Kg3 0.49
1 Kb3 0.0
2 Kf4 0.0
2 Kc4 0.0
3 Ke5 0.0
3 Qa6 0.0
4 Nxc2 0.0
4 Kb5 0.0
5 Rg6 0.0
5 Nxf2 0.0
6 Re6 0.0
6 Nde4 0.0
7 Kf5 -6.16
7 Rxc2 0.0
8 Qh7 0.0
8 Qb7 0.0
9 Qf7 0.0
9 Nc5 0.0
10 Kg6 -18.4
10 Nxe6 -18.32
11 Kh7 -16.33
11 Rc8 -16.85
12 Rg8 -15.18
12 Bb2 -12.6
13 Rxc8 -32.23
13 Qxc8 -20.08
14 Bg2 -29.38
14 Kb6 -28.14
15 Qd7 -27.36
15 Qf8 -24.77
16 Ng3 #-8
16 Nc7 #-7
17 Qe7 #-7
17 Qb8 #-6
18 Nh1 #-5
18 Ka7 #-4
19 Nxf2 #-4
19 Qc8 #-3
20 Qb4 #-3
20 Bd4 #-2
21 Nd1 #-2
21 Bb6 #-1
22 Bd5 #-1
--- clk
1 Kg3 0:03:00
1 Kb3 0:03:00
2 Kf4 0:03:00
2 Kc4 0:02:59
3 Ke5 0:03:00
3 Qa6 0:02:58
4 Nxc2 0:03:00
4 Kb5 0:02:57
5 Rg6 0:03:00
5 Nxf2 0:02:56
6 Re6 0:03:00
6 Nde4 0:02:55
7 Kf5 0:03:00
7 Rxc2 0:02:35
8 Qh7 0:03:00
8 Qb7 0:02:28
9 Qf7 0:03:00
9 Nc5 0:02:20
10 Kg6 0:02:57
10 Nxe6 0:02:14
11 Kh7 0:02:55
11 Rc8 0:02:10
12 Rg8 0:02:53
12 Bb2 0:02:04
13 Rxc8 0:02:49
13 Qxc8 0:01:59
14 Bg2 0:02:47
14 Kb6 0:01:47
15 Qd7 0:02:43
15 Qf8 0:01:41
16 Ng3 0:02:40
16 Nc7 0:01:34
17 Qe7 0:02:35
17 Qb8 0:01:29
18 Nh1 0:02:31
18 Ka7 0:01:24
19 Nxf2 0:02:27
19 Qc8 0:01:15
20 Qb4 0:02:21
20 Bd4 0:01:14
21 Nd1 0:02:16
21 Bb6 0:01:12
22 Bd5 0:02:16
22 Kb8# 0:01:10
tamas@tami:~$
Thank you @gbtami . This could be a minor issue that I am overlooking, When I run the above code I get the error ( AttributeError: module 'chess.pgn' has no attribute 'GameModelCreator') https://pastebin.com/M7sjSQ6V
The name changed over time. It is GameBuilder now. See https://github.com/niklasf/python-chess/blob/master/chess/pgn.py#L808
Thank you @gbtami , Updating the GameModelCreator with GameBuilder works like a charm.
I've written a simple method for the GameNode class that parses a node's comment string and returns the clock string if one is present. Mind if I submit a pull request?
@jesseadamrandallhester Yes, please, if only to explore the design space.
@niklasf I went ahead and submitted the PR.
@niklasf My original commit failed the Travis CI check because I used try/catch as control flow in a way that it didn't like (and maybe wasn't a good idea). I tweaked the method and it passes now. It may still not be what we want, for whatever reason. If we want to use a different approach, I have another idea, which is to just extract all enhanced-PGN commands from a node's comment and return them in a dict. The method I wrote strictly handles timestamps.
Merged #583 and continued from there.
annotation | getter | setter | type |
---|---|---|---|
%clk |
GameNode.clock() |
GameNode.set_clock() |
float |
%eval |
GameNode.eval() |
GameNode.set_eval() |
chess.engine.PovScore |
%csl , %cal |
GameNode.arrows() |
GameNode.set_arrows() |
List[chess.svg.Arrow] |
TODO:
It might be useful to support this PGN extension. It might be not. I'd like to know what's your opinion on this.