Skip to content

Commit 1064bf5

Browse files
committed
Allow claiming fifty-moves before final move (fixes niklasf#632)
1 parent 3be5f59 commit 1064bf5

3 files changed

Lines changed: 38 additions & 17 deletions

File tree

chess/__init__.py

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1832,18 +1832,17 @@ def has_insufficient_material(self, color: Color) -> bool:
18321832

18331833
return True
18341834

1835+
def _is_halfmoves(self, n: int) -> bool:
1836+
return self.halfmove_clock >= n and any(self.generate_legal_moves())
1837+
18351838
def is_seventyfive_moves(self) -> bool:
18361839
"""
18371840
Since the 1st of July 2014, a game is automatically drawn (without
18381841
a claim by one of the players) if the half-move clock since a capture
18391842
or pawn move is equal to or greater than 150. Other means to end a game
18401843
take precedence.
18411844
"""
1842-
if self.halfmove_clock >= 150:
1843-
if any(self.generate_legal_moves()):
1844-
return True
1845-
1846-
return False
1845+
return self._is_halfmoves(150)
18471846

18481847
def is_fivefold_repetition(self) -> bool:
18491848
"""
@@ -1856,28 +1855,44 @@ def is_fivefold_repetition(self) -> bool:
18561855

18571856
def can_claim_draw(self) -> bool:
18581857
"""
1859-
Checks if the side to move can claim a draw by the fifty-move rule or
1858+
Checks if the player to move can claim a draw by the fifty-move rule or
18601859
by threefold repetition.
18611860
18621861
Note that checking the latter can be slow.
18631862
"""
18641863
return self.can_claim_fifty_moves() or self.can_claim_threefold_repetition()
18651864

1865+
def is_fifty_moves(self) -> bool:
1866+
return self._is_halfmoves(100)
1867+
18661868
def can_claim_fifty_moves(self) -> bool:
18671869
"""
1870+
Checks if the player to move can claim a draw by the fifty-move rule.
1871+
18681872
Draw by the fifty-move rule can be claimed once the clock of halfmoves
1869-
since the last capture or pawn move becomes equal or greater to 100
1870-
and the side to move still has a legal move they can make.
1873+
since the last capture or pawn move becomes equal or greater to 100,
1874+
or if there is a legal move that achieves this. Other means of ending
1875+
the game take precedence.
18711876
"""
1872-
# Fifty-move rule.
1873-
if self.halfmove_clock >= 100:
1874-
if any(self.generate_legal_moves()):
1875-
return True
1877+
if self.is_fifty_moves():
1878+
return True
1879+
1880+
if self.halfmove_clock >= 99:
1881+
for move in self.generate_legal_moves():
1882+
if not self.is_zeroing(move):
1883+
self.push(move)
1884+
try:
1885+
if self.is_fifty_moves():
1886+
return True
1887+
finally:
1888+
self.pop()
18761889

18771890
return False
18781891

18791892
def can_claim_threefold_repetition(self) -> bool:
18801893
"""
1894+
Checks if the player to move can claim a draw by threefold repetition.
1895+
18811896
Draw by threefold repetition can be claimed if the position on the
18821897
board occured for the third time or if such a repetition is reached
18831898
with one of the possible legal moves.

chess/variant.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -700,10 +700,8 @@ def _push_capture(self, move: chess.Move, capture_square: chess.Square, piece_ty
700700
else:
701701
self.pockets[self.turn].add(piece_type)
702702

703-
def can_claim_fifty_moves(self) -> bool:
704-
return False
705-
706-
def is_seventyfive_moves(self) -> bool:
703+
def _is_halfmoves(self, n: int) -> bool:
704+
# No draw by 50-move rule or 75-move rule.
707705
return False
708706

709707
def is_irreversible(self, move: chess.Move) -> bool:

test.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1268,27 +1268,35 @@ def test_trivial_is_repetition(self):
12681268
def test_fifty_moves(self):
12691269
# Test positions from Jan Timman vs. Christopher Lutz (1995).
12701270
board = chess.Board()
1271+
self.assertFalse(board.is_fifty_moves())
12711272
self.assertFalse(board.can_claim_fifty_moves())
12721273
board = chess.Board("8/5R2/8/r2KB3/6k1/8/8/8 w - - 19 79")
1274+
self.assertFalse(board.is_fifty_moves())
12731275
self.assertFalse(board.can_claim_fifty_moves())
12741276
board = chess.Board("8/8/6r1/4B3/8/4K2k/5R2/8 b - - 68 103")
1277+
self.assertFalse(board.is_fifty_moves())
12751278
self.assertFalse(board.can_claim_fifty_moves())
12761279
board = chess.Board("6R1/7k/8/8/1r3B2/5K2/8/8 w - - 99 119")
1277-
self.assertFalse(board.can_claim_fifty_moves())
1280+
self.assertFalse(board.is_fifty_moves())
1281+
self.assertTrue(board.can_claim_fifty_moves())
12781282
board = chess.Board("8/7k/8/6R1/1r3B2/5K2/8/8 b - - 100 119")
1283+
self.assertTrue(board.is_fifty_moves())
12791284
self.assertTrue(board.can_claim_fifty_moves())
12801285
board = chess.Board("8/7k/8/1r3KR1/5B2/8/8/8 w - - 105 122")
1286+
self.assertTrue(board.is_fifty_moves())
12811287
self.assertTrue(board.can_claim_fifty_moves())
12821288

12831289
# Once checkmated, it is too late to claim.
12841290
board = chess.Board("k7/8/NKB5/8/8/8/8/8 b - - 105 176")
1291+
self.assertFalse(board.is_fifty_moves())
12851292
self.assertFalse(board.can_claim_fifty_moves())
12861293

12871294
# A stalemate is a draw, but you can not and do not need to claim it by
12881295
# the fifty-move rule.
12891296
board = chess.Board("k7/3N4/1K6/1B6/8/8/8/8 b - - 99 1")
12901297
self.assertTrue(board.is_stalemate())
12911298
self.assertTrue(board.is_game_over())
1299+
self.assertFalse(board.is_fifty_moves())
12921300
self.assertFalse(board.can_claim_fifty_moves())
12931301
self.assertFalse(board.can_claim_draw())
12941302

0 commit comments

Comments
 (0)