def minimax(self, board: str): """ Does a minimax search. :param board: :return: (move, (val, game_length)): the move to achieve the best val with the longest game for current player. """ """ Your Code Here """ # ----------------------------BEGIN CODE---------------------------- # disclaimer: I did only minimax (as said on the homework description https://drive.google.com/file/d/1JXBi_5JB8fwTWX34j0ZwN5YwjoIexh-Z/view) # my minimax does NOT try to prolong the game. It always goes for the best move! # initializing default values moveToMake = validMoves(board)[0] bestValue = -100 # this will find the best move to go to, since value only returns the best score # util's setMove creates a copy, so we can use that as the successor for validmove in validMoves(board): currentValue = self.value( setMove(board, validmove, whoseMove(board)), 0) if currentValue > bestValue: bestValue = currentValue moveToMake = validmove # currently the val and game_length tuple is arbitrary because i have not implemented depth! return (moveToMake)
def minimax(self, board: str, count: int = 0) -> (int, int, int): """ Does a minimax search. :param board: :param count: The length of the game. A longer count is better. :return: (val, move, count): the best minimax val for current player with longest count. The move to achieve that. """ mark = whoseMove(board) # possMoves are [(val, move, count)] (val in [1, 0, -1]) for move in self.validMoves(board)] # These are the possible moves considering a full minimax analysis. # The recursive call to minimax is made in self.makeAndEvaluateMove(board, move, mark, count+1) possMoves = [ self.makeAndEvaluateMove(board, move, mark, count + 1) for move in validMoves(board) ] minOrMax = max if mark == XMARK else min (bestVal, _, _) = minOrMax(possMoves, key=lambda possMove: possMove[0]) bestMoves = [(val, move, count) for (val, move, count) in possMoves if val == bestVal] (_, _, longestBestMoveCount) = max(bestMoves, key=lambda possMove: possMove[2]) # Get all moves with best val and with longest count longestBestMoves = [(val, move, count) for (val, move, count) in bestMoves if count == longestBestMoveCount] return choice(longestBestMoves)
def otherMove(board: str, emptyCells: int) -> Set[int]: """ Special case moves. :param board: :param emptyCells: number of empty cells :return: Selected move """ availableCorners = {pos for pos in CORNERS if isAvailable(board, pos)} if emptyCells == 9: return availableCorners if emptyCells == 8: return {CENTER} if isAvailable(board, CENTER) else availableCorners # The following is for X's second move. It applies only if X's first move was to a corner. if emptyCells == 7 and board.index(XMARK) in CORNERS: oFirstMove = board.index(OMARK) # If O's first move is a side cell, X should take the center. # Otherwise, X should take the corner opposite its first move. if oFirstMove in SIDES: return {CENTER} if oFirstMove == CENTER: opCorner = oppositeCorner(board.index(XMARK)) return {opCorner} return availableCorners # If this is O's second move and X has diagonal corners, O should take a side move. # If X has two adjacent corners, O blocked (above). So, if there are 2 available corners # they are diagonal. if emptyCells == 6 and len(availableCorners) == 2: return {pos for pos in SIDES if isAvailable(board, pos)} # If none of the special cases apply, take the center if available, # otherwise a corner, otherwise any valid move. return ({CENTER} if isAvailable(board, CENTER) else availableCorners if availableCorners else validMoves(board))
def _makeAMove(self, board: str) -> int: (myWins, otherWins, _, _) = self.winsBlocksForks(board) move = choice( [self.findEmptyCell(board, myWin) for myWin in myWins] if myWins else [self.findEmptyCell(board, otherWin) for otherWin in otherWins] if otherWins else validMoves(board)) return move
def _makeAMove(self, board: str) -> int: """ Update qValues and select a move based on representative board from this board's equivalence class. Should not be random like the following. """ # Either a random move or the move with the highest QValue for this state. move = (qTable.getBestMove(board, self.typeName) if self.isATestGame else choice(validMoves(board))) return move
def minvalue(self, board: str, score): # set v to positive infinity v = 9999 # min of value(successor) for validmove in validMoves(board): v = min( v, self.value(setMove(board, validmove, whoseMove(board)), score)) return v
def maxvalue(self, board: str, score): # set v to negative infinity v = -9999 # max of value(successor) for validmove in validMoves(board): v = max( v, self.value(setMove(board, validmove, whoseMove(board)), score)) return v
def value(self, board: str, score): # terminal state is when there is a winner or a tie # ----------terminal states------------- # maximizer gets 100 for winning if theWinner(board) == XMARK: score += 100 return score elif theWinner(board) == OMARK: score -= 100 return score # check for a tie if len(validMoves(board)) == 0: return score # ------terminal states end, begin recursion------- # if next agent is the maximizer, get the maxvalue if whoseMove(board) == XMARK: return self.maxvalue(board, score) # if next agent is minimizer, return min value if whoseMove(board) == OMARK: return self.minvalue(board, score)
def _makeAMove(self, board: str) -> int: (myWins, otherWins, myForks, otherForks) = self.winsBlocksForks(board) availCorners = [pos for pos in CORNERS if isAvailable(board, pos)] availCenter = [pos for pos in [CENTER] if isAvailable(board, pos)] availSides = [pos for pos in SIDES if isAvailable(board, pos)] myOppositeCorners = [ pos for pos in CORNERS if isAvailable(board, pos) and board[oppositeCorner(pos)] == self.myMark ] move = choice( [self.findEmptyCell(board, myWin) for myWin in myWins] if myWins else [self.findEmptyCell(board, otherWin) for otherWin in otherWins] if otherWins else myForks if myForks else # If emptyCellsCount(board) == 6, I'm playing 'O'. If a diagonal is XOX, don't take corner. availSides if board[CENTER] == self.myMark and emptyCellsCount(board) == 6 else otherForks if otherForks else availCorners if availCorners and self.myMark == 'X' and len(availCorners) % 2 == 0 else availCenter if availCenter else myOppositeCorners if board[CENTER] == self.opMark and myOppositeCorners else availCorners if availCorners else validMoves(board)) return move
def _makeAMove(self, board: str) -> int: """ Select and return a move. Overridden by subclasses. """ # If not overridden, make a random valid move. move = choice(validMoves(board)) return move
def aStar(start, finish): """ parameters: start: a tuple (i,j) of the starting coodinates of the robot. finish: a tuple (i,j) of the rendezvous point for the robot. returns: path: a list of tuple [(i,j), (l,k),..]. the tuples are coordinates the robot must take in order to get to the finish. the list is all the coordinates together. """ # node that we have NOT explored its moves open_list = [] # nodes that we have explored its moves closed_list = [] # the depth we add to the total cost g_score = 0 temp = True # heuristic of the start node h_score_start_node = manhatton(start, finish) # creating the node for the start coordinate node_start = Node(index=start, parent=None, g_score=g_score, h_score=h_score_start_node) # node for the rendezvous point node_target = Node(finish, None, 0, 0) # adding the starting position node to the open_list open_list.append(node_start) # as long as we have a node in the open_list while open_list and temp: # select the node with the least cost in open_list node_current = getCurrentNode(open_list) # if we reached the rendezvous point, break if node_current.index == node_target.index: break # get all the valid moves moves = validMoves(ROOM, node_current.index, ROOM_DIM) # increase the g_score by 1 as we expand the node_current g_score += 1 # for each move in the valid move for node_current for move in moves: # create node for child of node_current node_successor = Node(index=move, parent=node_current, g_score=g_score, h_score=manhatton(move, finish)) # check if the successor the target point if node_successor.index == node_target.index: temp = False break # we have not come accross this node or expanded it if (not inOpen(node_successor, open_list)) and (not inClosed( node_successor, closed_list)): # add to the open_list open_list.append(node_successor) # remove the node_current to not visit it again open_list.remove(node_current) # to keep track of nodes expanded closed_list.append(node_current) # if open_list is not empty, that means we broke out of the while loop cause we reached the target point if open_list: # backtrack to append all the nodes in a list for the path path = [] # add the rendezvous point path.append(node_target) while node_current.parent: # while there exists a node to back track path.append(node_current) # add node to path node_current = node_current.parent # move on to the next node # add the start node path.append(node_start) # reverse the path path.reverse() return path # open_list is empty, means that we exhausted all of our options and no path has been found else: return []