From efb8b4278c85e0145b29b867de03dc715456dd86 Mon Sep 17 00:00:00 2001 From: Litschi Date: Fri, 27 Mar 2026 19:31:11 +0100 Subject: [PATCH 1/2] feat: added piece_count function instead of chess.popcount(board.occupied) --- CHANGELOG.rst | 9 +++++++++ chess/__init__.py | 3 +++ chess/gaviota.py | 8 ++++---- chess/syzygy.py | 10 +++++----- test.py | 2 +- 5 files changed, 22 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 03a9555d1..212db89b1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,15 @@ Changelog for python-chess ========================== +New in unreleased (27th Mar 2026) +--------------------------------- + +Bugfixes: +* Fixed typo in README.rst. + +Changes: +* Added ``board.piece_count`` function. + New in v1.11.2 (25th Feb 2025) ------------------------------ diff --git a/chess/__init__.py b/chess/__init__.py index 7fe4cb9cc..12b249bd5 100644 --- a/chess/__init__.py +++ b/chess/__init__.py @@ -841,6 +841,9 @@ def clear_board(self) -> None: :class:`~chess.Board` also clears the move stack. """ self._clear_board() + + def piece_count(self) -> int: + return popcount(self.occupied) def pieces_mask(self, piece_type: PieceType, color: Color) -> Bitboard: if piece_type == PAWN: diff --git a/chess/gaviota.py b/chess/gaviota.py index 8beb18d4d..7152a18f0 100644 --- a/chess/gaviota.py +++ b/chess/gaviota.py @@ -1519,8 +1519,8 @@ def probe_dtm(self, board: chess.Board) -> int: raise KeyError(f"gaviota tables do not contain positions with castling rights: {board.fen()}") # Supports only up to 5 pieces. - if chess.popcount(board.occupied) > 5: - raise KeyError(f"gaviota tables support up to 5 pieces, not {chess.popcount(board.occupied)}: {board.fen()}") + if board.piece_count() > 5: + raise KeyError(f"gaviota tables support up to 5 pieces, not {board.piece_count()}: {board.fen()}") # KvK is a draw. if board.occupied == board.kings: @@ -1885,8 +1885,8 @@ def _probe_hard(self, board: chess.Board, wdl_only: bool = False) -> int: if board.castling_rights: raise KeyError(f"gaviota tables do not contain positions with castling rights: {board.fen()}") - if chess.popcount(board.occupied) > 5: - raise KeyError(f"gaviota tables support up to 5 pieces, not {chess.popcount(board.occupied)}: {board.fen()}") + if board.piece_count() > 5: + raise KeyError(f"gaviota tables support up to 5 pieces, not {board.piece_count()}: {board.fen()}") stm = ctypes.c_uint(0 if board.turn == chess.WHITE else 1) ep_square = ctypes.c_uint(board.ep_square if board.ep_square else 64) diff --git a/chess/syzygy.py b/chess/syzygy.py index 0c6b7822c..2250db5b5 100644 --- a/chess/syzygy.py +++ b/chess/syzygy.py @@ -1548,8 +1548,8 @@ def probe_wdl_table(self, board: chess.Board) -> int: try: table = typing.cast(WdlTable, self.wdl[key]) except KeyError: - if chess.popcount(board.occupied) > TBPIECES: - raise KeyError(f"syzygy tables support up to {TBPIECES} pieces, not {chess.popcount(board.occupied)}: {board.fen()}") + if board.piece_count() > TBPIECES: + raise KeyError(f"syzygy tables support up to {TBPIECES} pieces, not {board.piece_count()}: {board.fen()}") raise MissingTableError(f"did not find wdl table {key}") self._bump_lru(table) @@ -1567,8 +1567,8 @@ def probe_ab(self, board: chess.Board, alpha: int, beta: int, threats: bool = Fa # positions that have more pieces than the maximum number of supported # pieces. We artificially limit this to one additional level, to # make sure search remains somewhat bounded. - if chess.popcount(board.occupied) > TBPIECES + 1: - raise KeyError(f"syzygy tables support up to {TBPIECES} pieces, not {chess.popcount(board.occupied)}: {board.fen()}") + if board.piece_count() > TBPIECES + 1: + raise KeyError(f"syzygy tables support up to {TBPIECES} pieces, not {board.piece_count()}: {board.fen()}") # Special case: Variant with compulsory captures. if self.variant.captures_compulsory: @@ -1613,7 +1613,7 @@ def sprobe_ab(self, board: chess.Board, alpha: int, beta: int, threats: bool = F threats_found = False - if threats or chess.popcount(board.occupied) >= 6: + if threats or board.piece_count() >= 6: for threat in board.generate_legal_moves(~board.pawns): board.push(threat) try: diff --git a/test.py b/test.py index 4927a2b80..228c62f3f 100755 --- a/test.py +++ b/test.py @@ -1220,7 +1220,7 @@ def test_clear(self): self.assertFalse(board.ep_square) self.assertFalse(board.piece_at(chess.E1)) - self.assertEqual(chess.popcount(board.occupied), 0) + self.assertEqual(board.piece_count(), 0) def test_threefold_repetition(self): board = chess.Board() From a345dbd131fb5cbcbffdc9e50901d359480926c5 Mon Sep 17 00:00:00 2001 From: Niklas Fiekas Date: Fri, 3 Apr 2026 21:24:06 +0200 Subject: [PATCH 2/2] Document board.piece_count() --- CHANGELOG.rst | 9 --------- chess/__init__.py | 7 ++++++- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 212db89b1..03a9555d1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,15 +1,6 @@ Changelog for python-chess ========================== -New in unreleased (27th Mar 2026) ---------------------------------- - -Bugfixes: -* Fixed typo in README.rst. - -Changes: -* Added ``board.piece_count`` function. - New in v1.11.2 (25th Feb 2025) ------------------------------ diff --git a/chess/__init__.py b/chess/__init__.py index 12b249bd5..9ea44f36e 100644 --- a/chess/__init__.py +++ b/chess/__init__.py @@ -841,8 +841,13 @@ def clear_board(self) -> None: :class:`~chess.Board` also clears the move stack. """ self._clear_board() - + def piece_count(self) -> int: + """ + Gets the number of pieces on the board. + + Does not include Crazyhouse pockets. + """ return popcount(self.occupied) def pieces_mask(self, piece_type: PieceType, color: Color) -> Bitboard: