def random(side, board, flags, chooser): ''' Return a random move, resulting board, and value of the resulting board. Return: (value, moveList, boardList) value (int or float): value of the board after making the chosen move moveList (list): list with one element, the chosen move moveTree (dict: encode(*move)->dict): a tree of moves that were evaluated in the search process Input: side (boolean): True if player1 (Min) plays next, otherwise False board (2-tuple of lists): current board layout, used by generateMoves and makeMove flags (list of flags): list of flags, used by generateMoves and makeMove chooser: a function similar to random.choice, but during autograding, might not be random. ''' moves = [move for move in generateMoves(side, board, flags)] print('moves: ', moves) if len(moves) > 0: move = chooser(moves) newside, newboard, newflags = makeMove(side, board, move[0], move[1], flags, move[2]) value = evaluate(newboard) print('value: ', value) print('[move]: ', [move]) print('{ encode(*move): {} }: ', {encode(*move): {}}) return (value, [move], {encode(*move): {}}) else: return (evaluate(board), [], {})
def alphabeta(side, board, flags, depth, alpha=-math.inf, beta=math.inf): ''' Return minimax-optimal move sequence, and a tree that exhibits alphabeta pruning. Return: (value, moveList, moveTree) value (float): value of the final board in the minimax-optimal move sequence moveList (list): the minimax-optimal move sequence, as a list of moves moveTree (dict: encode(*move)->dict): a tree of moves that were evaluated in the search process Input: side (boolean): True if player1 (Min) plays next, otherwise False board (2-tuple of lists): current board layout, used by generateMoves and makeMove flags (list of flags): list of flags, used by generateMoves and makeMove depth (int >=0): depth of the search (number of moves) ''' moves = [move for move in generateMoves(side, board, flags)] moveTree = {} if len(moves) == 0 or depth == 0: return evaluate(board), [], moveTree best_val = None best_movelist = [] if side: # Min best_val = math.inf for move in moves: newside, newboard, newflags = makeMove(side, board, move[0], move[1], flags, move[2]) poss_val, poss_movelist, poss_movetree = alphabeta( newside, newboard, newflags, depth - 1, alpha, beta) moveTree[encode(*move)] = poss_movetree if poss_val < best_val: best_val = poss_val best_movelist = [move] + poss_movelist beta = min(beta, best_val) if beta <= alpha: return best_val, best_movelist, moveTree else: # Max best_val = -math.inf for move in moves: newside, newboard, newflags = makeMove(side, board, move[0], move[1], flags, move[2]) poss_val, poss_movelist, poss_movetree = alphabeta( newside, newboard, newflags, depth - 1, alpha, beta) moveTree[encode(*move)] = poss_movetree if poss_val > best_val: best_val = poss_val best_movelist = [move] + poss_movelist alpha = max(alpha, best_val) if alpha >= beta: return best_val, best_movelist, moveTree return best_val, best_movelist, moveTree
def alphabeta(side, board, flags, depth, alpha=-math.inf, beta=math.inf): ''' Return minimax-optimal move sequence, and a tree that exhibits alphabeta pruning. Return: (value, moveList, moveTree) value (float): value of the final board in the minimax-optimal move sequence moveList (list): the minimax-optimal move sequence, as a list of moves moveTree (dict: encode(*move)->dict): a tree of moves that were evaluated in the search process Input: side (boolean): True if player1 (Min) plays next, otherwise False board (2-tuple of lists): current board layout, used by generateMoves and makeMove flags (list of flags): list of flags, used by generateMoves and makeMove depth (int >=0): depth of the search (number of moves) ''' moves = [move for move in generateMoves(side, board, flags)] if depth == 0 or len(moves) == 0: return (evaluate(board), [], {}) if side == False: move_tree = {} move_list = [] target = -math.inf for move in moves: newside, newboard, newflags = makeMove(side, board, move[0], move[1], flags, move[2]) value, l, t = alphabeta(newside, newboard, newflags, depth - 1, alpha, beta) move_tree[encode(*move)] = t if value > target: target = value alpha = max(target, alpha) max_move = move move_list = l if alpha >= beta: break move_list.insert(0, max_move) return (target, move_list, move_tree) else: move_tree = {} move_list = [] target = math.inf for move in moves: newside, newboard, newflags = makeMove(side, board, move[0], move[1], flags, move[2]) value, l, t = alphabeta(newside, newboard, newflags, depth - 1, alpha, beta) move_tree[encode(*move)] = t if value < target: target = value beta = min(target, beta) min_move = move move_list = l if alpha >= beta: break move_list.insert(0, min_move) return (target, move_list, move_tree)
def minimax(side, board, flags, depth): ''' Return a minimax-optimal move sequence, tree of all boards evaluated, and value of best path. Return: (value, moveList, moveTree) value (float): value of the final board in the minimax-optimal move sequence moveList (list): the minimax-optimal move sequence, as a list of moves moveTree (dict: encode(*move)->dict): a tree of moves that were evaluated in the search process Input: side (boolean): True if player1 (Min) plays next, otherwise False board (2-tuple of lists): current board layout, used by generateMoves and makeMove flags (list of flags): list of flags, used by generateMoves and makeMove depth (int >=0): depth of the search (number of moves) ''' value = math.inf if side == True else -math.inf moveList = [] moveTree = {} bestMove = [] # create minimax tree move:(next move, heuristic) # bubble up from leaves # base case if depth == 1: # find all possible moves at board state for move in generateMoves(side, board, flags): moveTree[encode(move[0], move[1], move[2])] = {} newside, newboard, newflags = makeMove(side, board, move[0], move[1], flags, move[2]) # check if move is optimal if side == True and evaluate( newboard ) < value: # min player which means wants lowest value value = evaluate(newboard) bestMove = move elif side == False and evaluate(newboard) > value: value = evaluate(newboard) bestMove = move moveList.append(bestMove) return value, moveList, moveTree for move in generateMoves(side, board, flags): moveTree[encode(move[0], move[1], move[2])] = {} newside, newboard, newflags = makeMove(side, board, move[0], move[1], flags, move[2]) minmax = minimax(newside, newboard, newflags, depth - 1) moveTree[encode(move[0], move[1], move[2])] = minmax[2] moves = minmax[2] if side == True and minmax[ 0] < value: # min player which means wants lowest value value = minmax[0] bestMove = move moveList = minmax[1] elif side == False and minmax[0] > value: value = minmax[0] bestMove = move moveList = minmax[1] moveList.insert(0, bestMove) return value, moveList, moveTree
def minimax(side, board, flags, depth): ''' Return a minimax-optimal move sequence, tree of all boards evaluated, and value of best path. Return: (value, moveList, moveTree) value (float): value of the final board in the minimax-optimal move sequence moveList (list): the minimax-optimal move sequence, as a list of moves moveTree (dict: encode(*move)->dict): a tree of moves that were evaluated in the search process Input: side (boolean): True if player1 (Min) plays next, otherwise False board (2-tuple of lists): current board layout, used by generateMoves and makeMove flags (list of flags): list of flags, used by generateMoves and makeMove depth (int >=0): depth of the search (number of moves) ''' # Base case if depth == 0: return evaluate(board), [], {} moves = {} moveTrees = {} moveLists = {} # Go through all possible moves for move in generateMoves(side, board, flags): newside, newboard, newflags = makeMove(side, board, move[0], move[1], flags, move[2]) # Fan out next level of tree score, moveList, moveTree = minimax(newside, newboard, newflags, depth - 1) moves[encode(*move)] = score moveTrees[encode(*move)] = moveTree moveLists[encode(*move)] = moveList # Choose move based on side optimization if len(moves) > 0: if side: best_move = min(moves, key=moves.get) else: best_move = max(moves, key=moves.get) else: return evaluate(board), [], {} newMoveList = [] if moveLists[best_move] != None and len(moveLists[best_move]) >= 0: newMoveList = [decode(best_move), *moveLists[best_move]] # print(moves) # print(moveTrees) return moves[best_move], newMoveList, moveTrees
def stochastic(side, board, flags, depth, breadth, chooser): ''' Choose the best move based on breadth randomly chosen paths per move, of length depth-1. Return: (value, moveList, moveTree) value (float): average board value of the paths for the best-scoring move moveLists (list): any sequence of moves, of length depth, starting with the best move moveTree (dict: encode(*move)->dict): a tree of moves that were evaluated in the search process Input: side (boolean): True if player1 (Min) plays next, otherwise False board (2-tuple of lists): current board layout, used by generateMoves and makeMove flags (list of flags): list of flags, used by generateMoves and makeMove depth (int >=0): depth of the search (number of moves) breadth: number of different paths chooser: a function similar to random.choice, but during autograding, might not be random. ''' value = 0 moveList = [] moveTree = {} initialMoves = [] if side == True: # black moves for move in generateMoves(side, board, flags): newside, newboard, newflags = makeMove(side, board, move[0], move[1], flags, move[2]) moveTree[encode(move[0], move[1], move[2])] = {} # print("\nmove: ",move,"\nvalue: ",evaluate(newboard)) # find all possible next moves random = stochastic_helper(newside, newboard, newflags, depth - 1, breadth, chooser, depth) # print("next value: ",random[0]) moveTree[encode(move[0], move[1], move[2])] = random[2] random[1].insert(0, move) initialMoves.append((random[0], random[1])) value, moveList = min(initialMoves) else: # white moves for move in generateMoves(side, board, flags): newside, newboard, newflags = makeMove(side, board, move[0], move[1], flags, move[2]) moveTree[encode(move[0], move[1], move[2])] = {} # find all possible next moves random = stochastic_helper(newside, newboard, newflags, depth - 1, breadth, chooser, depth) moveTree[encode(move[0], move[1], move[2])] = random[2] random[1].insert(0, move) initialMoves.append((random[0], random[1])) value, moveList = max(initialMoves) # print(initialMoves) # print(min(initialMoves)) # print(value) # print(moveList) # print(moveTree) return value, moveList, moveTree
def minimax(side, board, flags, depth): ''' Return a minimax-optimal move sequence, tree of all boards evaluated, and value of best path. Return: (value, moveList, moveTree) value (float): value of the final board in the minimax-optimal move sequence moveList (list): the minimax-optimal move sequence, as a list of moves moveTree (dict: encode(*move)->dict): a tree of moves that were evaluated in the search process Input: side (boolean): True if player1 (Min) plays next, otherwise False board (2-tuple of lists): current board layout, used by generateMoves and makeMove flags (list of flags): list of flags, used by generateMoves and makeMove depth (int >=0): depth of the search (number of moves) ''' moves = [move for move in generateMoves(side, board, flags)] if depth == 0 or len(moves) == 0: return (evaluate(board), [], {}) if not side: max_value = -math.inf move_tree = {} move_list = [] for move in moves: newside, newboard, newflags = makeMove(side, board, move[0], move[1], flags, move[2]) value, l, t = minimax(newside, newboard, newflags, depth - 1) move_tree[encode(*move)] = t if value > max_value: max_value = value max_move = move move_list = l move_list.insert(0, max_move) return (max_value, move_list, move_tree) else: min_value = math.inf move_tree = {} move_list = [] for move in moves: newside, newboard, newflags = makeMove(side, board, move[0], move[1], flags, move[2]) value, l, t = minimax(newside, newboard, newflags, depth - 1) move_tree[encode(*move)] = t if value < min_value: min_value = value min_move = move move_list = l move_list.insert(0, min_move) return (min_value, move_list, move_tree)
def stochastic(side, board, flags, depth, breadth, chooser): ''' Choose the best move based on breadth randomly chosen paths per move, of length depth-1. Return: (value, moveList, moveTree) value (float): average board value of the paths for the best-scoring move moveLists (list): any sequence of moves, of length depth, starting with the best move moveTree (dict: encode(*move)->dict): a tree of moves that were evaluated in the search process Input: side (boolean): True if player1 (Min) plays next, otherwise False board (2-tuple of lists): current board layout, used by generateMoves and makeMove flags (list of flags): list of flags, used by generateMoves and makeMove depth (int >=0): depth of the search (number of moves) breadth: number of different paths chooser: a function similar to random.choice, but during autograding, might not be random. ''' moves = [move for move in generateMoves(side, board, flags)] moveList = [] moveTree = {} if len(moves) == 0 or depth == 0 or breadth == 0: return evaluate(board), moveList, moveTree init_values = [] init_move_lists = [] for move in moves: init_side, init_board, init_flags = makeMove(side, board, move[0], move[1], flags, move[2]) init_movetree = {} value_sum = 0 rand_movelist = None for i in range(breadth): val, rand_movelist, rand_movetree = stoch_path( init_side, init_board, init_flags, depth - 1, chooser) value_sum += val init_movetree.update(rand_movetree) init_values.append(value_sum / breadth) init_move_lists.append([move]) moveTree[encode(*move)] = init_movetree if side: # Min min_val = math.inf min_path = None for i in range(len(init_values)): if init_values[i] < min_val: min_val = init_values[i] min_path = init_move_lists[i] return min_val, min_path, moveTree else: # Max max_val = -math.inf max_path = None for i in range(len(init_values)): if init_values[i] > max_val: max_val = init_values[i] max_path = init_move_lists[i] return max_val, max_path, moveTree
def stochastic_helper(side, board, flags, level, breadth, chooser, depth): value = 0 moveList = [] moveTree = {} moves = [] initialMoves = [] # base case if level == 0: return evaluate(board), moveList, moveTree # create list of possible moves for chooser for move in generateMoves(side, board, flags): moves.append(move) if level == depth - 1: for i in range(breadth): move = chooser(moves) newside, newboard, newflags = makeMove(side, board, move[0], move[1], flags, move[2]) random = stochastic_helper(newside, newboard, newflags, level - 1, breadth, chooser, depth) moveTree[encode(move[0], move[1], move[2])] = random[2] random[1].insert(0, move) initialMoves.append((random[0], random[1])) # value = random[0] + evaluate(board) # moveList = random[1].copy() # moveList.insert(0,move) # print(initialMoves) for next in initialMoves: value += next[0] value = value / breadth if side == True: moveList = min(initialMoves)[1] else: moveList = max(initialMoves)[1] # print(initialMoves) else: move = chooser(moves) newside, newboard, newflags = makeMove(side, board, move[0], move[1], flags, move[2]) random = stochastic_helper(newside, newboard, newflags, level - 1, breadth, chooser, depth) moveTree[encode(move[0], move[1], move[2])] = random[2] value = random[0] moveList = random[1].copy() moveList.insert(0, move) return value, moveList, moveTree
def stochastic(side, board, flags, depth, breadth, chooser): ''' Choose the best move based on breadth randomly chosen paths per move, of length depth-1. Return: (value, moveList, moveTree) value (float): average board value of the paths for the best-scoring move moveLists (list): any sequence of moves, of length depth, starting with the best move moveTree (dict: encode(*move)->dict): a tree of moves that were evaluated in the search process Input: side (boolean): True if player1 (Min) plays next, otherwise False board (2-tuple of lists): current board layout, used by generateMoves and makeMove flags (list of flags): list of flags, used by generateMoves and makeMove depth (int >=0): depth of the search (number of moves) breadth: number of different paths chooser: a function similar to random.choice, but during autograding, might not be random. ''' possibleMoves = generateMoves(side, board, flags) bestAvgValue = 99999 movesTree = {} for move in possibleMoves: totalVal = 0 fro, to, promote = move firstSide, firstBoard, firstFlag = makeMove(side, board, fro, to, flags, promote) movesTree[(encode(*move))] = {} for i in range(breadth): curVal, moveList, moveTree = stochasticPath( firstSide, firstBoard, firstFlag, depth - 1, breadth, chooser) for key in moveTree: movesTree[(encode(*move))][key] = moveTree[key] totalVal += curVal avgVal = float(totalVal) / float(breadth) if avgVal < bestAvgValue: bestAvgValue = avgVal bestMove = [move] + moveList return bestAvgValue, bestMove, movesTree
def stochasticPath(side, board, flags, depth, breadth, chooser): if depth == 0: return evaluate(board), [], {} movesTree = {} # for some reason it needs a list possibleMoves = [move for move in generateMoves(side, board, flags)] thisMove = chooser(possibleMoves) fro, to, promote = thisMove newSide, newBoard, newFlag = makeMove(side, board, fro, to, flags, promote) newVal, newMoveList, newMoveTree = stochasticPath(newSide, newBoard, newFlag, depth - 1, breadth, chooser) movesTree[encode(*thisMove)] = newMoveTree movesList = [thisMove] + newMoveList return newVal, movesList, movesTree
def stoch_path(side, board, flags, depth, chooser): moves = [move for move in generateMoves(side, board, flags)] moveList = [] moveTree = {} if depth == 0 or len(moves) == 0: return evaluate(board), moveList, moveTree rand_move = chooser(moves) newside, newboard, newflags = makeMove(side, board, rand_move[0], rand_move[1], flags, rand_move[2]) path_val, path_list, path_tree = stoch_path(newside, newboard, newflags, depth - 1, chooser) moveTree[encode(*rand_move)] = path_tree moveList = [rand_move] + path_list return path_val, moveList, moveTree
def findPath(side, board, flags, depth, chooser): # print(depth) if depth == 0: return evaluate(board), [], {} else: moves = [] for move in generateMoves(side, board, flags): moves.append(move) if len(moves) == 0: return evaluate(board), [], {} chosen_move = chooser(moves) newside, newboard, newflags = makeMove(side, board, chosen_move[0], chosen_move[1], flags, chosen_move[2]) score, path, tree = findPath(newside, newboard, newflags, depth - 1, chooser) # print(path, [chosen_move, *path]) return score, [chosen_move, *path], {encode(*chosen_move): tree}
def minimax(side, board, flags, depth): ''' Return a minimax-optimal move sequence, tree of all boards evaluated, and value of best path. Return: (value, moveList, moveTree) value (float): value of the final board in the minimax-optimal move sequence moveList (list): the minimax-optimal move sequence, as a list of moves moveTree (dict: encode(*move)->dict): a tree of moves that were evaluated in the search process Input: side (boolean): True if player1 (Min) plays next, otherwise False board (2-tuple of lists): current board layout, used by generateMoves and makeMove flags (list of flags): list of flags, used by generateMoves and makeMove depth (int >=0): depth of the search (number of moves) ''' if depth == 0: return evaluate(board), [], {} possibleMoves = generateMoves(side, board, flags) # maximizing player if side == 0: maxVal = -9999999999 movesTree = {} moveList = [] for move in possibleMoves: fro, to, promote = move newSide, newBoard, newFlag = makeMove(side, board, fro, to, flags, promote) curVal, curMoveList, curMoveTree = minimax(newSide, newBoard, newFlag, depth - 1) movesTree[encode(*move)] = curMoveTree curMax = maxVal maxVal = max(curVal, maxVal) if maxVal > curMax: movesList = [move] + curMoveList return maxVal, movesList, movesTree # minimizing player else: minVal = 9999999999 movesTree = {} moveList = [] for move in possibleMoves: fro, to, promote = move newSide, newBoard, newFlag = makeMove(side, board, fro, to, flags, promote) curVal, curMoveList, curMoveTree = minimax(newSide, newBoard, newFlag, depth - 1) movesTree[encode(*move)] = curMoveTree curMin = minVal minVal = min(minVal, curVal) if minVal < curMin: moveList = [move] + curMoveList return minVal, moveList, movesTree
def stochastic(side, board, flags, depth, breadth, chooser): ''' Choose the best move based on breadth randomly chosen paths per move, of length depth-1. Return: (value, moveList, moveTree) value (float): average board value of the paths for the best-scoring move moveLists (list): any sequence of moves, of length depth, starting with the best move moveTree (dict: encode(*move)->dict): a tree of moves that were evaluated in the search process Input: side (boolean): True if player1 (Min) plays next, otherwise False board (2-tuple of lists): current board layout, used by generateMoves and makeMove flags (list of flags): list of flags, used by generateMoves and makeMove depth (int >=0): depth of the search (number of moves) breadth: number of different paths chooser: a function similar to random.choice, but during autograding, might not be random. ''' initial_moves = [move for move in generateMoves(side, board, flags)] path_avg_score = [] moveTree = {} moveList = [] moveList_dict = {} for m in range(len(initial_moves)): newside, newboard, newflags = makeMove(side, board, initial_moves[m][0], initial_moves[m][1], flags, initial_moves[m][2]) s = newside b = newboard f = newflags breadth_score = [] curr_list = [] curr_list.append(initial_moves[m]) moveTree[encode(*initial_moves[m])] = {} for i in range(breadth): newside = s newflags = f newboard = b curr_dict = moveTree[encode(*initial_moves[m])] for j in range(depth - 1): random_moves = [ move for move in generateMoves(newside, newboard, newflags) ] random_move = chooser(random_moves) if i == 0: curr_list.append(random_move) curr_dict[encode(*random_move)] = {} curr_dict = curr_dict[encode(*random_move)] newside, newboard, newflags = makeMove(newside, newboard, random_move[0], random_move[1], newflags, random_move[2]) final_score = evaluate(newboard) breadth_score.append(final_score) average = sum(breadth_score) / breadth path_avg_score.append(average) moveList_dict[encode(*initial_moves[m])] = curr_list if side: best_average = min(path_avg_score) best_index = path_avg_score.index(best_average) else: best_average = max(path_avg_score) best_index = path_avg_score.index(best_average) moveList = [] best_move = initial_moves[best_index] moveList = moveList_dict[encode(*best_move)] return best_average, moveList, moveTree
def alphabeta(side, board, flags, depth, alpha=-math.inf, beta=math.inf): ''' Return minimax-optimal move sequence, and a tree that exhibits alphabeta pruning. Return: (value, moveList, moveTree) value (float): value of the final board in the minimax-optimal move sequence moveList (list): the minimax-optimal move sequence, as a list of moves moveTree (dict: encode(*move)->dict): a tree of moves that were evaluated in the search process Input: side (boolean): True if player1 (Min) plays next, otherwise False board (2-tuple of lists): current board layout, used by generateMoves and makeMove flags (list of flags): list of flags, used by generateMoves and makeMove depth (int >=0): depth of the search (number of moves) ''' value = 0 moveList = [] moveTree = {} if depth == 0: return evaluate(board), moveList, moveTree if side == False: # max/white value = -math.inf for move in generateMoves(side, board, flags): newside, newboard, newflags = makeMove(side, board, move[0], move[1], flags, move[2]) alphabet = alphabeta(newside, newboard, newflags, depth - 1, alpha, beta) moveTree[encode(move[0], move[1], move[2])] = alphabet[2] if alphabet[0] > value: value = alphabet[0] alphabet[1].insert(0, move) moveList = alphabet[1].copy() alpha = max(alpha, value) if alpha >= beta: # prune check break # print("depth: ",depth) # print(value) # print(moveList) # # print(moveTree) # print("list len: ",len(moveList)) return value, moveList, moveTree else: # min/black value = math.inf for move in generateMoves(side, board, flags): newside, newboard, newflags = makeMove(side, board, move[0], move[1], flags, move[2]) alphabet = alphabeta(newside, newboard, newflags, depth - 1, alpha, beta) moveTree[encode(move[0], move[1], move[2])] = alphabet[2] if alphabet[0] < value: value = alphabet[0] alphabet[1].insert(0, move) moveList = alphabet[1].copy() beta = min(beta, value) if beta <= alpha: # prune check break # print("depth: ", depth) # print(value) # print(moveTree) # print(moveList) return value, moveList, moveTree
def alphabeta(side, board, flags, depth, alpha=-math.inf, beta=math.inf): ''' Return minimax-optimal move sequence, and a tree that exhibits alphabeta pruning. Return: (value, moveList, moveTree) value (float): value of the final board in the minimax-optimal move sequence moveList (list): the minimax-optimal move sequence, as a list of moves moveTree (dict: encode(*move)->dict): a tree of moves that were evaluated in the search process Input: side (boolean): True if player1 (Min) plays next, otherwise False board (2-tuple of lists): current board layout, used by generateMoves and makeMove flags (list of flags): list of flags, used by generateMoves and makeMove depth (int >=0): depth of the search (number of moves) ''' # Base case if depth == 0: return evaluate(board), [], {} moves = {} moveTrees = {} moveLists = {} if side: value = math.inf # Go through all possible moves for move in generateMoves(side, board, flags): newside, newboard, newflags = makeMove(side, board, move[0], move[1], flags, move[2]) # Fan out next level of tree score, moveList, moveTree = alphabeta(newside, newboard, newflags, depth - 1, alpha, beta) moves[encode(*move)] = score moveTrees[encode(*move)] = moveTree moveLists[encode(*move)] = moveList value = value if value < score else score beta = beta if beta < value else value if (beta <= alpha): return score, [], moveTrees else: value = -math.inf # Go through all possible moves for move in generateMoves(side, board, flags): newside, newboard, newflags = makeMove(side, board, move[0], move[1], flags, move[2]) # Fan out next level of tree score, moveList, moveTree = alphabeta(newside, newboard, newflags, depth - 1, alpha, beta) moves[encode(*move)] = score moveTrees[encode(*move)] = moveTree moveLists[encode(*move)] = moveList value = value if value > score else score alpha = alpha if alpha > value else value if (alpha >= beta): return score, [], moveTrees # Choose move based on side optimization if len(moves) > 0: if side: best_move = min(moves, key=moves.get) else: best_move = max(moves, key=moves.get) else: return evaluate(board), [], {} newMoveList = [] if moveLists[best_move] != None and len(moveLists[best_move]) >= 0: newMoveList = [decode(best_move), *moveLists[best_move]] # print(moves) # print("Move tree", moveTrees) return moves[best_move], newMoveList, moveTrees
def stochastic(side, board, flags, depth, breadth, chooser): ''' Choose the best move based on breadth randomly chosen paths per move, of length depth-1. Return: (value, moveList, moveTree) value (float): average board value of the paths for the best-scoring move moveLists (list): any sequence of moves, of length depth, starting with the best move moveTree (dict: encode(*move)->dict): a tree of moves that were evaluated in the search process Input: side (boolean): True if player1 (Min) plays next, otherwise False board (2-tuple of lists): current board layout, used by generateMoves and makeMove flags (list of flags): list of flags, used by generateMoves and makeMove depth (int >=0): depth of the search (number of moves) breadth: number of different paths chooser: a function similar to random.choice, but during autograding, might not be random. ''' def findPath(side, board, flags, depth, chooser): # print(depth) if depth == 0: return evaluate(board), [], {} else: moves = [] for move in generateMoves(side, board, flags): moves.append(move) if len(moves) == 0: return evaluate(board), [], {} chosen_move = chooser(moves) newside, newboard, newflags = makeMove(side, board, chosen_move[0], chosen_move[1], flags, chosen_move[2]) score, path, tree = findPath(newside, newboard, newflags, depth - 1, chooser) # print(path, [chosen_move, *path]) return score, [chosen_move, *path], {encode(*chosen_move): tree} potential_moves = {} # Iteratively looping through all depth level moves for move in generateMoves(side, board, flags): # print(move) # Make move newside, newboard, newflags = makeMove(side, board, move[0], move[1], flags, move[2]) moves = [] for child_move in generateMoves(newside, newboard, newflags): moves.append(child_move) if len(moves) > 0: # Investigate breadth moves and keep track of the best one total_score = 0 best_move = None best_score = best_score = math.inf if side else -math.inf best_move_list = None move_trees = {} for i in range(breadth): chosen_move = chooser(moves) # print("Grandchildren: ", chosen_move) nextMoveSide, nextMoveBoard, nextMoveFlags = makeMove( newside, newboard, chosen_move[0], chosen_move[1], newflags, chosen_move[2]) score, moveList, moveTree = findPath(nextMoveSide, nextMoveBoard, nextMoveFlags, depth - 2, chooser) move_trees[encode(*chosen_move)] = moveTree if (side and score <= best_score) or (not (side) and score >= best_score): best_score = score best_move = chosen_move best_move_list = moveList # print(moveList, best_score, score) total_score += score # print(best_move_list, best_move) # Calculate the average score avg_score = total_score / breadth potential_moves[encode(*move)] = (avg_score, [best_move, *best_move_list], move_trees) else: potential_moves[encode(*move)] = (evaluate(newboard), [], {}) if (len(potential_moves) > 0): best_move = None best_move_list = None move_trees = {} best_score = math.inf if side else -math.inf for move in potential_moves: # print(move, potential_moves[move]) move_trees[move] = potential_moves[move][2] if (side and potential_moves[move][0] <= best_score) or ( not (side) and potential_moves[move][0] >= best_score): # print(move, potential_moves[move]) best_score = potential_moves[move][0] best_move_list = potential_moves[move][1] best_move = move # print([decode(best_move), *best_move_list], {best_move: best_move_tree}) #print(best_score, [decode(best_move), *best_move_list], move_trees) return best_score, [decode(best_move), *best_move_list], move_trees else: return evaluate(board, [], {})