Example #1
0
    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)
Example #2
0
 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)
Example #3
0
    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))
Example #4
0
 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
Example #5
0
 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
Example #6
0
 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
Example #7
0
 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
Example #8
0
    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)
Example #9
0
 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
Example #10
0
 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
Example #11
0
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 []