How to stop alpha-beta with a timer in iterative deepening

901 Views Asked by At

I have created a minimax function with alpha beta pruning that I call with iterative deepening. The problem is that when timer is done the function keeps running until it finishes on the depth it started with before the timer ran out.

What I want: When timer runs out the minimax function should quit and either return none (I keep best move outside of minimax, see code for minimax call below), or return the previously calculated best move. I just can't seem to figure out how to implement that in the minimax function, everything I tried results in it still completing the depth it is currently on.

Minimax function:

def minimax(gamestate, depth, alpha, beta, maximizing_player):

    if depth == 0 or gamestate.is_check_mate or gamestate.is_stale_mate:
        return None, evaluate(gamestate)

    gamestate.is_white_turn = not maximizing_player
    children = gamestate.get_valid_moves()

    best_move = children[0]

    if maximizing_player:
        max_eval = -math.inf
        for child in children:
            board_copy = copy.deepcopy(gamestate)
            board_copy.make_move(child)
            current_eval = ai_minimax(board_copy, depth - 1, alpha, beta, False)[1]
            if current_eval > max_eval:
                max_eval = current_eval
                best_move = child
            alpha = max(alpha, current_eval)
            if beta <= alpha:
                break
        return best_move, max_eval

    else:
        min_eval = math.inf
        for child in children:
            board_copy = copy.deepcopy(gamestate)
            board_copy.make_move(child)
            current_eval = ai_minimax(board_copy, depth - 1, alpha, beta, True)[1]
            if current_eval < min_eval:
                min_eval = current_eval
                best_move = child
            beta = min(beta, current_eval)
            if beta <= alpha:
                break
        return best_move, min_eval 

Function call with iterative deepening:

for depth in range(1, max_search_depth):
    time_start = time.time()
    move, evaluation = minimax(gamestate, depth, alpha, beta, maximizing_player)
    time_end = time.time()
    timer = time_end - time_start
    if timer > max_search_time:
        break 
1

There are 1 best solutions below

0
On

I often solve this kind of problem using a custom Timeout class.

import signal

class TimeoutError(Exception):
    """
    Custom error for Timeout class.
    """

    pass


class Timeout:
    """
    A timeout handler with context manager.
    Based on UNIX signals.
    """

    def __init__(self, seconds=1, error_message="Timeout"):
        self.seconds = seconds
        self.error_message = error_message

    def handle_timeout(self, signum, frame):
        raise TimeoutError(self.error_message)

    def __enter__(self):
        signal.signal(signal.SIGALRM, self.handle_timeout)
        signal.alarm(self.seconds)

    def __exit__(self, type, value, traceback):
        signal.alarm(0)

You can run your recursive function inside a with statement like this:

with Timeout(5):
    try:
        result = minimax(gamestate, depth, alpha, beta, maximizing_player)
    except TimeoutError:
        result = None