def is_insufficient_material(self): # Enough material. if self.knights or self.rooks or self.queens or self.kings: return False # Must have bishops. if not (self.occupied_co[chess.WHITE] & self.bishops and self.occupied_co[chess.BLACK] & self.bishops): return False # All pawns must be blocked. w_pawns = self.pawns & self.occupied_co[chess.WHITE] b_pawns = self.pawns & self.occupied_co[chess.BLACK] b_blocked_pawns = chess.shift_up(w_pawns) & b_pawns w_blocked_pawns = chess.shift_down(b_pawns) & w_pawns if (b_blocked_pawns | w_blocked_pawns) != self.pawns: return False turn = self.turn turn = chess.WHITE if any(self.generate_pseudo_legal_moves(self.pawns)): return False turn = chess.BLACK if any(self.generate_pseudo_legal_moves(self.pawns)): return False self.turn = turn # Bishop and pawns of each side are on distinct color complexes. if self.occupied_co[chess.WHITE] & chess.BB_DARK_SQUARES == 0: return self.occupied_co[chess.BLACK] & chess.BB_LIGHT_SQUARES == 0 elif self.occupied_co[chess.WHITE] & chess.BB_LIGHT_SQUARES == 0: return self.occupied_co[chess.BLACK] & chess.BB_DARK_SQUARES == 0 else: return False
def get_special_flags(self, board, castling_rights): special_flags = 0 # Hash in the castling flags [768 ... 771]. if castling_rights[0]: # white_king special_flags ^= POLYGLOT_RANDOM_ARRAY[768] if castling_rights[1]: # white_queen special_flags ^= POLYGLOT_RANDOM_ARRAY[769] if castling_rights[2]: # black_king special_flags ^= POLYGLOT_RANDOM_ARRAY[770] if castling_rights[3]: # black_queen special_flags ^= POLYGLOT_RANDOM_ARRAY[771] # Hash in the en passant file [772 ... 779]. if board.ep_square: # But only if there's actually a pawn ready to capture it. Legality # of the potential capture is irrelevant. if board.turn == chess.WHITE: ep_mask = chess.shift_down(chess.BB_SQUARES[board.ep_square]) else: ep_mask = chess.shift_up(chess.BB_SQUARES[board.ep_square]) ep_mask = chess.shift_left(ep_mask) | chess.shift_right(ep_mask) if ep_mask & board.pawns & board.occupied_co[board.turn]: rand = POLYGLOT_RANDOM_ARRAY[772 + chess.square_file(board.ep_square)] special_flags ^= rand # Hash in turn [780] if board.turn == chess.WHITE: special_flags ^= POLYGLOT_RANDOM_ARRAY[780] return special_flags
def hash_ep_square(self, board: chess.Board) -> int: # Hash in the en passant file. if board.ep_square: # But only if there's actually a pawn ready to capture it. Legality # of the potential capture is irrelevant. if board.turn == chess.WHITE: ep_mask = chess.shift_down(chess.BB_SQUARES[board.ep_square]) else: ep_mask = chess.shift_up(chess.BB_SQUARES[board.ep_square]) ep_mask = chess.shift_left(ep_mask) | chess.shift_right(ep_mask) if ep_mask & board.pawns & board.occupied_co[board.turn]: return self.array[772 + chess.square_file(board.ep_square)] return 0
def zobrist_hash(board): """ Calculates the Polyglot Zobrist hash of the position. A zobrist hash is an exclusive or of pseudo random values picked from an array. Which values are picked is decided by features of the position, such as piece positions, castling rights and en passant squares. For this implementation an array of 781 values is required. """ # Hash in the board setup. zobrist_hash = board_zobrist_hash(board) # Hash in the castling flags. if board.has_kingside_castling_rights(chess.WHITE): zobrist_hash ^= POLYGLOT_RANDOM_ARRAY[768] if board.has_queenside_castling_rights(chess.WHITE): zobrist_hash ^= POLYGLOT_RANDOM_ARRAY[768 + 1] if board.has_kingside_castling_rights(chess.BLACK): zobrist_hash ^= POLYGLOT_RANDOM_ARRAY[768 + 2] if board.has_queenside_castling_rights(chess.BLACK): zobrist_hash ^= POLYGLOT_RANDOM_ARRAY[768 + 3] # Hash in the en passant file. if board.ep_square: # But only if theres actually a pawn ready to capture it. Legality # of the potential capture is irrelevant. if board.turn == chess.WHITE: ep_mask = chess.shift_down(chess.BB_SQUARES[board.ep_square]) else: ep_mask = chess.shift_up(chess.BB_SQUARES[board.ep_square]) ep_mask = chess.shift_left(ep_mask) | chess.shift_right(ep_mask) if ep_mask & board.pawns & board.occupied_co[board.turn]: zobrist_hash ^= POLYGLOT_RANDOM_ARRAY[772 + chess.square_file(board.ep_square)] # Hash in the turn. if board.turn == chess.WHITE: zobrist_hash ^= POLYGLOT_RANDOM_ARRAY[780] return zobrist_hash
def apply_hash(key, board, move: chess.Move): pivot = 1 if board.piece_at(move.from_square).color else 0 from_square = move.from_square from_piece_type = board.piece_type_at(move.from_square) fpiece_index = (typing.cast(chess.PieceType, from_piece_type) - 1) * 2 + pivot to_square = move.to_square # move the piece at the source square key ^= Z_ARR[64 * fpiece_index + from_square] key ^= Z_ARR[64 * fpiece_index + to_square] # update castling rights if (from_piece_type == chess.KING): # castling move if (chess.square_distance(from_square, to_square) > 1): rook_index = (typing.cast(chess.PieceType, chess.ROOK) - 1) * 2 + pivot # queenside if (to_square == chess.C1 or to_square == chess.C8): if (board.turn): key ^= Z_ARR[64 * rook_index + chess.A1] key ^= Z_ARR[64 * rook_index + chess.D1] key ^= Z_ARR[769] else: key ^= Z_ARR[64 * rook_index + chess.A8] key ^= Z_ARR[64 * rook_index + chess.D8] key ^= Z_ARR[771] # kingside elif (to_square == chess.G1 or to_square == chess.G8): if (board.turn): key ^= Z_ARR[64 * rook_index + chess.H1] key ^= Z_ARR[64 * rook_index + chess.F1] key ^= Z_ARR[768] else: key ^= Z_ARR[64 * rook_index + chess.H8] key ^= Z_ARR[64 * rook_index + chess.F8] key ^= Z_ARR[770] # non castling move with king moving else: if board.has_castling_rights(board.turn): if board.turn: key ^= Z_ARR[768] key ^= Z_ARR[769] else: key ^= Z_ARR[770] key ^= Z_ARR[771] elif from_piece_type == chess.ROOK: if (board.has_queenside_castling_rights(board.turn) and from_square == chess.A1 or from_square == chess.A8): if board.turn: key ^= Z_ARR[769] else: key ^= Z_ARR[771] if (board.has_kingside_castling_rights(board.turn) and (from_square == chess.H1 or from_square == chess.H8)): if board.turn: key ^= Z_ARR[768] else: key ^= Z_ARR[770] # capture moves if board.is_capture(move) and not board.is_en_passant(move): # remove the piece at the moving to square to_piece_type = board.piece_type_at(to_square) tpiece_index = (typing.cast(chess.PieceType, to_piece_type) - 1) * \ 2 + (1 if board.piece_at(move.to_square).color else 0) key ^= Z_ARR[64 * tpiece_index + to_square] # promotion if (from_piece_type == chess.PAWN and (chess.square_rank(to_square) == 0 or chess.square_rank(to_square) == 7)): if move.promotion: new_p_index = (typing.cast(chess.PieceType, move.promotion) - 1) * 2 + pivot # replace current piece type with new piece type key ^= Z_ARR[64 * fpiece_index + to_square] key ^= Z_ARR[64 * new_p_index + to_square] # update ep rights (chess.polyglot) if board.ep_square: if board.turn == chess.WHITE: ep_mask = chess.shift_down(chess.BB_SQUARES[board.ep_square]) else: ep_mask = chess.shift_up(chess.BB_SQUARES[board.ep_square]) ep_mask = chess.shift_left(ep_mask) | chess.shift_right(ep_mask) if ep_mask & board.pawns & board.occupied_co[board.turn]: key ^= Z_ARR[772 + chess.square_file(board.ep_square)] if (board.is_en_passant(move)): to_square_file = chess.square_file(to_square) if board.turn: capture_rank = chess.square_rank(to_square) - 1 cap_square = chess.square(to_square_file, capture_rank) tpiece_index = (typing.cast(chess.PieceType, chess.PAWN) - 1) * 2 key ^= Z_ARR[64 * tpiece_index + cap_square] else: capture_rank = chess.square_rank(to_square) + 1 cap_square = chess.square(to_square_file, capture_rank) tpiece_index = (typing.cast(chess.PieceType, chess.PAWN) - 1) * 2 + 1 key ^= Z_ARR[64 * tpiece_index + cap_square] return key