def is_pin_see(fen: str, move: chess.Move) -> bool: aug = board.AugBoard(fen) if aug.piece_type_at(move.from_square) not in [ chess.QUEEN, chess.ROOK, chess.BISHOP, ]: return False attackers_before_move = aug.attacking_pairs(aug.current_color) aug.push(move) if aug.is_checkmate(): return False attackers_after_move = aug.attacking_pairs(aug.other_color) triples = [] for attacker, attacked in attackers_after_move - attackers_before_move: capturers = aug.square_capturers(attacker) if capturers and aug.see(attacker, moves_without_stop=1) >= 0: continue without_attacked = board.AugBoard(aug.fen()) without_attacked.remove_piece_at(attacked) for some_attacker, new_attacked in without_attacked.attacking_pairs( aug.other_color): if all(( some_attacker == attacker, attacked in chess.SquareSet.between(attacker, new_attacked), without_attacked.piece_value_at(new_attacked) > aug.piece_value_at(attacked), )): triples.append((attacker, attacked, new_attacked)) for a, b, c in triples: # TODO: consider removing this condition and handling it another way since pawns can be also pinned if aug.piece_type_at(b) == chess.PAWN: continue # absolute pin if aug.piece_type_at(c) == chess.KING: return True # relative pin without_attacked = board.AugBoard(aug.fen()) without_attacked.remove_piece_at(b) without_attacked.push(chess.Move.null()) if without_attacked.see(c, attacker=a, moves_without_stop=1) > 0: return True return False
def is_fork_simple(fen: str, move: chess.Move) -> bool: aug = board.AugBoard(fen) return (not aug.gives_checkmate(move) and len( aug.move_attacks(move, min_value=aug.piece_value_at(move.from_square) + 1) | aug.move_attacks(move, min_value=3, defended=False)) >= 2 and not aug.can_move_be_captured(move))
def creates_material_gain_capture(fen: str, move: chess.Move) -> bool: aug = board.AugBoard(fen) if aug.gives_check(move): return False if aug.has_positive_see_capture(): return False aug.push(move) aug.push(chess.Move.null()) return aug.has_positive_see_capture()
def is_sacrifice_see(fen: str, move: chess.Move) -> bool: """ A move is a sacrifice when it loses material. NOTE: Wikipedia proposed a division between sham and real sacrifices: https://en.wikipedia.org/wiki/Sacrifice_(chess)#Types_of_sacrifice maybe something to consider for the future. """ aug = board.AugBoard(fen) return aug.see( move.to_square, attacker=move.from_square, moves_without_stop=1) < 0
def creates_mate_threat(fen: str, move: chess.Move) -> bool: aug = board.AugBoard(fen) if aug.gives_check(move): return False if aug.has_mate(): return False aug.push(move) aug.push(chess.Move.null()) return aug.has_mate()
def get_mating_net_piece_type_counters( fen: str) -> Optional[Tuple[Counter, Counter]]: mating_net = get_mating_net(fen) if mating_net is None: return None maters, cutters = mating_net aug = board.AugBoard(fen) return ( Counter(aug.piece_type_at(square) for square in maters), Counter(aug.piece_type_at(square) for square in cutters), )
def is_arabian_mate_extended(fen: str, move: chess.Move): """ Same as in Wikipedia definition except that King can be on any border square. """ aug = board.AugBoard(fen) king = aug.other_color_king() if (chess.square_file(king) not in [0, 7]) and ( chess.square_rank(king) not in [0, 7] ): return False return is_arabian_mate_extra_extended(fen, move)
def is_fork_see(fen, move): aug = board.AugBoard(fen) if aug.gives_checkmate(move): return False attacked = aug.move_attacks( move, min_value=aug.piece_value_at(move.from_square) + 1) | aug.move_attacks(move, min_value=3, defended=False) if len(attacked) < 2: return False aug.push(move) return (not aug.square_capturers(move.to_square) or aug.see(move.to_square, moves_without_stop=1) < 0)
def get_mating_net(fen) -> Optional[Tuple[chess.SquareSet, chess.SquareSet]]: aug = board.AugBoard(fen) if not aug.is_checkmate(): return None their_king = aug.current_color_king() maters = aug.attackers(aug.other_color, their_king) escape_squares = [ square for square in aug.attacks(their_king) if aug.piece_at(square) is None ] cutters = chess.SquareSet() for escape_square in escape_squares: cutters.update(aug.attackers(aug.other_color, escape_square)) cutters -= maters return maters, cutters
def is_smothered_mate(fen, move): """ According to Wikipedia: > In chess, a smothered mate is a checkmate delivered by a knight in which the mated king is unable to move > because he is surrounded (or smothered) by his own pieces. source: https://en.wikipedia.org/wiki/Smothered_mate """ aug = board.AugBoard(fen) aug.push(move) if not aug.is_checkmate(): return False their_king = aug.current_color_king() for square in aug.attacks(their_king): piece = aug.piece_at(square) if piece is None or piece.color != aug.current_color: return False return True
def is_back_rank_mate(fen, move): """ According to Wikipedia: > In chess, a back-rank checkmate (also known as the corridor mate) is a checkmate delivered by a rook or queen > along a back rank (that is, the row on which the pieces [not pawns] stand at the start of the game) in which > the mated king is unable to move up the board because the king is blocked by friendly pieces (usually pawns) > on the second rank. source: https://en.wikipedia.org/wiki/Back-rank_checkmate """ aug = board.AugBoard(fen) their_back_rank = 7 if aug.other_color == chess.BLACK else 0 their_king = aug.other_color_king() if any(( their_king is None, chess.square_rank(their_king) != their_back_rank, chess.square_rank(move.to_square) != their_back_rank, )): return False aug.push(move) if any(( not aug.is_checkmate(), aug.piece_type_at(move.to_square) not in [chess.QUEEN, chess.ROOK], )): return False num_friendly_blocking_pieces = 0 for square in aug.attacks(their_king) - aug.attacks(move.to_square): if chess.square_rank(square) == their_back_rank: continue if bool(aug.attackers(aug.other_color, square)): continue piece = aug.piece_at(square) if piece is None or piece.color != aug.current_color: return False else: num_friendly_blocking_pieces += 1 return num_friendly_blocking_pieces > 0
def is_arabian_mate_classic(fen: str, move: chess.Move): """ According to Wikipedia: > In the Arabian mate, the knight and the rook team up to trap the opposing king on a corner of the board. > The rook sits on a square adjacent to the king both to prevent escape along the diagonal and to deliver checkmate > while the knight sits two squares away diagonally from the king to prevent escape on the square next to the king > and to protect the rook. source: https://en.wikipedia.org/wiki/Checkmate_pattern#Arabian_mate """ aug = board.AugBoard(fen) their_king = aug.other_color_king() if any( ( their_king is None, (chess.BB_SQUARES[their_king] & chess.BB_CORNERS) == chess.BB_EMPTY, aug.piece_type_at(move.from_square) != chess.ROOK, ) ): return False aug.push(move) if not aug.is_checkmate(): return False patterns = { chess.A1: ([chess.A2, chess.B1], chess.C3), chess.H1: ([chess.H2, chess.G1], chess.F3), chess.A8: ([chess.A7, chess.B8], chess.C6), chess.H8: ([chess.H7, chess.G8], chess.F6), } rook_squares, knight_square = patterns[their_king] return move.to_square in rook_squares and aug.piece_at( knight_square ) == chess.Piece(chess.KNIGHT, aug.other_color)
def is_arabian_mate_extra_extended(fen: str, move: chess.Move): """ Same as in Wikipedia definition except that King can be on any square. """ aug = board.AugBoard(fen) king = aug.other_color_king() if any((king is None, aug.piece_type_at(move.from_square) != chess.ROOK,)): return False aug.push(move) if not aug.is_checkmate(): return False rook = move.to_square if chess.square_distance(king, rook) > 1 or king not in aug.attacks(rook): return False knights_defending_rook = [ square for square in aug.attackers(aug.other_color, rook) if aug.piece_type_at(square) == chess.KNIGHT ] if not knights_defending_rook: return False escape_squares = [ square for square in aug.attacks(king) if aug.piece_at(square) is None ] for escape_square in escape_squares: if not aug.attackers(aug.other_color, escape_square): return False return True
def __init__(self, fen): self.board = board.AugBoard(fen) self.moves = tuple(self.board.legal_moves) self.their_board = self.board.copy() self.their_board.push(chess.Move.null()) self.their_moves = tuple(self.their_board.legal_moves)
def is_skewer_see(fen: str, move: chess.Move) -> bool: aug = board.AugBoard(fen) if aug.piece_type_at(move.from_square) not in [ chess.QUEEN, chess.ROOK, chess.BISHOP, ]: return False attackers_before_move = aug.attacking_pairs(aug.current_color) aug.push(move) if aug.is_checkmate(): return False attackers_after_move = aug.attacking_pairs(aug.other_color) triples = [] for attacker, attacked in attackers_after_move - attackers_before_move: if not any(True for _ in aug.generate_legal_moves_from_square(attacked)): continue if aug.piece_value_at(attacker) > aug.piece_value_at(attacked): continue if (aug.square_capturers(attacker) and aug.see(attacker, moves_without_stop=1) >= 0): continue without_attacked = board.AugBoard(aug.fen()) without_attacked.remove_piece_at(attacked) for some_attacker, new_attacked in without_attacked.attacking_pairs( aug.other_color): if aug.piece_value_at(new_attacked) < 3: continue if all(( some_attacker == attacker, attacked in chess.SquareSet.between(attacker, new_attacked), without_attacked.piece_value_at(new_attacked) < aug.piece_value_at(attacked), )): triples.append((attacker, attacked, new_attacked)) for a, b, c in triples: found_escape = False for m in aug.generate_legal_moves_from_square(b): m_val = aug.piece_value_at(m.to_square) try: aug.push(m) if m_val - aug.see(c, attacker=a) >= 0: found_escape = True break finally: aug.pop() if not found_escape: return True return False
def is_fork_simplest(fen, move): aug = board.AugBoard(fen) return (not aug.gives_checkmate(move) and len( aug.move_attacks( move, min_value=aug.piece_value_at(move.from_square) + 1)) >= 2 and not aug.can_move_be_captured(move))
def get_move_mating_net_piece_type_counters( fen: str, move: chess.Move ) -> Optional[Tuple[Set[chess.PieceType], Set[chess.PieceType]]]: aug = board.AugBoard(fen) aug.push(move) return get_mating_net_piece_type_counters(aug.fen())