@@ -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 )
0 commit comments