Skip to content

Commit c471450

Browse files
committed
Introduce Board._effective_promoted()
1 parent 76cbe98 commit c471450

File tree

2 files changed

+41
-41
lines changed

2 files changed

+41
-41
lines changed

chess/__init__.py

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -907,16 +907,19 @@ def color_at(self, square: Square) -> Optional[Color]:
907907
else:
908908
return None
909909

910+
def _effective_promoted(self) -> Bitboard:
911+
return BB_EMPTY
912+
910913
def king(self, color: Color) -> Optional[Square]:
911914
"""
912-
Finds the king square of the given side. Returns ``None`` if there
913-
is no king of that color.
915+
Finds the unique king square of the given side. Returns ``None`` if
916+
there is no king or multiple kings of that color.
914917
915918
In variants with king promotions, only non-promoted kings are
916919
considered.
917920
"""
918-
king_mask = self.occupied_co[color] & self.kings & ~self.promoted
919-
return msb(king_mask) if king_mask and popcount(king_mask) == 1 else None
921+
king_mask = self.occupied_co[color] & self.kings & ~self._effective_promoted()
922+
return msb(king_mask) if king_mask and not king_mask & (king_mask - 1) else None
920923

921924
def attacks_mask(self, square: Square) -> Bitboard:
922925
bb_square = BB_SQUARES[square]
@@ -1135,7 +1138,7 @@ def set_piece_at(self, square: Square, piece: Optional[Piece], promoted: bool =
11351138
else:
11361139
self._set_piece_at(square, piece.piece_type, piece.color, promoted)
11371140

1138-
def board_fen(self, *, promoted: Optional[bool] = False) -> str:
1141+
def board_fen(self, *, promoted: Optional[bool] = None) -> str:
11391142
"""
11401143
Gets the board FEN (e.g.,
11411144
``rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR``).
@@ -1153,7 +1156,14 @@ def board_fen(self, *, promoted: Optional[bool] = False) -> str:
11531156
builder.append(str(empty))
11541157
empty = 0
11551158
builder.append(piece.symbol())
1156-
if promoted and BB_SQUARES[square] & self.promoted:
1159+
1160+
if promoted is None:
1161+
promoted_mask = self._effective_promoted()
1162+
elif promoted:
1163+
promoted_mask = self.promoted
1164+
else:
1165+
promoted_mask = BB_EMPTY
1166+
if BB_SQUARES[square] & promoted_mask:
11571167
builder.append("~")
11581168

11591169
if BB_SQUARES[square] & BB_FILE_H:
@@ -1335,7 +1345,7 @@ def chess960_pos(self) -> Optional[int]:
13351345
return None
13361346
if self.pawns != BB_RANK_2 | BB_RANK_7:
13371347
return None
1338-
if self.promoted:
1348+
if self._effective_promoted():
13391349
return None
13401350

13411351
# Piece counts.
@@ -2452,12 +2462,12 @@ def push(self, move: Move) -> None:
24522462

24532463
# Update castling rights.
24542464
self.castling_rights &= ~to_bb & ~from_bb
2455-
if piece_type == KING and not promoted:
2465+
if piece_type == KING and not self._effective_promoted() & from_bb:
24562466
if self.turn == WHITE:
24572467
self.castling_rights &= ~BB_RANK_1
24582468
else:
24592469
self.castling_rights &= ~BB_RANK_8
2460-
elif captured_piece_type == KING and not self.promoted & to_bb:
2470+
elif captured_piece_type == KING and not self._effective_promoted() & to_bb:
24612471
if self.turn == WHITE and square_rank(move.to_square) == RANK_8:
24622472
self.castling_rights &= ~BB_RANK_8
24632473
elif self.turn == BLACK and square_rank(move.to_square) == RANK_1:
@@ -3404,8 +3414,8 @@ def _reduces_castling_rights(self, move: Move) -> bool:
34043414
cr = self.clean_castling_rights()
34053415
touched = BB_SQUARES[move.from_square] ^ BB_SQUARES[move.to_square]
34063416
return bool(touched & cr or
3407-
cr & BB_RANK_1 and touched & self.kings & self.occupied_co[WHITE] & ~self.promoted or
3408-
cr & BB_RANK_8 and touched & self.kings & self.occupied_co[BLACK] & ~self.promoted)
3417+
cr & BB_RANK_1 and touched & self.kings & self.occupied_co[WHITE] & ~self._effective_promoted() or
3418+
cr & BB_RANK_8 and touched & self.kings & self.occupied_co[BLACK] & ~self._effective_promoted())
34093419

34103420
def is_irreversible(self, move: Move) -> bool:
34113421
"""
@@ -3459,16 +3469,16 @@ def clean_castling_rights(self) -> Bitboard:
34593469
black_castling &= (BB_A8 | BB_H8)
34603470

34613471
# The kings must be on e1 or e8.
3462-
if not self.occupied_co[WHITE] & self.kings & ~self.promoted & BB_E1:
3472+
if not self.occupied_co[WHITE] & self.kings & ~self._effective_promoted() & BB_E1:
34633473
white_castling = 0
3464-
if not self.occupied_co[BLACK] & self.kings & ~self.promoted & BB_E8:
3474+
if not self.occupied_co[BLACK] & self.kings & ~self._effective_promoted() & BB_E8:
34653475
black_castling = 0
34663476

34673477
return white_castling | black_castling
34683478
else:
34693479
# The kings must be on the back rank.
3470-
white_king_mask = self.occupied_co[WHITE] & self.kings & BB_RANK_1 & ~self.promoted
3471-
black_king_mask = self.occupied_co[BLACK] & self.kings & BB_RANK_8 & ~self.promoted
3480+
white_king_mask = self.occupied_co[WHITE] & self.kings & BB_RANK_1 & ~self._effective_promoted()
3481+
black_king_mask = self.occupied_co[BLACK] & self.kings & BB_RANK_8 & ~self._effective_promoted()
34723482
if not white_king_mask:
34733483
white_castling = 0
34743484
if not black_king_mask:
@@ -3506,7 +3516,7 @@ def has_kingside_castling_rights(self, color: Color) -> bool:
35063516
castling rights.
35073517
"""
35083518
backrank = BB_RANK_1 if color == WHITE else BB_RANK_8
3509-
king_mask = self.kings & self.occupied_co[color] & backrank & ~self.promoted
3519+
king_mask = self.kings & self.occupied_co[color] & backrank & ~self._effective_promoted()
35103520
if not king_mask:
35113521
return False
35123522

@@ -3527,7 +3537,7 @@ def has_queenside_castling_rights(self, color: Color) -> bool:
35273537
castling rights.
35283538
"""
35293539
backrank = BB_RANK_1 if color == WHITE else BB_RANK_8
3530-
king_mask = self.kings & self.occupied_co[color] & backrank & ~self.promoted
3540+
king_mask = self.kings & self.occupied_co[color] & backrank & ~self._effective_promoted()
35313541
if not king_mask:
35323542
return False
35333543

@@ -3600,11 +3610,11 @@ def status(self) -> Status:
36003610
errors |= STATUS_EMPTY
36013611

36023612
# There must be exactly one king of each color.
3603-
if not self.occupied_co[WHITE] & self.kings:
3613+
if not self.occupied_co[WHITE] & self.kings & ~self._effective_promoted():
36043614
errors |= STATUS_NO_WHITE_KING
3605-
if not self.occupied_co[BLACK] & self.kings:
3615+
if not self.occupied_co[BLACK] & self.kings & ~self._effective_promoted():
36063616
errors |= STATUS_NO_BLACK_KING
3607-
if popcount(self.occupied & self.kings) > 2:
3617+
if popcount(self.occupied & self.kings & ~self._effective_promoted()) > 2:
36083618
errors |= STATUS_TOO_MANY_KINGS
36093619

36103620
# There can not be more than 16 pieces of any color.
@@ -3638,7 +3648,7 @@ def status(self) -> Status:
36383648

36393649
# More than the maximum number of possible checkers in the variant.
36403650
checkers = self.checkers_mask()
3641-
our_kings = self.kings & self.occupied_co[self.turn] & ~self.promoted
3651+
our_kings = self.kings & self.occupied_co[self.turn] & ~self._effective_promoted()
36423652
if checkers:
36433653
if popcount(checkers) > 2:
36443654
errors |= STATUS_TOO_MANY_CHECKERS
@@ -3822,7 +3832,7 @@ def generate_castling_moves(self, from_mask: Bitboard = BB_ALL, to_mask: Bitboar
38223832
return
38233833

38243834
backrank = BB_RANK_1 if self.turn == WHITE else BB_RANK_8
3825-
king = self.occupied_co[self.turn] & self.kings & ~self.promoted & backrank & from_mask
3835+
king = self.occupied_co[self.turn] & self.kings & ~self._effective_promoted() & backrank & from_mask
38263836
king &= -king
38273837
if not king:
38283838
return
@@ -3879,6 +3889,7 @@ def _to_chess960(self, move: Move) -> Move:
38793889
def _transposition_key(self) -> Hashable:
38803890
return (self.pawns, self.knights, self.bishops, self.rooks,
38813891
self.queens, self.kings,
3892+
self._effective_promoted(),
38823893
self.occupied_co[WHITE], self.occupied_co[BLACK],
38833894
self.turn, self.clean_castling_rights(),
38843895
self.ep_square if self.has_legal_en_passant() else None)

chess/variant.py

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -127,16 +127,8 @@ def is_legal(self, move: chess.Move) -> bool:
127127
else:
128128
return not any(self.generate_pseudo_legal_captures())
129129

130-
def _transposition_key(self) -> Hashable:
131-
if self.has_chess960_castling_rights():
132-
return (super()._transposition_key(), self.kings & self.promoted)
133-
else:
134-
return super()._transposition_key()
135-
136-
def board_fen(self, *, promoted: Optional[bool] = None) -> str:
137-
if promoted is None:
138-
promoted = self.has_chess960_castling_rights()
139-
return super().board_fen(promoted=promoted)
130+
def _effective_promoted(self) -> Bitboard:
131+
return self.kings & self.promoted if self.castling_rights else chess.BB_EMPTY
140132

141133
def status(self) -> chess.Status:
142134
status = super().status()
@@ -261,9 +253,9 @@ def _push_capture(self, move: chess.Move, capture_square: chess.Square, piece_ty
261253

262254
# Destroy castling rights.
263255
self.castling_rights &= ~explosion_radius
264-
if explosion_radius & self.kings & self.occupied_co[chess.WHITE] & ~self.promoted:
256+
if explosion_radius & self.kings & self.occupied_co[chess.WHITE] & ~self._effective_promoted():
265257
self.castling_rights &= ~chess.BB_RANK_1
266-
if explosion_radius & self.kings & self.occupied_co[chess.BLACK] & ~self.promoted:
258+
if explosion_radius & self.kings & self.occupied_co[chess.BLACK] & ~self._effective_promoted():
267259
self.castling_rights &= ~chess.BB_RANK_8
268260

269261
# Explode the capturing piece.
@@ -930,9 +922,11 @@ def _is_halfmoves(self, n: int) -> bool:
930922
def is_irreversible(self, move: chess.Move) -> bool:
931923
return self._reduces_castling_rights(move)
932924

925+
def _effective_promoted(self) -> Bitboard:
926+
return self.promoted & ~self.kings & ~self.pawns
927+
933928
def _transposition_key(self) -> Hashable:
934929
return (super()._transposition_key(),
935-
self.promoted,
936930
str(self.pockets[chess.WHITE]), str(self.pockets[chess.BLACK]))
937931

938932
def legal_drop_squares_mask(self) -> chess.Bitboard:
@@ -1009,7 +1003,7 @@ def has_insufficient_material(self, color: chess.Color) -> bool:
10091003
# a different color complex.
10101004
return (
10111005
chess.popcount(self.occupied) + sum(len(pocket) for pocket in self.pockets) <= 3 and
1012-
not self.promoted and
1006+
not self._effective_promoted() and
10131007
not self.pawns and
10141008
not self.rooks and
10151009
not self.queens and
@@ -1041,11 +1035,6 @@ def set_fen(self, fen: str) -> None:
10411035
self.pockets[chess.WHITE] = white_pocket
10421036
self.pockets[chess.BLACK] = black_pocket
10431037

1044-
def board_fen(self, *, promoted: Optional[bool] = None) -> str:
1045-
if promoted is None:
1046-
promoted = True
1047-
return super().board_fen(promoted=promoted)
1048-
10491038
def epd(self, shredder: bool = False, en_passant: chess.EnPassantSpec = "legal", promoted: Optional[bool] = None, **operations: Union[None, str, int, float, chess.Move, Iterable[chess.Move]]) -> str:
10501039
epd = super().epd(shredder=shredder, en_passant=en_passant, promoted=promoted)
10511040
board_part, info_part = epd.split(" ", 1)

0 commit comments

Comments
 (0)