Skip to content

Instantly share code, notes, and snippets.

@luccabb
Last active December 8, 2021 03:55
Show Gist options
  • Save luccabb/846bd3af6d3e7e518b91da0efbaeef79 to your computer and use it in GitHub Desktop.
Save luccabb/846bd3af6d3e7e518b91da0efbaeef79 to your computer and use it in GitHub Desktop.
Null Move pruning
def negamax(
board: chess.Board,
depth: int,
player: int,
null_move: bool,
alpha: float = float("-inf"),
beta: float = float("inf")) -> Tuple[Union[int, chess.Move]]:
"""
This functions receives a board, depth and a player; and it returns
the best move for the current board based on how many depths we're looking ahead
and which player is playing. Alpha and beta are used to prune the search tree.
Alpha represents the best score for the maximizing player (best choice (highest value) we've found
along the path for max) and beta represents the best score for the minimizing player
(best choice (lowest value) we've found along the path for min). When Alpha is higher
than or equal to Beta, we can prune the search tree; because it means that the
maximizing player won't find a better move in this branch.
OBS:
- We only need to evaluate the value for leaf nodes because they are our final states
of the board and therefore we need to use their values to base our decision of what is
the best move.
Arguments:
- board: chess board state
- depth: how many depths we want to calculate for this board
- player: player that is currently moving pieces. 1 for AI (max), -1 for human (min).
- null_move (bool): whether or not we should try the null move pruning technique.
Returns:
- best_score, best_move: returns best move that it found and its value.
"""
# recursion base case
if depth == 0:
# evaluate current board
score = player * board_value(board)
return score, None
# null move pruning
if null_move and depth >= (R+1) and not board.is_check():
board.push(chess.Move.null())
score = -negamax(board, depth -1 - R, -player, False, -beta, -beta+1)[0]
board.pop()
if score >= beta:
return beta, None
best_move = None
# initializing best_score
best_score = float("-inf")
for move in board.legal_moves:
# Make the move
board.push(move)
# if threefold repetition we do not analyze this position
if board.can_claim_threefold_repetition():
board.pop()
continue
score = -negamax(board, depth-1, -player, null_move, -beta, -alpha)[0]
# take move back
board.pop()
if score >= beta:
return score, move
# update best move
if score > best_score:
best_score = score
best_move = move
# setting alpha variable to do prunning
alpha = max(alpha, score)
# alpha beta prunning when we already found a solution that is at least as good as the current one
# those branches won't be able to influence the final decision so we don't need to waste time analyzing them
if alpha >= beta:
break
# if it returned no best move, we make a random one
if not best_move:
if board.legal_moves:
best_move = random.choice([move for move in board.legal_moves])
else:
best_move = (None, None)
return best_score, best_move
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment