From a2aa8ce6889490dde4ccc5823ae99ecc945f522f Mon Sep 17 00:00:00 2001 From: Niklas Fiekas Date: Sun, 5 Mar 2017 15:05:08 +0100 Subject: [PATCH] Replace bit_scan with scan_forward --- chess/__init__.py | 166 ++++++++++++---------------------------------- chess/variant.py | 49 +++++--------- 2 files changed, 57 insertions(+), 158 deletions(-) diff --git a/chess/__init__.py b/chess/__init__.py index b40b4ff8a..dcf3ad07e 100644 --- a/chess/__init__.py +++ b/chess/__init__.py @@ -1110,11 +1110,9 @@ def board_zobrist_hash(self, array=None): zobrist_hash = 0 for pivot, squares in enumerate(self.occupied_co): - square = bit_scan(squares) - while square != -1 and square is not None: + for square in scan_forward(squares): piece_index = (self.piece_type_at(square) - 1) * 2 + pivot zobrist_hash ^= array[64 * piece_index + square] - square = bit_scan(squares, square + 1) return zobrist_hash @@ -1391,15 +1389,10 @@ def generate_pseudo_legal_moves(self, from_mask=BB_ALL, to_mask=BB_ALL): # Generate piece moves. non_pawns = our_pieces & ~self.pawns & from_mask - from_square = bit_scan(non_pawns) - while from_square != -1 and from_square is not None: + for from_square in scan_forward(non_pawns): moves = self.attacks_mask(from_square) & ~our_pieces & to_mask - to_square = bit_scan(moves) - while to_square != -1 and to_square is not None: + for to_square in scan_forward(moves): yield Move(from_square, to_square) - to_square = bit_scan(moves, to_square + 1) - - from_square = bit_scan(non_pawns, from_square + 1) # Generate castling moves. for move in self.generate_castling_moves(from_mask, to_mask): @@ -1412,14 +1405,12 @@ def generate_pseudo_legal_moves(self, from_mask=BB_ALL, to_mask=BB_ALL): # Generate pawn captures. capturers = pawns - from_square = bit_scan(capturers) - while from_square != -1 and from_square is not None: + for from_square in scan_forward(capturers): targets = ( BB_PAWN_ATTACKS[self.turn][from_square] & self.occupied_co[not self.turn] & to_mask) - to_square = bit_scan(targets) - while to_square != -1 and to_square is not None: + for to_square in scan_forward(targets): if square_rank(to_square) in [0, 7]: yield Move(from_square, to_square, QUEEN) yield Move(from_square, to_square, ROOK) @@ -1428,10 +1419,6 @@ def generate_pseudo_legal_moves(self, from_mask=BB_ALL, to_mask=BB_ALL): else: yield Move(from_square, to_square) - to_square = bit_scan(targets, to_square + 1) - - from_square = bit_scan(capturers, from_square + 1) - # Prepare pawn advance generation. if self.turn == WHITE: single_moves = pawns << 8 & ~self.occupied @@ -1444,8 +1431,7 @@ def generate_pseudo_legal_moves(self, from_mask=BB_ALL, to_mask=BB_ALL): double_moves &= to_mask # Generate single pawn moves. - to_square = bit_scan(single_moves) - while to_square != -1 and to_square is not None: + for to_square in scan_forward(single_moves): from_square = to_square + (8 if self.turn == BLACK else -8) if square_rank(to_square) in [0, 7]: @@ -1456,14 +1442,10 @@ def generate_pseudo_legal_moves(self, from_mask=BB_ALL, to_mask=BB_ALL): else: yield Move(from_square, to_square) - to_square = bit_scan(single_moves, to_square + 1) - # Generate double pawn moves. - to_square = bit_scan(double_moves) - while to_square != -1 and to_square is not None: + for to_square in scan_forward(double_moves): from_square = to_square + (16 if self.turn == BLACK else -16) yield Move(from_square, to_square) - to_square = bit_scan(double_moves, to_square + 1) # Generate en passant captures. for move in self.generate_pseudo_legal_ep(from_mask, to_mask): @@ -1477,10 +1459,8 @@ def generate_pseudo_legal_ep(self, from_mask=BB_ALL, to_mask=BB_ALL): self.pawns & self.occupied_co[self.turn] & from_mask & BB_PAWN_ATTACKS[not self.turn][self.ep_square]) - capturer = bit_scan(capturers) - while capturer != -1 and capturer is not None: + for capturer in scan_forward(capturers): yield Move(capturer, self.ep_square) - capturer = bit_scan(capturers, capturer + 1) def generate_pseudo_legal_captures(self, from_mask=BB_ALL, to_mask=BB_ALL): return itertools.chain( @@ -2108,17 +2088,11 @@ def castling_shredder_fen(self): builder = [] - black_castling_rights = castling_rights & BB_RANK_8 - while black_castling_rights: - mask = black_castling_rights & -black_castling_rights - builder.append(FILE_NAMES[square_file(bit_scan(mask))]) - black_castling_rights = black_castling_rights & (black_castling_rights - 1) + for square in scan_forward(castling_rights & BB_RANK_8): + builder.append(FILE_NAMES[square_file(square)]) - white_castling_rights = castling_rights & BB_RANK_1 - while white_castling_rights: - mask = white_castling_rights & -white_castling_rights - builder.append(FILE_NAMES[square_file(bit_scan(mask))].upper()) - white_castling_rights = white_castling_rights & (white_castling_rights - 1) + for square in scan_forward(castling_rights & BB_RANK_1): + builder.append(FILE_NAMES[square_file(square)].upper()) builder.reverse() @@ -2132,34 +2106,22 @@ def castling_xfen(self): if not king_mask: continue - king_file = square_file(bit_scan(king_mask)) + king_file = square_file(lsb(king_mask)) backrank = BB_RANK_1 if color == WHITE else BB_RANK_8 - castling_rights = self.clean_castling_rights() & backrank - while castling_rights: - rook = castling_rights & -castling_rights - rook_file = square_file(bit_scan(rook)) - + for rook_square in scan_forward(self.clean_castling_rights() & backrank): + rook_file = square_file(rook_square) a_side = rook_file < king_file - shredder = False - other_rooks = self.occupied_co[color] & self.rooks & backrank & ~rook - while other_rooks: - other_rook = other_rooks & -other_rooks - if (square_file(bit_scan(other_rook)) < rook_file) == a_side: - shredder = True - break - other_rooks = other_rooks & (other_rooks - 1) + other_rooks = self.occupied_co[color] & self.rooks & backrank & ~BB_SQUARES[rook_square] - if shredder: + if any((square_file(other) < rook_file) == a_side for other in scan_forward(other_rooks)): ch = FILE_NAMES[rook_file] else: ch = "q" if a_side else "k" builder.append(ch.upper() if color == WHITE else ch) - castling_rights = castling_rights & (castling_rights - 1) - if builder: builder.reverse() return "".join(builder) @@ -3194,24 +3156,16 @@ def generate_non_evasions(self, from_mask=BB_ALL, to_mask=BB_ALL): # Generate piece moves. non_pawns = our_pieces & ~self.pawns & from_mask - while non_pawns: - from_bb = non_pawns & -non_pawns - from_square = bit_scan(from_bb) - + for from_square in scan_forward(non_pawns): mask = self.pin_mask(self.turn, from_square) moves = self.attacks_mask(from_square) & ~our_pieces & mask & to_mask - to_square = bit_scan(moves) - while to_square != -1 and to_square is not None: - if from_bb & self.kings and self.attackers_mask(not self.turn, to_square): + for to_square in scan_forward(moves): + if self.kings & BB_SQUARES[from_square] and self.attackers_mask(not self.turn, to_square): # Do not move the king into check. pass else: yield Move(from_square, to_square) - to_square = bit_scan(moves, to_square + 1) - - non_pawns = non_pawns & (non_pawns - 1) - # Generate castling moves. Since we are generating non-evasions we # already know that we are not in check. for move in self.generate_castling_moves(from_mask, to_mask): @@ -3223,15 +3177,13 @@ def generate_non_evasions(self, from_mask=BB_ALL, to_mask=BB_ALL): return # Generate pawn captures. - from_square = bit_scan(pawns) - while from_square != -1 and from_square is not None: + for from_square in scan_forward(pawns): targets = ( BB_PAWN_ATTACKS[self.turn][from_square] & self.occupied_co[not self.turn] & to_mask & self.pin_mask(self.turn, from_square)) - to_square = bit_scan(targets) - while to_square != -1 and to_square is not None: + for to_square in scan_forward(targets): if square_rank(to_square) in [0, 7]: yield Move(from_square, to_square, QUEEN) yield Move(from_square, to_square, ROOK) @@ -3240,26 +3192,17 @@ def generate_non_evasions(self, from_mask=BB_ALL, to_mask=BB_ALL): else: yield Move(from_square, to_square) - to_square = bit_scan(targets, to_square + 1) - - from_square = bit_scan(pawns, from_square + 1) - # Generate en passant captures. ep_square_mask = BB_SQUARES[self.ep_square] if self.ep_square else BB_VOID if ep_square_mask & to_mask: capturers = BB_PAWN_ATTACKS[not self.turn][self.ep_square] & pawns - while capturers: - capturer_bb = capturers & -capturers - capturer = bit_scan(capturer_bb) - + for capturer in scan_forward(capturers): if ep_square_mask & self.pin_mask(self.turn, capturer): - if self._ep_skewered(capturer_bb): + if self._ep_skewered(BB_SQUARES[capturer]): break else: yield Move(capturer, self.ep_square) - capturers = capturers & (capturers - 1) - # Prepare pawn advance generation. if self.turn == WHITE: single_moves = pawns << 8 & ~self.occupied @@ -3272,14 +3215,12 @@ def generate_non_evasions(self, from_mask=BB_ALL, to_mask=BB_ALL): double_moves &= to_mask # Generate single pawn moves. - while single_moves: - to_bb = single_moves & -single_moves - to_square = bit_scan(to_bb) + for to_square in scan_forward(single_moves): from_square = to_square + (8 if self.turn == BLACK else -8) mask = self.pin_mask(self.turn, from_square) - if mask & to_bb: - if BB_BACKRANKS & to_bb: + if mask & BB_SQUARES[to_square]: + if square_rank(to_square) in [0, 7]: yield Move(from_square, to_square, QUEEN) yield Move(from_square, to_square, ROOK) yield Move(from_square, to_square, BISHOP) @@ -3287,27 +3228,16 @@ def generate_non_evasions(self, from_mask=BB_ALL, to_mask=BB_ALL): else: yield Move(from_square, to_square) - single_moves = single_moves & (single_moves - 1) - # Generate double pawn moves. - while double_moves: - to_bb = double_moves & -double_moves - to_square = bit_scan(to_bb) + for to_square in scan_forward(double_moves): from_square = to_square + (16 if self.turn == BLACK else -16) mask = self.pin_mask(self.turn, from_square) - if mask & to_bb: + if mask & BB_SQUARES[to_square]: yield Move(from_square, to_square) - double_moves = double_moves & (double_moves - 1) - def _attacked_for_king(self, path): - test_square = bit_scan(path) - while test_square != -1 and test_square is not None: - if self.attackers_mask(not self.turn, test_square): - return True - test_square = bit_scan(path, test_square + 1) - return False + return any(self.attackers_mask(not self.turn, sq) for sq in scan_forward(path)) def generate_castling_moves(self, from_mask=BB_ALL, to_mask=BB_ALL): if self.is_variant_end(): @@ -3413,9 +3343,8 @@ def generate_evasions(self, from_mask=BB_ALL, to_mask=BB_ALL): if king_attackers & last_double_mask: attacker_attackers |= ep_capturers & self.attackers_mask(self.turn, self.ep_square) & from_mask - while attacker_attackers: - attacker = attacker_attackers & -attacker_attackers - from_square = bit_scan(attacker) + for from_square in scan_forward(attacker_attackers): + attacker = BB_SQUARES[from_square] mask = self.pin_mask(self.turn, from_square) @@ -3434,8 +3363,6 @@ def generate_evasions(self, from_mask=BB_ALL, to_mask=BB_ALL): else: yield Move(from_square, to_square) - attacker_attackers = attacker_attackers & (attacker_attackers - 1) - # Eliminate the sliding moves where we are still in check by the same # piece. attackers = king_attackers @@ -3463,10 +3390,8 @@ def generate_evasions(self, from_mask=BB_ALL, to_mask=BB_ALL): # Move the king. Capturing other pieces or even the attacker is # allowed. moves = BB_KING_ATTACKS[king_square] & ~our_pieces & to_mask - while moves: - to_bb = moves & -moves - to_square = bit_scan(to_bb) - + for to_square in scan_forward(moves): + to_bb = BB_SQUARES[to_square] attacked_square = self.attackers_mask(not self.turn, to_square) capture_attacker = to_bb & attacker_masks & king_attackers @@ -3501,25 +3426,21 @@ def generate_evasions(self, from_mask=BB_ALL, to_mask=BB_ALL): moves &= to_mask # Try moving pieces to the empty squares. - while moves: - empty_bb = moves & -moves - empty_square = bit_scan(empty_bb) + for empty_square in scan_forward(moves): + empty_bb = BB_SQUARES[empty_square] # Blocking piece moves (excluding pawns and the king). blockers = self.attackers_mask(self.turn, empty_square) & ~our_pawns & ~our_king & from_mask - blocker = bit_scan(blockers) - while blocker != -1 and blocker is not None: + for blocker in scan_forward(blockers): mask = self.pin_mask(self.turn, blocker) if mask & empty_bb: yield Move(blocker, empty_square) - blocker = bit_scan(blockers, blocker + 1) - # Generate pawn advances to the empty square. blocking_pawn = empty_bb & forward_pawns if blocking_pawn: from_bb = blocking_pawn >> 8 if self.turn == WHITE else blocking_pawn << 8 - from_square = bit_scan(from_bb) + from_square = lsb(from_bb) if from_bb & from_mask and self.pin_mask(self.turn, from_square) & empty_bb: if empty_bb & BB_BACKRANKS: @@ -3541,15 +3462,13 @@ def generate_evasions(self, from_mask=BB_ALL, to_mask=BB_ALL): from_bb = empty_bb << 16 middle_bb = empty_bb << 8 - from_square = bit_scan(from_bb) + from_square = lsb(from_bb) if from_bb & from_mask and middle_bb & ~self.occupied: mask = self.pin_mask(self.turn, from_square) if mask & empty_bb: yield Move(from_square, empty_square) - moves = moves & (moves - 1) - def _from_chess960(self, from_square, to_square, promotion=None): if not self.chess960 and from_square in [E1, E8] and to_square in [A1, H1, A8, H8] and self.piece_type_at(from_square) == KING: if from_square == E1: @@ -3922,15 +3841,12 @@ def __len__(self): return pop_count(self.mask) def __iter__(self): - square = bit_scan(self.mask) - while square != -1 and square is not None: - yield square - square = bit_scan(self.mask, square + 1) + return scan_forward(self.mask) def __reversed__(self): string = bin(self.mask) l = len(string) - r = string.find("1", 0) + r = string.find("1") while r != -1: yield l - r - 1 r = string.find("1", r + 1) diff --git a/chess/variant.py b/chess/variant.py index c4c32759e..d138a49d8 100644 --- a/chess/variant.py +++ b/chess/variant.py @@ -243,23 +243,15 @@ def _attacked_for_king(self, path): # Can castle onto attacked squares if they are connected to the # enemy king. enemy_kings = self.kings & self.occupied_co[not self.turn] - enemy_king = chess.bit_scan(enemy_kings) - while enemy_king != -1 and enemy_king is not None: + for enemy_king in chess.scan_forward(enemy_kings): path &= ~chess.BB_KING_ATTACKS[enemy_king] - enemy_king = chess.bit_scan(enemy_kings, enemy_king + 1) return super(AtomicBoard, self)._attacked_for_king(path) def _kings_connected(self): - kings = self.kings & self.occupied_co[chess.WHITE] - king_square = chess.bit_scan(kings) - while king_square != -1 and king_square is not None: - if chess.BB_KING_ATTACKS[king_square] & self.kings & self.occupied_co[chess.BLACK]: - return True - - king_square = chess.bit_scan(kings, king_square + 1) - - return False + white_kings = self.kings & self.occupied_co[chess.WHITE] + black_kings = self.kings & self.occupied_co[chess.BLACK] + return any(chess.BB_KING_ATTACKS[sq] & black_kings for sq in chess.scan_forward(white_kings)) def _push_capture(self, move, capture_square, piece_type, was_promoted): # Explode the capturing piece. @@ -267,10 +259,8 @@ def _push_capture(self, move, capture_square, piece_type, was_promoted): # Explode all non pawns around. explosion_radius = chess.BB_KING_ATTACKS[move.to_square] & ~self.pawns - explosion = chess.bit_scan(explosion_radius) - while explosion != -1 and explosion is not None: + for explosion in chess.scan_forward(explosion_radius): self._remove_piece_at(explosion) - explosion = chess.bit_scan(explosion_radius, explosion + 1) # Destroy castling rights. self.castling_rights &= ~explosion_radius @@ -381,22 +371,17 @@ def is_variant_end(self): if self.turn == chess.WHITE or self.kings & self.occupied_co[chess.BLACK] & chess.BB_RANK_8: return True - black_king = chess.bit_scan(self.kings & self.occupied_co[chess.BLACK]) - if black_king is None or black_king == -1: + black_kings = self.kings & self.occupied_co[chess.BLACK] + if not black_kings: return True + black_king = chess.lsb(black_kings) + # White has reached the backrank. The game is over if black can not # also reach the backrank on the next move. Check if there are any # safe squares for the king. targets = chess.BB_KING_ATTACKS[black_king] & chess.BB_RANK_8 - target = chess.bit_scan(targets) - while target != -1 and target is not None: - if not self.attackers_mask(chess.WHITE, target): - return False - target = chess.bit_scan(targets, target + 1) - - return True - + return all(self.attackers_mask(chess.WHITE, target) for target in chess.scan_forward(targets)) def is_variant_draw(self): in_goal = self.kings & chess.BB_RANK_8 return all(in_goal & side for side in self.occupied_co) @@ -698,16 +683,17 @@ def is_zeroing(self, move): def legal_drop_squares_mask(self): king_bb = self.kings & self.occupied_co[self.turn] - king_square = chess.bit_scan(king_bb) - if king_square is None or king_square == -1: + if not king_bb: return ~self.occupied + king_square = chess.lsb(king_bb) + king_attackers = self.attackers_mask(not self.turn, king_square) num_attackers = chess.pop_count(king_attackers) - if num_attackers == 0: + if not king_attackers: return ~self.occupied - elif num_attackers == 1: + elif chess.pop_count(king_attackers) == 1: king_rank_mask = chess.RANK_MASK[king_bb] king_file_mask = chess.FILE_MASK[king_bb] king_diag_ne = chess.DIAG_MASK_NE[king_bb] @@ -753,13 +739,10 @@ def is_legal(self, move): return super(CrazyhouseBoard, self).is_legal(move) def generate_pseudo_legal_drops(self, to_mask=chess.BB_ALL): - to_mask = to_mask & ~self.occupied - to_square = chess.bit_scan(to_mask) - while to_square != -1 and to_square is not None: + for to_square in chess.scan_forward(to_mask & ~self.occupied): for pt, count in self.pockets[self.turn].pieces.items(): if count and (pt != chess.PAWN or not chess.BB_BACKRANKS & chess.BB_SQUARES[to_square]): yield chess.Move(to_square, to_square, drop=pt) - to_square = chess.bit_scan(to_mask, to_square + 1) def generate_legal_drops(self, to_mask=chess.BB_ALL): return self.generate_pseudo_legal_drops(to_mask=self.legal_drop_squares_mask() & to_mask)