from sys import stderr import time import math from typing import Any, Callable import Goban def _alphabeta( board: Goban.Board, heuristic: Callable[[Goban.Board, Any], float], color, alpha=-math.inf, beta=math.inf, depth: int = 3, shouldStop=lambda: False ) -> tuple[float, Any]: if board.is_game_over() or depth == 0: return heuristic(board, color), None wantMax = (board.next_player() == color) best_move = None if wantMax: acc = -math.inf for move in board.generate_legal_moves(): if Goban.Board.flat_to_name(move) == "PASS": continue board.push(move) value = _alphabeta( board, heuristic=heuristic, color=color, alpha=alpha, beta=beta, depth=depth - 1, shouldStop=shouldStop )[0] board.pop() if value > acc: acc = value best_move = move alpha = max(alpha, acc) if shouldStop() or acc >= beta: break # beta cutoff else: acc = math.inf for move in board.generate_legal_moves(): if Goban.Board.flat_to_name(move) == "PASS": continue board.push(move) value = _alphabeta( board, heuristic=heuristic, color=color, alpha=alpha, beta=beta, depth=depth - 1, shouldStop=shouldStop )[0] board.pop() if value < acc: acc = value best_move = move beta = min(beta, acc) if shouldStop() or acc <= alpha: break # alpha cutoff return acc, best_move def alphabeta( board: Goban.Board, heuristic: Callable[[Goban.Board, Any], float], color, depth: int = 3, ): _, move = _alphabeta(board, heuristic=heuristic, color=color, depth=depth) return move def IDDFS( board: Goban.Board, heuristic: Callable[[Goban.Board, Any], float], color, max_depth: int = 10, duration: float = 5.0 # Duration in seconds ): best_move = None start_time = time.time() shouldStop = lambda: (time.time() - start_time) >= duration for depth in range(1, max_depth + 1): value, move = _alphabeta( board, heuristic=heuristic, color=color, depth=depth, shouldStop=shouldStop ) if shouldStop(): break print(f"{depth}, {value}", file=stderr) best_move = move return best_move