Skip to content

Commit

Permalink
Restore previous behavior of GameNode.comment
Browse files Browse the repository at this point in the history
GameNode.comment now returns all comments as a single string with single
spaces separating the comments.

GameNode.comments (plural) returns all comments as a list of strings.
  • Loading branch information
MarkZH committed Apr 21, 2024
1 parent 4e695e2 commit 90d81da
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 53 deletions.
90 changes: 53 additions & 37 deletions chess/pgn.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,8 +290,8 @@ class GameNode(abc.ABC):
"""

@property
def comment(self) -> GameNodeComment:
return self._comment
def comment(self) -> str:
return " ".join(self._comment)

@comment.setter
def comment(self, new_comment: Union[str, list[str], GameNodeComment]) -> None:
Expand All @@ -300,11 +300,19 @@ def comment(self, new_comment: Union[str, list[str], GameNodeComment]) -> None:
else:
self._comment = GameNodeComment(new_comment)

@property
def comments(self) -> GameNodeComment:
return self._comment

@comments.setter
def comments(self, new_comment: Union[str, list[str], GameNodeComment]) -> None:
self.comment = new_comment

_starting_comment: GameNodeComment

@property
def starting_comment(self) -> GameNodeComment:
return self._starting_comment
def starting_comment(self) -> str:
return " ".join(self._starting_comment)

@starting_comment.setter
def starting_comment(self, new_comment: Union[str, list[str], GameNodeComment]) -> None:
Expand All @@ -313,6 +321,14 @@ def starting_comment(self, new_comment: Union[str, list[str], GameNodeComment])
else:
self._starting_comment = GameNodeComment(new_comment)

@property
def starting_comments(self) -> GameNodeComment:
return self._starting_comment

@starting_comments.setter
def starting_comments(self, new_comment: Union[str, list[str], GameNodeComment]) -> None:
self._starting_comment = new_comment

nags: Set[int]

def __init__(self, *, comment: Union[str, list[str]] = "") -> None:
Expand Down Expand Up @@ -539,7 +555,7 @@ def add_line(self, moves: Iterable[chess.Move], *, comment: Union[str, list[str]
starting_comment = ""

# Merge comment and NAGs.
node.comment.append(comment)
node.comments.append(comment)
node.nags.update(nags)

return node
Expand All @@ -551,7 +567,7 @@ def eval(self) -> Optional[chess.engine.PovScore]:
Complexity is `O(n)`.
"""
match = EVAL_REGEX.search(" ".join(self.comment))
match = EVAL_REGEX.search(self.comment)
if not match:
return None

Expand All @@ -577,7 +593,7 @@ def eval_depth(self) -> Optional[int]:
Complexity is `O(1)`.
"""
match = EVAL_REGEX.search(" ".join(self.comment))
match = EVAL_REGEX.search(self.comment)
return int(match.group("depth")) if match and match.group("depth") else None

def set_eval(self, score: Optional[chess.engine.PovScore], depth: Optional[int] = None) -> None:
Expand All @@ -595,15 +611,15 @@ def set_eval(self, score: Optional[chess.engine.PovScore], depth: Optional[int]
eval = f"[%eval #{score.white().mate()}{depth_suffix}]"

found = 0
for index in range(len(self.comment)):
self.comment[index], found = EVAL_REGEX.subn(_condense_affix(eval), self.comment[index], count=1)
for index in range(len(self.comments)):
self.comments[index], found = EVAL_REGEX.subn(_condense_affix(eval), self.comments[index], count=1)
if found:
break

self.comment.remove_empty()
self.comments.remove_empty()

if not found and eval:
self.comment.append(eval)
self.comments.append(eval)

def arrows(self) -> List[chess.svg.Arrow]:
"""
Expand All @@ -613,7 +629,7 @@ def arrows(self) -> List[chess.svg.Arrow]:
Returns a list of :class:`arrows <chess.svg.Arrow>`.
"""
arrows = []
for match in ARROWS_REGEX.finditer(" ".join(self.comment)):
for match in ARROWS_REGEX.finditer(self.comment):
for group in match.group("arrows").split(","):
arrows.append(chess.svg.Arrow.from_pgn(group))

Expand All @@ -635,10 +651,10 @@ def set_arrows(self, arrows: Iterable[Union[chess.svg.Arrow, Tuple[Square, Squar
pass
(csl if arrow.tail == arrow.head else cal).append(arrow.pgn()) # type: ignore

for index in range(len(self.comment)):
self.comment[index] = ARROWS_REGEX.sub(_condense_affix(""), self.comment[index])
for index in range(len(self.comments)):
self.comments[index] = ARROWS_REGEX.sub(_condense_affix(""), self.comments[index])

self.comment.remove_empty()
self.comments.remove_empty()

prefix = ""
if csl:
Expand All @@ -647,7 +663,7 @@ def set_arrows(self, arrows: Iterable[Union[chess.svg.Arrow, Tuple[Square, Squar
prefix += f"[%cal {','.join(cal)}]"

if prefix:
self.comment.insert(0, prefix)
self.comments.insert(0, prefix)

def clock(self) -> Optional[float]:
"""
Expand All @@ -657,7 +673,7 @@ def clock(self) -> Optional[float]:
Returns the player's remaining time to the next time control after this
move, in seconds.
"""
match = CLOCK_REGEX.search(" ".join(self.comment))
match = CLOCK_REGEX.search(self.comment)
if match is None:
return None
return int(match.group("hours")) * 3600 + int(match.group("minutes")) * 60 + float(match.group("seconds"))
Expand All @@ -677,15 +693,15 @@ def set_clock(self, seconds: Optional[float]) -> None:
clk = f"[%clk {hours:d}:{minutes:02d}:{seconds_part}]"

found = 0
for index in range(len(self.comment)):
self.comment[index], found = CLOCK_REGEX.subn(_condense_affix(clk), self.comment[index], count=1)
for index in range(len(self.comments)):
self.comments[index], found = CLOCK_REGEX.subn(_condense_affix(clk), self.comments[index], count=1)
if found:
break

self.comment.remove_empty()
self.comments.remove_empty()

if not found and clk:
self.comment.append(clk)
self.comments.append(clk)

def emt(self) -> Optional[float]:
"""
Expand All @@ -695,7 +711,7 @@ def emt(self) -> Optional[float]:
Returns the player's elapsed move time use for the comment of this
move, in seconds.
"""
match = EMT_REGEX.search(" ".join(self.comment))
match = EMT_REGEX.search(" ".join(self.comments))
if match is None:
return None
return int(match.group("hours")) * 3600 + int(match.group("minutes")) * 60 + float(match.group("seconds"))
Expand All @@ -715,15 +731,15 @@ def set_emt(self, seconds: Optional[float]) -> None:
emt = f"[%emt {hours:d}:{minutes:02d}:{seconds_part}]"

found = 0
for index in range(len(self.comment)):
self.comment[index], found = EMT_REGEX.subn(_condense_affix(emt), self.comment[index], count=1)
for index in range(len(self.comments)):
self.comments[index], found = EMT_REGEX.subn(_condense_affix(emt), self.comments[index], count=1)
if found:
break

self.comment.remove_empty()
self.comments.remove_empty()

if not found and emt:
self.comment.append(emt)
self.comments.append(emt)

@abc.abstractmethod
def accept(self, visitor: BaseVisitor[ResultT]) -> ResultT:
Expand Down Expand Up @@ -868,7 +884,7 @@ def end(self) -> ChildNode:

def _accept_node(self, parent_board: chess.Board, visitor: BaseVisitor[ResultT]) -> None:
if self.starting_comment:
visitor.visit_comment(self.starting_comment)
visitor.visit_comment(self.starting_comments)

visitor.visit_move(parent_board, self.move)

Expand All @@ -879,8 +895,8 @@ def _accept_node(self, parent_board: chess.Board, visitor: BaseVisitor[ResultT])
for nag in sorted(self.nags):
visitor.visit_nag(nag)

if self.comment:
visitor.visit_comment(self.comment)
if self.comments:
visitor.visit_comment(self.comments)

def _accept(self, parent_board: chess.Board, visitor: BaseVisitor[ResultT], *, sidelines: bool = True) -> None:
stack = [_AcceptFrame(self, sidelines=sidelines)]
Expand Down Expand Up @@ -1011,8 +1027,8 @@ def accept(self, visitor: BaseVisitor[ResultT]) -> ResultT:
board = self.board()
visitor.visit_board(board)

if self.comment:
visitor.visit_comment(self.comment)
if self.comments:
visitor.visit_comment(self.comments)

if self.variations:
self.variations[0]._accept(board, visitor)
Expand Down Expand Up @@ -1347,14 +1363,14 @@ def visit_comment(self, comment: GameNodeComment) -> None:
# Add as a comment for the current node if in the middle of
# a variation. Add as a comment for the game if the comment
# starts before any move.
new_comment = self.variation_stack[-1].comment + comment
new_comment.remove_empty()
self.variation_stack[-1].comment = new_comment
new_comments = self.variation_stack[-1].comments + comment
new_comments.remove_empty()
self.variation_stack[-1].comments = new_comments
else:
# Otherwise, it is a starting comment.
new_comment = self.starting_comment + comment
new_comment.remove_empty()
self.starting_comment = new_comment
new_comments = self.starting_comment + comment
new_comments.remove_empty()
self.starting_comment = new_comments

def visit_move(self, board: chess.Board, move: chess.Move) -> None:
self.variation_stack[-1] = self.variation_stack[-1].add_variation(move)
Expand Down
43 changes: 27 additions & 16 deletions test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2220,19 +2220,24 @@ def test_read_game_with_multicomment_move(self):
game = chess.pgn.read_game(pgn)
first_move = game.variation(0)
self.assertEqual(first_move.comment, "A common opening")
self.assertEqual(first_move.comments, "A common opening")
self.assertEqual(first_move.comments, ["A common opening"])
second_move = first_move.variation(0)
self.assertEqual(second_move.comment, ["A common response", "An uncommon comment"])
second_move.comment.pop()
self.assertEqual(second_move.comment, ["A common response"])
self.assertEqual(second_move.comments, ["A common response", "An uncommon comment"])
second_move.comments.pop()
self.assertEqual(second_move.comments, ["A common response"])
self.assertEqual(second_move.comments, "A common response")
self.assertEqual(second_move.comment, "A common response")
second_move.comment.append("A replaced comment")
second_move.comments.append("A replaced comment")
multiple_comments = ["A common response", "A replaced comment"]
self.assertEqual(second_move.comment, multiple_comments)
for move_comment, test_comment in zip(second_move.comment, multiple_comments):
self.assertEqual(second_move.comments, multiple_comments)
for move_comment, test_comment in zip(second_move.comments, multiple_comments):
self.assertEqual(move_comment, test_comment)
self.assertEqual(list(second_move.comment), multiple_comments)
second_move.comment.pop(0)
self.assertEqual(second_move.comment, ["A replaced comment"])
self.assertEqual(list(second_move.comments), multiple_comments)
self.assertEqual(second_move.comment, " ".join(multiple_comments))
second_move.comments.pop(0)
self.assertEqual(second_move.comments, ["A replaced comment"])
self.assertEqual(second_move.comments, "A replaced comment")
self.assertEqual(second_move.comment, "A replaced comment")

def test_comment_at_eol(self):
Expand Down Expand Up @@ -2845,22 +2850,24 @@ def test_annotations(self):
self.assertTrue(game.clock() is None)
clock = 12345
game.set_clock(clock)
self.assertEqual(game.comment, ["foo [%bar] baz", "[%clk 3:25:45]"])
self.assertEqual(game.comment, "foo [%bar] baz [%clk 3:25:45]")
self.assertEqual(game.comments, ["foo [%bar] baz", "[%clk 3:25:45]"])
self.assertEqual(game.clock(), clock)

self.assertTrue(game.eval() is None)
game.set_eval(chess.engine.PovScore(chess.engine.Cp(-80), chess.WHITE))
self.assertEqual(game.comment, ["foo [%bar] baz", "[%clk 3:25:45]", "[%eval -0.80]"])
self.assertEqual(game.comments, ["foo [%bar] baz", "[%clk 3:25:45]", "[%eval -0.80]"])
self.assertEqual(game.eval().white().score(), -80)
self.assertEqual(game.eval_depth(), None)
game.set_eval(chess.engine.PovScore(chess.engine.Mate(1), chess.WHITE), 5)
self.assertEqual(game.comment, ["foo [%bar] baz", "[%clk 3:25:45]", "[%eval #1,5]"])
self.assertEqual(game.comments, ["foo [%bar] baz", "[%clk 3:25:45]", "[%eval #1,5]"])
self.assertEqual(game.eval().white().mate(), 1)
self.assertEqual(game.eval_depth(), 5)

self.assertEqual(game.arrows(), [])
game.set_arrows([(chess.A1, chess.A1), chess.svg.Arrow(chess.A1, chess.H1, color="red"), chess.svg.Arrow(chess.B1, chess.B8)])
self.assertEqual(game.comment, ["[%csl Ga1][%cal Ra1h1,Gb1b8]", "foo [%bar] baz", "[%clk 3:25:45]", "[%eval #1,5]"])
self.assertEqual(game.comments, ["[%csl Ga1][%cal Ra1h1,Gb1b8]", "foo [%bar] baz", "[%clk 3:25:45]", "[%eval #1,5]"])
self.assertEqual(game.comment, "[%csl Ga1][%cal Ra1h1,Gb1b8] foo [%bar] baz [%clk 3:25:45] [%eval #1,5]")
arrows = game.arrows()
self.assertEqual(len(arrows), 3)
self.assertEqual(arrows[0].color, "green")
Expand All @@ -2870,17 +2877,21 @@ def test_annotations(self):
self.assertTrue(game.emt() is None)
emt = 321
game.set_emt(emt)
self.assertEqual(game.comment, ["[%csl Ga1][%cal Ra1h1,Gb1b8]", "foo [%bar] baz", "[%clk 3:25:45]", "[%eval #1,5]", "[%emt 0:05:21]"])
self.assertEqual(game.comments, ["[%csl Ga1][%cal Ra1h1,Gb1b8]", "foo [%bar] baz", "[%clk 3:25:45]", "[%eval #1,5]", "[%emt 0:05:21]"])
self.assertEqual(game.comment, "[%csl Ga1][%cal Ra1h1,Gb1b8] foo [%bar] baz [%clk 3:25:45] [%eval #1,5] [%emt 0:05:21]")
self.assertEqual(game.emt(), emt)

game.set_eval(None)
self.assertEqual(game.comment, ["[%csl Ga1][%cal Ra1h1,Gb1b8]", "foo [%bar] baz", "[%clk 3:25:45]", "[%emt 0:05:21]"])
self.assertEqual(game.comments, ["[%csl Ga1][%cal Ra1h1,Gb1b8]", "foo [%bar] baz", "[%clk 3:25:45]", "[%emt 0:05:21]"])
self.assertEqual(game.comment, "[%csl Ga1][%cal Ra1h1,Gb1b8] foo [%bar] baz [%clk 3:25:45] [%emt 0:05:21]")

game.set_emt(None)
self.assertEqual(game.comment, ["[%csl Ga1][%cal Ra1h1,Gb1b8]", "foo [%bar] baz", "[%clk 3:25:45]"])
self.assertEqual(game.comments, ["[%csl Ga1][%cal Ra1h1,Gb1b8]", "foo [%bar] baz", "[%clk 3:25:45]"])
self.assertEqual(game.comment, "[%csl Ga1][%cal Ra1h1,Gb1b8] foo [%bar] baz [%clk 3:25:45]")

game.set_clock(None)
game.set_arrows([])
self.assertEqual(game.comments, "foo [%bar] baz")
self.assertEqual(game.comment, "foo [%bar] baz")

def test_eval(self):
Expand Down

0 comments on commit 90d81da

Please sign in to comment.