def alphabeta_max_node(board, color, alpha, beta, level, limit): if board in dp_alphabeta: return dp_alphabeta[board] if color == 1: opponent_color = 2 else: opponent_color = 1 if len(get_possible_moves(board, color)) == 0 or level == limit: return compute_utility(board, color) order = [] #the PQ should return the lowest utility for this node for successor in get_possible_moves(board, color): temp_board = play_move(board, color, successor[0], successor[1]) #max node is opponent - AI heappush(order, (compute_utility(temp_board, opponent_color), temp_board)) v = -inf while order: min_util = heappop(order) #returns a tuple pair new_board = min_util[1] #get the board v = max( v, alphabeta_min_node(new_board, opponent_color, alpha, beta, level + 1, limit)) if v >= beta: dp_alphabeta.update({board: v}) return v alpha = max(alpha, v) dp_alphabeta.update({board: v}) return v
def select_move_alphabeta(board, color): """ Given a board and a player color, decide on a move. The return value is a tuple of integers (i,j), where i is the column and j is the row on the board. """ if is_leaf_node(board, color): raise Exception("No legal moves allowed") else: alpha = -1000 beta = 1000 moves = get_possible_moves(board, color) moves = get_possible_moves(board, color) successors_moves = {} for move in moves: successors_moves[play_move(board, color, move[0], move[1])] = move sorted_successors = sorted(successors_moves.keys(), key=lambda x: compute_utility(x, color), reverse=True) max_val = -1000 max_move = () for successor in sorted_successors: # successor = play_move(board, color, move[0], move[1]) val = alphabeta_min_node(successor, color, alpha, beta, 0, 16) if val > max_val: max_val = val max_move = successors_moves[successor] return max_move
def alphabeta_min_node(board, color, alpha, beta, level, limit): """ Method for min node using alpha beta pruning. If a terminal stage is reached, return current utility. Else recursively call method for max node on each possible move and do cutoffs/updates. """ if limit == level or len(get_possible_moves(board, other_color(color))) == 0: # terminal state return compute_utility(board, color) else: # order the child states ordered_states = [] for x in get_possible_moves(board, other_color(color)): # for every possible move of this min node next_board = play_move(board, other_color(color), x[0], x[1]) # get the result board of this move # put board state with maximum utility at the root of heap heappush(ordered_states, (compute_utility(next_board, color), next_board)) cur = float('inf') # keep record the min_max for this min node while(len(ordered_states) != 0): max_value = alphabeta_max_node(heappop(ordered_states)[1], color, alpha, beta, level + 1, limit) cur = min(cur, max_value) if (cur <= alpha): # min_max has dropped below parent max node's max_min value, cut off return cur beta = min(beta, cur) # update current min_max for this node return cur # cut off didn't occur, return min_max
def compute_heuristic(board, color): #not implemented, optional # IMPLEMENT # Board size d = len(board) # Minimize the number of disks the opponent result = get_score(board) if color == 1: utility = result[0] - result[1] else: utility = result[1] - result[0] # Minimize the number of moves the opponent can make dark_moves = len(get_possible_moves(board, 1)) light_moves = len(get_possible_moves(board, 2)) if color == 1: mobility = dark_moves - light_moves else: mobility = light_moves - dark_moves # Check board size to prevent index out of range if d < 4: return utility + mobility # Now the rest satisfies board size >= 4 dark = 0 light = 0 # Highly value taking corner fields corners = [(0, 0), (d - 1, 0), (0, d - 1), (d - 1, d - 1)] for (column, row) in corners: if board[column][row] == 1: dark += 500 elif board[column][row] == 2: light += 500 # Highly penalize taking the fields next to the corners near_corners = [(1, 0), (0, 1), (1, d - 1), (d - 1, 1), (0, d - 2), (d - 2, 0), (d - 1, d - 2), (d - 2, d - 1)] for (column, row) in near_corners: if board[column][row] == 1: dark -= 50 elif board[column][row] == 2: light -= 50 # Value other border tiles than remaining tiles for i in range(2, d - 2): edges = [(0, i), (i, 0), (i, d - 1), (d - 1, i)] for (column, row) in edges: if board[column][row] == 1: dark += 50 elif board[column][row] == 2: light += 50 if color == 1: weight = dark - light else: weight = light - dark return utility + mobility + weight
def alphabeta_max_node(board, color, alpha, beta, limit, caching = 0, ordering = 0): #IMPLEMENT # current player is MIN global cache if color==1: other_color=2 else: other_color=1 state = board if caching==1: if state in cache: return cache[state] if limit == 0: return None, compute_utility(board,color) # name ordering heuristics if not ordering_heuristic(board,get_possible_moves(board,color),other_color,color,ordering,True): # terminal status, return utility value res = None, compute_utility(board, color) if caching == 1: cache[state] = res return res best_score = float('-Inf') best_move = None limit-=1 for move in ordering_heuristic(board,get_possible_moves(board,color),color,color,ordering,True): tmp_move, score = alphabeta_min_node(play_move(board,color, move[0],move[1]), color, alpha, beta, limit, caching) if best_score < score: best_score = score best_move = move if beta < best_score: beta = best_score if beta <= alpha: break res = best_move, best_score if caching == 1: cache[state] = res return res
def select_move_alphabeta(board, color, limit, caching=0, ordering=0): """ Given a board and a player color, decide on a move. The return value is a tuple of integers (i,j), where i is the column and j is the row on the board. Note that other parameters are accepted by this function: If limit is a positive integer, your code should enfoce a depth limit that is equal to the value of the parameter. Search only to nodes at a depth-limit equal to the limit. If nodes at this level are non-terminal return a heuristic value (see compute_utility) If caching is ON (i.e. 1), use state caching to reduce the number of state evaluations. If caching is OFF (i.e. 0), do NOT use state caching to reduce the number of state evaluations. If ordering is ON (i.e. 1), use node ordering to expedite pruning and reduce the number of state evaluations. If ordering is OFF (i.e. 0), do NOT use node ordering to expedite pruning and reduce the number of state evaluations. """ #IMPLEMENT if get_possible_moves(board, color) == None or limit == 0: return compute_utility(board, color) global have_seen_this_before infinity = float('inf') bestval = -infinity beta = infinity suc_boards = [] for m in get_possible_moves(board, color): suc_boards.append((play_move(board, color, m[0], m[1]), m)) for state, m in suc_boards: move, value = alphabeta_min_node(state, color, bestval, beta, limit - 1, caching) if value > bestval: bestval = value bestmove = m return bestmove #change this!
def minimax_max_node(board, color, limit, caching=0): #returns highest possible utility #IMPLEMENT if caching == 1: if board in have_seen_this_before: return ([], have_seen_this_before[board]) if get_possible_moves(board, color) == []: if not board in have_seen_this_before: have_seen_this_before[board] = compute_utility(board, color) return ([], compute_utility(board, color)) if limit == 0: if not board in have_seen_this_before: have_seen_this_before[board] = compute_heuristic(board, color) return ([], compute_heuristic(board, color)) if limit == -1: limit = limit else: limit = limit - 1 infinity = float('inf') maxval = -infinity suc_boards = [] for m in get_possible_moves(board, color): suc_boards.append((play_move(board, color, m[0], m[1]), m)) for state, m in suc_boards: a = maxval maxval = max(maxval, minimax_min_node(state, color, limit, caching)[1]) if a != maxval: #if maxval changed, we store the move move = m if not board in have_seen_this_before: have_seen_this_before[board] = maxval return (move, maxval)
def compute_mobility(board, color): dark, light = len(get_possible_moves(board, 1)), len(get_possible_moves(board, 2)) mobility = dark - light if color == 2: mobility = -mobility return mobility
def compute_movability(board, color): #this function computes movability as a subtraction in the number of possible moves of the players dark, light = len(get_possible_moves(board, 1)), len(get_possible_moves(board,2)) if color == 1: return dark-light else: return light-dark
def minimax_max_node(board, color, level): if len(get_possible_moves(board,color)) == 0 or level > 2: return compute_utility(board, color) else: lst = [] for c in get_possible_moves(board,color): lst.append(minimax_min_node(play_move(board,color,c[0],c[1]),color, level+1)) return max(lst)
def alphabeta_max_node(board, color, alpha, beta, level): if len(get_possible_moves(board,color)) == 0 or level > 3: return compute_utility(board, color) else: for c in get_possible_moves(board,color): new_board = play_move(board,color,c[0],c[1]) value = alphabeta_min_node(new_board,color,alpha,math.inf,level+1) if value > alpha: alpha = value if beta > alpha: return alpha return alpha
def compute_heuristic(board, color): # not implemented, optional # IMPLEMENT weight = 0 opp_color = 1 if color == 1: opp_color = 2 sidelen = len(board) # Try to restrain opponent moves - mobility my_moves = get_possible_moves(board, color) opp_moves = get_possible_moves(board, opp_color) if my_moves + opp_moves > 0: weight += 100 * ((my_moves - opp_moves) / (my_moves + opp_moves)) # Parity score = get_score(board) weight += 100 * (compute_utility(board, color) / (score[0] + score[1])) # Corners my_corners = 0 opp_corners = 0 nw_corner = board[0][0] ne_corner = board[0][sidelen - 1] sw_corner = board[sidelen - 1][0] se_corner = board[sidelen - 1][sidelen - 1] if nw_corner == color: my_corners += 1 elif nw_corner == opp_color: opp_corners += 1 if ne_corner == color: my_corners += 1 elif ne_corner == opp_color: opp_corners += 1 if sw_corner == color: my_corners += 1 elif sw_corner == opp_color: opp_corners += 1 if se_corner == color: my_corners += 1 elif se_corner == opp_color: opp_corners += 1 if my_corners + opp_corners > 0: weight += 100 * ((my_corners - opp_corners) / (my_corners + opp_corners)) # Stability return weight
def alphabeta_max_node(board, color, alpha, beta): if not get_possible_moves(board, color): return compute_utility(board, color) v = -math.inf for a in get_possible_moves(board, color): #color v = max( v, alphabeta_min_node(play_move(board, color, a[0], a[1]), color, alpha, beta)) if v >= beta: return v alpha = max(alpha, v) return v
def minimax_min_node(board, color, limit, caching=0): if color == 1: possible_moves = get_possible_moves(board, 2) if limit == 0 or len(possible_moves) == 0: return (None, compute_utility(board, color)) best_utility = len(board)**2 best_move = None for move in possible_moves: board_ = play_move(board, 2, move[0], move[1]) if caching != 0 and board_ not in board_utility: max_node = minimax_max_node(board_, color, limit - 1, caching) board_utility[board_] = max_node if max_node[1] < best_utility: best_utility = max_node[1] best_move = move elif caching != 0 and board_ in board_utility: max_node = board_utility[board_] if max_node[1] < best_utility: best_utility = max_node[1] best_move = move else: max_node = minimax_max_node(board_, color, limit - 1, caching) if max_node[1] < best_utility: best_utility = max_node[1] best_move = move return (best_move, best_utility) else: possible_moves = get_possible_moves(board, 1) if limit == 0 or len(possible_moves) == 0: return (None, compute_utility(board, color)) best_utility = len(board)**2 best_move = None for move in possible_moves: board_ = play_move(board, 1, move[0], move[1]) if caching != 0 and board_ not in board_utility: max_node = minimax_max_node(board_, color, limit - 1, caching) board_utility[board_] = max_node if max_node[1] < best_utility: best_utility = max_node[1] best_move = move elif caching != 0 and board_ in board_utility: max_node = board_utility[board_] if max_node[1] < best_utility: best_utility = max_node[1] best_move = move else: max_node = minimax_max_node(board_, color, limit - 1, caching) if max_node[1] < best_utility: best_utility = max_node[1] best_move = move return (best_move, best_utility)
def alphabeta_min_node(board, color, alpha, beta, level): opp_color = 1 if color==2 else 2 if len(get_possible_moves(board,opp_color)) == 0 or level > 3: return compute_utility(board, color) else: for c in get_possible_moves(board,opp_color): new_board = play_move(board,opp_color,c[0],c[1]) value = alphabeta_max_node(new_board,color,-math.inf,beta,level+1) if value < beta: beta = value if alpha > beta: return beta return beta
def alphabeta_min_node(board, color, alpha, beta, limit, caching=0, ordering=0): #IMPLEMENT max_color = color min_color = 3 - color if caching == 1: if board in have_seen_this_before: return ([], have_seen_this_before[board]) if get_possible_moves(board, min_color) == []: if not board in have_seen_this_before: have_seen_this_before[board] = compute_utility(board, max_color) return ([], compute_utility(board, max_color)) if limit == 0: if not board in have_seen_this_before: have_seen_this_before[board] = compute_heuristic(board, max_color) return ([], compute_heuristic(board, max_color)) if limit == -1: limit = limit else: limit = limit - 1 infinity = float('inf') val = infinity suc_boards = [] move = [] for m in get_possible_moves(board, min_color): new_board = play_move(board, min_color, m[0], m[1]) suc_boards.append((new_board, m, compute_utility(new_board, max_color))) if ordering == 1: suc_boards = sorted(suc_boards, key=lambda x: x[2], reverse=True) for state, m, disks in suc_boards: a = val val = min( val, alphabeta_max_node(state, max_color, alpha, beta, limit, caching)[1]) if val <= alpha: return (m, val) beta = min(beta, val) if a != val: move = m if not board in have_seen_this_before: have_seen_this_before[board] = val return (move, val)
def minimax(board, depth, isMaximize, color): if depth == 0 or not get_possible_moves(board, color): #if not get_possible_moves(board, color): score1, score2 = get_score(board) if (color == 1): return score1, None elif (color == 2): return score2, None newBoards = list() moves = dict() possibleMoves = get_possible_moves(board, color) for i in range(len(possibleMoves)): newBoards.append( play_move(board, color, possibleMoves[i][0], possibleMoves[i][1])) moves[play_move(board, color, possibleMoves[i][0], possibleMoves[i][1])] = possibleMoves[i] if isMaximize: maxVal = -(float("inf")) bestMove = moves[newBoards[0]] for i in range(len(newBoards)): val, move = minimax(newBoards[i], depth - 1, False, color) maxVal = max(maxVal, val) #Add the move for return when the new value is better if (max(maxVal, val) == val): bestMove = moves[newBoards[i]] #currentMove = move if (color == 1): color = 2 else: color = 1 return maxVal, bestMove else: minVal = float('inf') bestMove = moves[newBoards[0]] for i in range(len(newBoards)): val, move = minimax(newBoards[i], depth - 1, True, color) minVal = min(minVal, val) #Add the move for return when the new value is better if (min(minVal, val) == val): bestMove = moves[newBoards[i]] #currentMove = move if (color == 1): color = 2 else: color = 1 return minVal, bestMove
def minimax_max_node(board, color): """ Return the minimum score the MAX player can achieve with any move starting at board. """ if not get_possible_moves(board, color): return compute_utility(board, color) alpha = -math.inf for i, j in get_possible_moves(board, color): new_board = play_move(board, color, i, j) utility = minimax_min_node(new_board, color) if utility > alpha: alpha = utility return alpha
def alphabeta_max_node(board, color, beta, level, limit): if not get_possible_moves(board, color): return compute_utility(board, color) alpha = -math.inf for i, j in get_possible_moves(board, color): new_board = play_move(board, color, i, j) if level + 1 > limit: continue utility = alphabeta_min_node(new_board, color, alpha, level + 1, limit) if utility > alpha: alpha = utility if alpha > beta: return alpha return alpha
def compute_heuristic(board, color): # not implemented, optional # Utility utility = compute_utility(board, color) dark, light = len(get_possible_moves(board, 1)), len(get_possible_moves(board, 2)) if color == 1: mobility = dark - light else: mobility = light - dark n = len(board) if n < 4: return utility + mobility dark, light = 0, 0 corners = [(0, 0), (-1, 0), (0, -1), (-1, -1)] for (i, j) in corners: if board[i][j] == 1: dark += 10000 elif board[i][j] == 2: light += 10000 corner_neighbors = [ (1, 0), (0, 1), (0, -2), (1, -1), (-2, 0), (-1, 1), (-1, -2), (-2, -1), ] for (i, j) in corner_neighbors: if board[i][j] == 1: dark -= 100 elif board[i][j] == 2: light -= 100 if n > 4: for i in range(2, n - 2): edge_vales = [(0, i), (i, 0), (i, -1), (-1, i)] for (row, col) in edge_vales: if board[row][col] == 1: dark += 100 elif board[row][col] == 2: light += 100 if color == 1: score = dark - light else: score = light - dark return utility + mobility + score
def alphabeta_max_node(board, color, alpha, beta, depth, maxdepth): if depth > maxdepth or len(get_possible_moves(board, color)) <= 0: if hash(board) not in seen_boards: seen_boards.add(hash(board)) seen_boards_cost[hash(board)] = compute_utility(board, color) return seen_boards_cost[hash(board)] else: nowAlpha = alpha bestVal = -3000000000 bestCheck = [] for x in get_possible_moves(board, color): playedBoard = play_move(board, color, x[0], x[1]) if playedBoard not in seen_boards_heuristic: seen_boards_heuristic.add(playedBoard) seen_boards_heuristic_cost[playedBoard] = get_heuristic( playedBoard, color) #toCheck = alphabeta_max_node(playedBoard, color, switch_color(currentColor), alpha, nowBeta, depth + 1, maxdepth ) bestCheck.append( (seen_boards_heuristic_cost[playedBoard], playedBoard)) bestCheck.sort() bestCheck.reverse() for x, playedBoard in bestCheck: #playedBoard = play_move(board, currentColor, x[0], x[1]) #toCheck = alphabeta_min_node(playedBoard, color, switch_color(currentColor), nowAlpha, beta, depth + 1, maxdepth ) toCheck = 0 if playedBoard not in seen_boards_min: seen_boards_min.add(playedBoard) seen_boards_min_cost[playedBoard] = alphabeta_min_node( playedBoard, color, nowAlpha, beta, depth + 1, maxdepth) toCheck = seen_boards_min_cost[playedBoard] toCheck += random.randint(-volatile_level, volatile_level) if toCheck > bestVal: bestVal = toCheck if bestVal > nowAlpha: nowAlpha = bestVal if nowAlpha >= beta: break #return max(childrenNodes) return bestVal
def ab(board, player, maxing, maxDepth, depth=0, alpha=-1000, beta=1000): if depth == maxDepth: return heuristic2(board, color), [] boards = get_possible_moves(board, player) if len(boards) == 0: return heuristic2(board, color), [] if maxing: bestScore = -1000 bestBoard = 0 bestMove = boards[0] for b in boards: if time.perf_counter() - startTime >= maxTime: return bestScore, bestMove movetoBoard = play_move(board, player, b[0], b[1]) score, move = ab(movetoBoard, player % 2 + 1, False, maxDepth, depth + 1, alpha, beta) if score > bestScore: bestBoard = movetoBoard bestScore = score bestMove = b alpha = max(alpha, bestScore) # Alpha Beta Pruning if beta <= alpha: break return bestScore, bestMove else: bestScore = 1000 bestBoard = 0 bestMove = boards[0] for b in boards: if time.perf_counter() - startTime >= maxTime: return bestScore, bestMove movetoBoard = play_move(board, player, b[0], b[1]) score, move = ab(movetoBoard, player % 2 + 1, True, maxDepth, depth + 1, alpha, beta) if score < bestScore: bestBoard = movetoBoard bestScore = score bestMove = b beta = min(beta, bestScore) # Alpha Beta Pruning if beta <= alpha: break return bestScore, bestMove
def minimax_min_node(board, color, depth): other_color = 1 if color == 2 else 2 possible_moves = get_possible_moves(board, other_color) move_states = {move : play_move(board, other_color, move[0], move[1]) for move in possible_moves} best_move = None best_value = None if len(possible_moves) > 0: if depth <= 3: for move, state in move_states.items(): if best_move == None or minimax_max_node(state, color, depth + 1) < best_value: best_move = move best_value = minimax_max_node(state, color, depth + 1) return best_value else: for move, state in move_states.items(): if best_value == None or compute_utility(state, color) < best_value: best_value = compute_utility(state, color) return best_value return compute_utility(board, color)
def minimax_min_node(board, color): '''returns the value of the min node''' opponent = switch_color(color) min_val = 1000 if is_leaf_node(board, opponent): if board not in board_values: board_values[board] = compute_utility(board, color) return board_values[board] # if not a leaf node compute minimum of children's maximum moves = get_possible_moves(board, opponent) successors = [] #play the moves and get the states map(lambda x: successors.append(play_move(board, opponent, x[0], x[1])), moves) successors.sort(key=lambda x: compute_utility(x, color)) for successor in successors: if successor not in board_values: board_values[successor] = minimax_max_node(successor, color) val = board_values[successor] min_val = min(min_val, val) return min_val
def alphabeta_max_node(board, color, alpha, beta, level, limit): '''returns the value of the node''' max_val = -1000 if is_leaf_node(board, opponent) or level==limit: if board not in board_values: board_values[board] = compute_utility(board, color) return board_values[board] # if not a leaf node. Need to compute the maximum of the mimimum of the children nodes moves = get_possible_moves(board, color) successors = [] map(lambda x: successors.append(play_move(board, color, x[0], x[1])), moves) successors.sort(key=lambda x: compute_utility(x, color), reverse=True) for successor in successors: # successor = play_move(board, color, move[0], move[1]) if successor not in board_values: board_values[successor] = alphabeta_min_node(successor, color, alpha, beta, level+1, limit) val = board_values[successor] max_val = max(max_val, val) if max_val >= beta: return max_val alpha = max(alpha, max_val) return max_val
def alphabeta_min_node(board, color, alpha, beta, level, limit): '''returns the value of the min node''' opponent = switch_color(color) min_val = 1000 if is_leaf_node(board, opponent) or level == limit: if board not in board_values: board_values[board] = compute_utility(board, color) return board_values[board] # if not a leaf node compute minimum of children's maximum moves = get_possible_moves(board, opponent) successors = [] #play the moves and get the states map(lambda x: successors.append(play_move(board, opponent, x[0], x[1])), moves) successors.sort(key=lambda x: compute_utility(x, color)) for successor in successors: # successor = play_move(board, opponent, move[0], move[1]) if successor not in board_values: board_values[successor] = alphabeta_max_node(successor, color, alpha, beta, level+1, limit) val = board_values[successor] min_val = min(min_val, val) if min_val <= alpha: return min_val beta = min(beta, min_val) return min_val
def ai_move(self): player_obj = self.players[self.game.current_player] try: i, j = player_obj.get_move(self.game) player = "Dark" if self.game.current_player == 1 else "Light" player = "{} {}".format(player_obj.name, player) self.log("{}: {},{}".format(player, i, j)) self.game.play(i, j) self.draw_board() if not get_possible_moves(self.game.board, self.game.current_player): if get_score(self.game.board)[0] > get_score( self.game.board)[1]: self.shutdown("Dark wins!") elif get_score(self.game.board)[0] < get_score( self.game.board)[1]: self.shutdown("White wins!") else: self.shutdown("Tie!") elif isinstance(self.players[self.game.current_player], AiPlayerInterface): self.root.after(1, lambda: self.ai_move()) else: self.root.bind("<Button-1>", lambda e: self.mouse_pressed(e)) except AiTimeoutError: self.shutdown("Game Over, {} lost (timeout)".format( player_obj.name))
def minimax_max_node(board, color, limit, caching=0): # returns highest possible utility # IMPLEMENT if caching == 1 and (board, color) in cache_board: return cache_board[(board, color)] actions = get_possible_moves(board, color) if len(actions) == 0 or limit == 0: utility = compute_heuristic(board, color) if caching: cache_board[(board, color)] = (None, utility) return (None, utility) best_move = None value = float('-inf') for move in actions: new_board = play_move(board, color, move[0], move[1]) nxt_move, nxt_val = minimax_min_node(new_board, color, limit - 1) if value < nxt_val: value, best_move = nxt_val, move if caching == 1: cache_board[(board, color)] = (best_move, value) return (best_move, value)
def minimax_min_node(board, color, limit, caching=0): # IMPLEMENT if caching == 1 and (board, color) in cache_board: return cache_board[(board, color)] opponent_color = 1 if color == 1: opponent_color = 2 actions = get_possible_moves(board, opponent_color) if not actions or limit == 0: if caching: cache_board[(board, color)] = (None, compute_heuristic(board, color)) return (None, compute_heuristic(board, color)) best_move = None value = float('inf') for move in actions: nxt_board = play_move(board, opponent_color, move[0], move[1]) nxt_move, nxt_val = minimax_max_node(nxt_board, color, limit - 1) if value > nxt_val: value, best_move = nxt_val, move if caching == 1: cache_board[(board, color)] = (best_move, value) return (best_move, value)
def alphabeta_max_node(board, color, alpha, beta, level, limit): if board in move_dictionary: return move_dictionary[board] # Retrieval from cached board states else: max_next_nodes = get_possible_moves(board, color) utility_list = [] if (len(max_next_nodes) == 0 or limit <= level): return compute_utility(board, color) # Leaf node reached v = float("-inf") next_dict = dict() for items in max_next_nodes: successor_board = play_move(board, color, items[0], items[1]) next_dict.update( {successor_board: compute_utility(successor_board, color)}) #alpha-beta pruning logic - with node ordering heuristic for key in sorted(next_dict, key=next_dict.get): v = max( v, alphabeta_min_node(key, color, alpha, beta, level + 1, limit)) if v >= beta: return v alpha = max(alpha, v) move_dictionary.update({board: v}) #Caching of the board states return v