예제 #1
0
def generate_ordered_moves_v1(board: SearchBoard, pv_move: chess.Move, killer_moves: List[List[chess.Move]],
                              search_history: List[List[int]], captures_only: bool = False) \
        -> Generator[chess.Move, None, None]:
    """
    Generates ordered moves using the following heuristics in this order:
    - principal variation move
    - most valuable victim, least valuable attacker captures (MVV LVA)
    - killer moves (beta cutoff but aren't captures)
    - search history (alpha cutoff but not beta cutoff and not a capture)
    :param board: The board to generate moves for
    :param pv_move: The principal variation move
    :param killer_moves: The killer moves table, a 2 x max_depth size array with the 0th index containing current
    killers and the 1st index containing next killers
    :param search_history: The search history table, a 64 x 64 size array that provides a heatmap of "relevant"
    (alpha cutoff meeting but not beta or capture) moves
    :param captures_only: True if only captures should be generated, False otherwise
    :return:
    """
    scored_moves: List[_ScoredMove] = []
    moves = board.generate_legal_captures(
    ) if captures_only else board.legal_moves
    for move in moves:
        if board.is_capture(move):
            attacker = board.piece_at(move.from_square).piece_type
            victim_piece = board.piece_at(move.to_square)
            if not victim_piece:  # en passant
                victim = chess.PAWN
            else:
                victim = victim_piece.piece_type
            score = MVV_LVA_SCORES[victim][attacker] + 10_000_000
        # "Quiet" moves
        elif killer_moves[0][
                board.searching_ply] == move:  # Current killer moves
            score = 9_000_000
        elif killer_moves[1][board.searching_ply] == move:  # Next killer moves
            score = 8_000_000
        else:  # Simply use search history heatmap
            score = search_history[move.from_square][move.to_square]

        # Principal variation should always be considered first
        if move == pv_move:
            score = 20_000_000
        scored_moves.append(_ScoredMove(move, score))
    return ordered_move_generator(scored_moves)
예제 #2
0
    def _minimax_alpha_beta(self, alpha: int, beta: int, board: SearchBoard,
                            depth: int, null_pruning: bool) -> int:
        # Check depth terminal condition
        if depth <= 0:
            return self._quiescence(alpha, beta, board)
        self.search_info.nodes += 1

        if self._terminal_condition(
                board) and board.searching_ply > 0:  # Stalemate/draw condition
            return 0

        # Evaluate
        evaluation = self.evaluator.evaluate_board(board)

        if board.searching_ply > self.max_depth - 1:  # Max depth
            return evaluation.board_evaluation

        # In-check test to "get out of check"
        in_check = board.is_check()
        depth += 1 if in_check else 0

        # Prove transposition table and return early if we have a hit
        pv_move, score = board.probe_transposition_table(
            alpha, beta, depth, self.mate_value)

        if pv_move:
            return score

        # Null move pruning (must also verify that the side isn't in check and not in zugzwang scenario)
        if null_pruning and not in_check and board.searching_ply > 0 \
                and not self._zugzwang(board) and depth >= self.reduction_factor + 1:
            board.push(chess.Move.null())
            score = -self._minimax_alpha_beta(
                -beta, -beta + 1, board, depth - self.reduction_factor - 1,
                False)
            board.pop()
            if self.search_info.stop:
                return 0
            # If the null move option improves on beta and isn't a mate then return it
            if score >= beta and abs(score) < self.mate_value:
                return beta

        old_alpha = alpha
        best_move = None
        best_score = -self.inf
        num_legal_moves = 0
        found_pv = False

        for move in self.move_orderer(board, pv_move):
            if move in self.search_info.excluded_moves:
                continue
            num_legal_moves += 1
            board.push(move)

            if found_pv:  # PVS (principal variation search)
                score = -self._minimax_alpha_beta(-alpha - 1, -alpha, board,
                                                  depth - 1, True)
                if alpha < score < beta:
                    score = -self._minimax_alpha_beta(-beta, -alpha, board,
                                                      depth - 1, True)
            else:
                score = -self._minimax_alpha_beta(-beta, -alpha, board,
                                                  depth - 1, True)

            board.pop()

            if self.search_info.stop:
                return 0

            capture = board.is_capture(move)

            if score > best_score:
                best_score = score
                best_move = move
                if score > alpha:  # Alpha cutoff
                    if score >= beta:  # Beta cutoff
                        self.search_info.fail_high_first += 1 if num_legal_moves == 1 else 0
                        self.search_info.fail_high += 1
                        if not capture:  # Killer move (beta cutoff but not capture)
                            self.search_killers[1][
                                board.searching_ply] = self.search_killers[0][
                                    board.searching_ply]
                            self.search_killers[0][board.searching_ply] = move
                        board.store_transposition_table_entry(
                            best_move, beta, depth,
                            TranspositionTableFlag.BETA, self.mate_value)
                        return beta
                    found_pv = True
                    alpha = score
                    if not capture:  # Alpha cutoff that isn't a capture
                        self.search_history[best_move.from_square][
                            best_move.to_square] += depth

        if num_legal_moves == 0:  # Checkmate cases
            if in_check:
                return -self.inf + board.searching_ply
            else:  # Draw
                return 0

        if alpha != old_alpha:  # Principal variation
            board.store_transposition_table_entry(best_move, best_score, depth,
                                                  TranspositionTableFlag.EXACT,
                                                  self.mate_value)
        else:
            board.store_transposition_table_entry(best_move, alpha, depth,
                                                  TranspositionTableFlag.ALPHA,
                                                  self.mate_value)

        return alpha