Exemple #1
0
        def minimax(board,currentDepth,isOdd):
            # Note: bool isOdd basically tells us we want the max

            #checking this here REALLY boosts efficiency (due to getLegalMoves getting skipped)
            if currentDepth == self.maxDepth:
                return super(MinimaxPlayer,self).h1(board,self.symbol)

            moves = None
            if isOdd:
                moves = game_rules.getLegalMoves(board,self.symbol)
            else:
                symbol = 'x' if self.symbol == 'o' else 'o'
                moves = game_rules.getLegalMoves(board,symbol)

            # check for end state
            if len(moves) == 0:
                return super(MinimaxPlayer,self).h1(board,self.symbol)
            
            # want max
            if isOdd:
                optimalEval = NEG_INF
                for move in moves:
                    newBoard = game_rules.makeMove(board,move)
                    optimalEval = max(optimalEval,minimax(newBoard,currentDepth+1,False))
                return optimalEval
            # want min
            else:
                optimalEval = POS_INF
                for move in moves:
                    newBoard = game_rules.makeMove(board,move)
                    optimalEval = min(optimalEval,minimax(newBoard,currentDepth+1,True))
                return optimalEval
Exemple #2
0
    def minimax_helper(self, board, depth, max_player, symbol):
        if symbol == "x":
            symbol = "o"
        else:
            symbol = "x"

        if max_player:
            moves = game_rules.getLegalMoves(board, symbol)
            new_value = NEG_INF
            depth = depth - 1
            if len(moves) == 0 or depth == 0:
                return self.h1(board, symbol)

            for move in moves:
                clone_board = game_rules.makePlayerMove(board, symbol, move)
                value = self.minimax_helper(clone_board, depth, False, symbol)
                if value > new_value:
                    new_value = value
            return new_value
        else:
            moves = game_rules.getLegalMoves(board, symbol)
            new_value = POS_INF
            depth = depth - 1
            if len(moves) == 0 or depth == 0:
                return self.h1(board, symbol)

            for move in moves:
                clone_board = game_rules.makePlayerMove(board, symbol, move)
                value = self.minimax_helper(clone_board, depth, True, symbol)
                if value < new_value:
                    new_value = value
            return new_value
Exemple #3
0
 def getMove(self, board):
     legalMoves = game_rules.getLegalMoves(board, self.symbol)
     if len(legalMoves) > 0:
         return self.ab_pruning(self.symbol, board, NEG_INF, POS_INF,
                                self.depth).move
     else:
         return None
Exemple #4
0
    def min_value(self, board, player, curr_depth):

        if curr_depth == 0:
            new_utility = self.h1(board, player)
            new_utility_move = [new_utility, None]
            return new_utility_move
        curr_depth = curr_depth - 1

        available_moves = game_rules.getLegalMoves(board, player)
        if len(available_moves) == 0:
            new_utility = self.h1(board, player)
            new_utility_move = [new_utility, None]
            return new_utility_move

        check_utility = [POS_INF, None]
        for move in available_moves:
            new_player = self.change_player(player)
            new_board = game_rules.makeMove(board, move)
            new_utility = self.max_value(new_board, new_player, curr_depth)

            if new_utility[0] < check_utility[0]:
                check_utility[0] = new_utility[0]
                check_utility[1] = move

        return check_utility
Exemple #5
0
 def Max_Value(self, board, a, b, depth, symbol):
     # set initial max value to minus infinity
     #val = -1000000000
     # get legalmoves
     legalMoves = game_rules.getLegalMoves(board,symbol)
     best = (-1000000000, None)
     # return utility when no more legalmoves
     if (len(legalMoves) == 0 or depth == 0):
         return (self.h1(board, symbol), None)
     
     for i in range(len(legalMoves)):
         nextBoard = game_rules.makeMove(board, legalMoves[i])
         #nextBoard = game_rules.makeMove(board, legalMoves[i])
         # when meet depth limit, then use heuristic function to replace the val Min_Value
         if symbol == 'x':
             val = self.Min_Value(nextBoard, a, b, depth - 1, 'o')[0]
         else:
             val = self.Min_Value(nextBoard, a, b, depth - 1, 'x')[0]
         if best[0] < val:
            best = (val,legalMoves[i])
         if (best[0] >= b):
             return best
         if (a < best[0]):
             a = best[0]
     return best
Exemple #6
0
    def min_valuealpha_beta(self, board, player, curr_depth, alpha, beta):

        if curr_depth == 0:
            new_utility = self.h1(board, player)
            new_utility_move = [new_utility, None]
            return new_utility_move
        curr_depth = curr_depth - 1

        available_moves = game_rules.getLegalMoves(board, player)
        if len(available_moves) == 0:
            new_utility = self.h1(board, player)
            new_utility_move = [new_utility, None]
            return new_utility_move

        check_utility = [POS_INF, None]
        for move in available_moves:
            new_player = self.change_player(player)
            new_board = game_rules.makeMove(board, move)
            new_utility = self.max_value_alpha_beta(new_board, new_player,
                                                    curr_depth, alpha, beta)

            if new_utility[0] < check_utility[0]:
                check_utility[0] = new_utility[0]
                check_utility[1] = move

            beta = min(beta, new_utility[0])
            if alpha >= beta:
                break

        return check_utility
Exemple #7
0
 def _sanitizedMove(self, board):
   legalMoves = getLegalMoves(board, self.symbol)
   resultMove = self._parseMove(self._run(board))
   if resultMove in legalMoves:
     return resultMove
   else:
     return ((1, 2), (3, 4))
Exemple #8
0
    def max_value(self, board, player, curr_depth):

        #check if the max depth is reached - then return the utility function
        if curr_depth == 0:
            new_utility = self.h1(board, player)
            new_utility_move = [new_utility, None]
            return new_utility_move
        curr_depth = curr_depth - 1
        available_moves = game_rules.getLegalMoves(board, player)
        #check if it is a leaf node -> return utility function
        if len(available_moves) == 0:
            new_utility = self.h1(board, player)
            new_utility_move = [new_utility, None]
            return new_utility_move

        check_utility = [NEG_INF, None]
        for move in available_moves:

            new_player = self.change_player(player)
            new_board = game_rules.makeMove(board, move)
            new_utility = self.min_value(new_board, new_player, curr_depth)

            #check if the utility returned is greater than the current utility
            #update the utility and the move associated with that
            if new_utility[0] > check_utility[0]:
                check_utility[0] = new_utility[0]
                check_utility[1] = move

        return check_utility
Exemple #9
0
 def getMove(self, board):
     legalMoves = game_rules.getLegalMoves(board, self.symbol)
     if len(legalMoves) > 0:
         return self.get_max(board, self.symbol, self.depth, float('-inf'),
                             float('inf'))[1]
     else:
         return None
Exemple #10
0
    def minimax(self, symbol, board, depth):
        legalMoves = game_rules.getLegalMoves(board, symbol)
        next_move = Move()
        if depth == 0 or len(legalMoves) == 0:
            next_move.cost = self.h1(board, symbol)
            return next_move

        if symbol == "x":
            new_symbol = "o"
        else:
            new_symbol = "x"

        if symbol == self.symbol:
            next_move.cost = NEG_INF
            for move in legalMoves:
                val = self.minimax(new_symbol,
                                   game_rules.makeMove(board, move), depth - 1)
                if val.cost > next_move.cost:
                    next_move.cost = val.cost
                    next_move.move = move
            return next_move
        else:
            next_move.cost = POS_INF
            for move in legalMoves:
                val = self.minimax(new_symbol,
                                   game_rules.makeMove(board, move), depth - 1)
                if val.cost < next_move.cost:
                    next_move.cost = val.cost
                    next_move.move = move
            return next_move
Exemple #11
0
 def _minValue(self, board, depth, symbol):
   legalMoves = game_rules.getLegalMoves(board, symbol)
   if depth == 0 or len(legalMoves) == 0:
     return (self._assessBoard(board, symbol), None)
   best = (1000000000, None)
   for move in legalMoves:
     child = game_rules.makeMove(board, move)
     (score, _) = self._minValue(child, depth-1, 'o' if symbol == 'x' else 'x')
     best = min(best, (score, move))
   return best
Exemple #12
0
    def getMove(self, board):
        # use super(MinimaxPlayer,self,board,self.symbol).h1 as evaluation function
        def decision(board,moves):
            cloneBoard = deepcopy(board)
            optimalMove = moves[0]
            optimalEval = NEG_INF
            for move in moves:
                newBoard = game_rules.makeMove(cloneBoard,move)
                val = minimax(newBoard,1,False) #send with depth 1, as this is depth 0, the root
                if val > optimalEval: #final max check
                    optimalMove = move
                    optimalEval = val
            return optimalMove
        
        # recursive function that simulates playing the max
        # (finds best move for self), or playing the min
        # (finds the best move for opponent)
        def minimax(board,currentDepth,isOdd):
            # Note: bool isOdd basically tells us we want the max

            #checking this here REALLY boosts efficiency (due to getLegalMoves getting skipped)
            if currentDepth == self.maxDepth:
                return super(MinimaxPlayer,self).h1(board,self.symbol)

            moves = None
            if isOdd:
                moves = game_rules.getLegalMoves(board,self.symbol)
            else:
                symbol = 'x' if self.symbol == 'o' else 'o'
                moves = game_rules.getLegalMoves(board,symbol)

            # check for end state
            if len(moves) == 0:
                return super(MinimaxPlayer,self).h1(board,self.symbol)
            
            # want max
            if isOdd:
                optimalEval = NEG_INF
                for move in moves:
                    newBoard = game_rules.makeMove(board,move)
                    optimalEval = max(optimalEval,minimax(newBoard,currentDepth+1,False))
                return optimalEval
            # want min
            else:
                optimalEval = POS_INF
                for move in moves:
                    newBoard = game_rules.makeMove(board,move)
                    optimalEval = min(optimalEval,minimax(newBoard,currentDepth+1,True))
                return optimalEval

        legalMoves = game_rules.getLegalMoves(board, self.symbol)
        if len(legalMoves) > 0:
            return decision(board,legalMoves)
        else: return None
Exemple #13
0
        def minimax_recur(depth, board, turn, currsymbol):
            #            print('**minimax_recur board: ', board)
            #            print('**current player: ', currsymbol)
            legalMoves = game_rules.getLegalMoves(board,
                                                  currsymbol)  #self.symbol)
            #            print('**all legal moves: ', legalMoves)

            if len(legalMoves) == 0 or depth == 0:
                #                print('**case1')
                #                print('**heuristic: ', self.h1(board, currsymbol))
                return None, self.h1(board, currsymbol)

            val = (POS_INF, NEG_INF)[turn]
            #            print('**val = ', val)
            move = None

            tmpmove = (None, None)

            for eachmove in legalMoves:
                #                print('**now move: ', eachmove)

                if tmpmove[0] != None:
                    board = self.originalboard(board, tmpmove[0], tmpmove[1],
                                               currsymbol)

#                print('board: ', board)

                tmpmove = (eachmove[0], eachmove[len(eachmove) - 1]
                           )  #start -> end
                #                print('**tmpmove: ', tmpmove)

                board = game_rules.makeMove(board, eachmove)
                #                print('**nowboard: ', board)
                succmove, succval = minimax_recur(depth - 1, board, not turn,
                                                  self.changeturn(currsymbol))
                #                print('*****now val:', succval)

                #                print('current move: ', eachmove)
                #                print('bestval: ', val)
                #                print('bestmove: ', move)
                a, b = (((move, val), (eachmove, succval))[succval < val],
                        ((move, val), (eachmove,
                                       succval))[succval > val])[turn]
                move = a
                val = b
#                print('**bestmove: ', move)
#                print('**bestval: ', val)

            board = self.originalboard(board, tmpmove[0], tmpmove[1],
                                       currsymbol)

            #            print('then return bestmove: ', move)
            #            print('then return bestval: ', val)
            return move, val
Exemple #14
0
    def max_value(self, board, depth):
        depth -= 1
        if depth == 0:
            return self.h1(board, self.symbol)
        legalMoves = game_rules.getLegalMoves(board, self.symbol)
        if len(legalMoves) == 0:
            return self.h1(board, self.symbol)

        v = NEG_INF
        for move in legalMoves:
            v = max(v, self.min_value(self.makeMove(board, move), depth))
        return v
Exemple #15
0
        def alphabeta(board,currentDepth,isOdd,alpha,beta):
            # Note: bool isOdd basically tells us we want the max

            #checking this here REALLY boosts efficiency (due to getLegalMoves getting skipped)
            if currentDepth == self.maxDepth:
                return super(AlphaBetaPlayer,self).h1(board,self.symbol)

            moves = None
            symbol = None
            if isOdd:
                moves = game_rules.getLegalMoves(board,self.symbol)
            else:
                symbol = 'x' if self.symbol == 'o' else 'o'
                moves = game_rules.getLegalMoves(board,symbol)

            # check for end state
            if len(moves) == 0:
                # so actually, I ran this using self.h1 and super(...).h1, and super gave me quicker test results... weird
                return super(AlphaBetaPlayer,self).h1(board,self.symbol)
            
            # want max
            if isOdd:
                loc_alpha = alpha
                for move in moves:
                    # check if b <= a; if so exit loop
                    if beta <= loc_alpha:
                        return loc_alpha
                    newBoard = game_rules.makeMove(board,move)
                    loc_alpha = max(loc_alpha,alphabeta(newBoard,currentDepth+1,False,loc_alpha,beta))
                return loc_alpha
            # want min
            else:
                loc_beta = beta
                for move in moves:
                    # check if b <= a; if so exit loop
                    if loc_beta <= alpha:
                        return loc_beta
                    newBoard = game_rules.makeMove(board,move)
                    loc_beta = min(loc_beta,alphabeta(newBoard,currentDepth+1,True,alpha,loc_beta))
                return loc_beta
Exemple #16
0
    def min_value(self, board, depth):
        depth -= 1
        if depth == 0:
            return self.h1(board, self.symbol)
        opponent_symbol = self.get_opponent_symbol(self.symbol)
        legalMoves = game_rules.getLegalMoves(board, opponent_symbol)
        if len(legalMoves) == 0:
            return self.h1(board, self.symbol)

        v = POS_INF
        for move in legalMoves:
            v = min(v, self.max_value(self.makeMove(board, move), depth))
        return v
Exemple #17
0
    def alphabeta_helper(self, board, depth, max_player, alpha, beta, symbol):
        if symbol == "x":
            symbol = "o"
        else:
            symbol = "x"

        if max_player:
            moves = game_rules.getLegalMoves(board, symbol)

            depth = depth - 1
            if len(moves) == 0 or depth == 0:
                return self.h1(board, symbol)

            for move in moves:
                clone_board = game_rules.makePlayerMove(board, symbol, move)
                value = self.alphabeta_helper(clone_board, depth, False, alpha,
                                              beta, symbol)
                if value > alpha:
                    alpha = value
                if beta <= alpha:
                    return alpha
            return alpha

        else:
            moves = game_rules.getLegalMoves(board, symbol)

            depth = depth - 1
            if len(moves) == 0 or depth == 0:
                return self.h1(board, symbol)
            for move in moves:
                clone_board = game_rules.makePlayerMove(board, symbol, move)
                value = self.alphabeta_helper(clone_board, depth, True, alpha,
                                              beta, symbol)
                if value < beta:
                    beta = value
                if beta <= alpha:
                    return beta
            return beta
Exemple #18
0
 def Min_Value(self, board, depth, symbol):
     legalMoves = game_rules.getLegalMoves(board, symbol)
     if depth == 0 or len(legalMoves) == 0:
         return (self.h1(board, symbol), None)
     best = (POS_INF, None)
     for i in range(len(legalMoves)):
         nextBoard = game_rules.makeMove(board, legalMoves[i])
         if symbol == 'x':
             val = self.Max_Value(nextBoard, depth - 1, 'o')[0]
         else:
             val = self.Max_Value(nextBoard, depth - 1, 'x')[0]
         if best[0] > val:
             best = (val, legalMoves[i])
     return best
Exemple #19
0
    def _takeTurn(self, move_pair=None):
        playerBoard = deepcopy(self.board)
        old = self.state

        if len(game_rules.getLegalMoves(self.board, self.GetTurn())) < 1:
            if self.state == X_TURN: self.state = O_VICTORY
            if self.state == O_TURN: self.state = X_VICTORY
            return

        if self.state == AWAITING_INITIAL_X:
            self._handleInitialX(playerBoard, self.board, move_pair)
        elif self.state == AWAITING_INITIAL_O:
            self._handleInitialO(playerBoard, self.board, move_pair)
        elif self.state == X_TURN:
            self._handleTurnX(playerBoard, self.board, move_pair)
        elif self.state == O_TURN:
            self._handleTurnO(playerBoard, self.board, move_pair)
        if self.state != old: self.turn_number += 1
Exemple #20
0
    def get_min(self, board, symbol, depth):
        legal_moves = game_rules.getLegalMoves(board, symbol)

        if depth == 0 or len(legal_moves) == 0:
            result = [self.h1(board, symbol), None]
            return result

        else:
            our_move = None
            min_val = float('inf')
            for move in legal_moves:
                new_board = game_rules.makeMove(board, move)
                val = self.get_max(new_board, self.opposite_symbol(symbol),
                                   depth - 1)[0]
                if val < min_val:
                    min_val = val
                    our_move = move
            result = [min_val, our_move]
            return result
Exemple #21
0
    def ab_pruning(self, symbol, board, alpha, beta, depth):
        legalMoves = game_rules.getLegalMoves(board, symbol)
        next_move = Move()
        if depth == 0 or len(legalMoves) == 0:
            next_move.cost = self.h1(board, symbol)
            return next_move

        if symbol == "x":
            new_symbol = "o"
        else:
            new_symbol = "x"

        if symbol == self.symbol:
            next_move.cost = NEG_INF
            for move in legalMoves:
                val = self.ab_pruning(new_symbol,
                                      game_rules.makeMove(board, move), alpha,
                                      beta, depth - 1)
                if val.cost > next_move.cost:
                    next_move.cost = val.cost
                    next_move.move = move

                alpha = max(alpha, val.cost)
                if beta <= alpha:
                    break
            return next_move
        else:
            next_move.cost = POS_INF
            for move in legalMoves:
                val = self.ab_pruning(new_symbol,
                                      game_rules.makeMove(board, move), alpha,
                                      beta, depth - 1)
                if val.cost < next_move.cost:
                    next_move.cost = val.cost
                    next_move.move = move
                beta = min(beta, val.cost)
                if beta <= alpha:
                    break
            return next_move
Exemple #22
0
    def getMove(self, board):
        #legalMoves = game_rules.getLegalMoves(board, self.symbol)
        #if len(legalMoves) > 0: return legalMoves[0]
        #else: return None
        legal_moves = game_rules.getLegalMoves(board, self.symbol)
        top_move = legal_moves[0]
        new_value = NEG_INF
        alpha = NEG_INF
        beta = POS_INF
        for move in legal_moves:
            clone_board = game_rules.makePlayerMove(board, self.symbol, move)
            value = self.alphabeta_helper(clone_board, self.depth, False,
                                          alpha, beta, self.symbol)
            if value > new_value:
                top_move = move
                new_value = value
            if value > alpha:
                alpha = value
            if alpha >= beta:
                break

        return top_move
Exemple #23
0
 def getMove(self, board):
     #legalMoves = game_rules.getLegalMoves(board, self.symbol)
     #if len(legalMoves) > 0: return legalMoves[0]
     #else: return None
     #print("Self.symbol is: {}".format(self.symbol))
     legal_moves = game_rules.getLegalMoves(board, self.symbol)
     #print("legal_moves are: {}".format(legal_moves))
     if len(legal_moves) == 0:
         #print("No possible Moves")
         return None
     else:
         top_move = legal_moves[0]
         new_value = NEG_INF
         for move in legal_moves:
             clone_board = game_rules.makePlayerMove(
                 board, self.symbol, move)
             value = self.minimax_helper(clone_board, self.depth, False,
                                         self.symbol)
             if value > new_value:
                 top_move = move
                 new_value = value
         return top_move
Exemple #24
0
    def get_max(self, board, symbol, depth, alpha, beta):
        legal_moves = game_rules.getLegalMoves(board, symbol)

        if depth == 0 or len(legal_moves) == 0:
            result = [self.h1(board, symbol), None]
            return result

        else:
            our_move = None
            max_val = float('-inf')
            for move in legal_moves:
                new_board = game_rules.makeMove(board, move)
                val = self.get_min(new_board, self.opposite_symbol(symbol),
                                   depth - 1, alpha, beta)[0]
                if val > max_val:
                    max_val = val
                    our_move = move
                if val > alpha:
                    alpha = val
                if alpha >= beta:
                    break
            result = [max_val, our_move]
            return result
Exemple #25
0
 def getMove(self, board):
     legalMoves = game_rules.getLegalMoves(board, self.symbol)
     return self.minimax_decision(legalMoves, board)
Exemple #26
0
 def h1(self, board, symbol):
     return -len(
         game_rules.getLegalMoves(board,
                                  'o' if self.symbol == 'x' else 'x'))
Exemple #27
0
 def getMove(self, board):
     legalMoves = game_rules.getLegalMoves(board, self.symbol)
     if len(legalMoves) > 0: return legalMoves[0]
     else: return None
Exemple #28
0
 def getMove(self, board):
     legalMoves = game_rules.getLegalMoves(board, self.symbol)
     if len(legalMoves) > 0: return random.choice(legalMoves)
     else: return None
Exemple #29
0
 def getMove(self, board):
   legalMoves = game_rules.getLegalMoves(board, self.symbol)
   if len(legalMoves) > 0:
     return random.choice(legalMoves)
   else:
     return ((0, 1), (2, 3))
Exemple #30
0
        def alphabeta_recur(depth, board, turn, currsymbol, alpha, beta):
            #            print('** now board: ', board)
            #            print('** now player: ', currsymbol)
            legalMoves = game_rules.getLegalMoves(board, currsymbol)
            #            print('all legal moves: ', legalMoves)

            if len(legalMoves) == 0 or depth == 0:
                #                print('case1')
                #                print('h1: ', self.h1(board, currsymbol))
                # only return heuristic
                # self.printCurrentInfo(depth, self.heuristic(board, self), None)
                return None, self.h1(board, currsymbol)

#            print('turn: ', turn)
            val = (POS_INF, NEG_INF)[turn]
            #            print('val: ', val)

            move = None

            tmpmove = (None, None)

            # judge every nodes possible, select the biggest/smallest one
            for eachmove in legalMoves:
                #                print('now eachmove: ', eachmove)

                if tmpmove[0] != None:
                    board = self.originalboard(board, tmpmove[0], tmpmove[1],
                                               currsymbol)

                tmpmove = (eachmove[0], eachmove[len(eachmove) - 1])
                #                print('new move: ', tmpmove)

                board = game_rules.makeMove(board, eachmove)
                #                print('board: ', board)

                succmove, succval = alphabeta_recur(
                    depth - 1, board, not turn, self.changeturn(currsymbol),
                    alpha, beta)
                #                print('now succval: ', succval)

                #                print('check a,b: ')
                #                print('succval: ', succval)
                #                print('val: ', val)
                #                print('eachmove: ', eachmove)
                #                print('move: ', move)
                #                print('turn: ', turn)
                a, b = (((move, val), (eachmove, succval))[succval < val],
                        ((move, val), (eachmove,
                                       succval))[succval > val])[turn]
                move = a
                val = b

                #                print('move: ', move)
                #                print('val: ', val)

                if turn:
                    alpha = (alpha, val)[alpha <= val]
#                    print('alpha: ', alpha)
                else:
                    beta = (beta, val)[beta >= val]


#                    print('beta: ', beta)
                if beta <= alpha:
                    #                    print('beta < alpha, break', beta, alpha)
                    break

            board = self.originalboard(board, tmpmove[0], tmpmove[1],
                                       currsymbol)

            #            print('then return move: ', move)
            #            print('then return bestval: ', val)
            return move, val
Exemple #31
0
 def getMove(self, board):
     legalMoves = game_rules.getLegalMoves(board, self.symbol)
     return self.alpha_beta_search(legalMoves, board)
Exemple #32
0
 def getMove(self, board):
     legalMoves = game_rules.getLegalMoves(board, self.symbol)
     if len(legalMoves) > 0:
         return self.minimax(self.symbol, board, self.depth).move
     else:
         return None
Exemple #33
0
    def getMove(self, board):
        # use super(MinimaxPlayer,self,board,self.symbol).h1 as evaluation function
        def decision(board,moves):
            cloneBoard = deepcopy(board) # clone used for simulation
            optimalMove = moves[0]
            alpha = NEG_INF
            beta = POS_INF
            for move in moves:
                newBoard = game_rules.makeMove(cloneBoard,move)
                val = alphabeta(newBoard,1,False,alpha,beta) #send with depth 1, as this is depth 0, the root
                if val > alpha: #final max check
                    optimalMove = move
                    alpha = val
            return optimalMove
        
        # recursive function that simulates playing the max
        # (finds best move for self), or playing the min
        # (finds the best move for opponent)
        def alphabeta(board,currentDepth,isOdd,alpha,beta):
            # Note: bool isOdd basically tells us we want the max

            #checking this here REALLY boosts efficiency (due to getLegalMoves getting skipped)
            if currentDepth == self.maxDepth:
                return super(AlphaBetaPlayer,self).h1(board,self.symbol)

            moves = None
            symbol = None
            if isOdd:
                moves = game_rules.getLegalMoves(board,self.symbol)
            else:
                symbol = 'x' if self.symbol == 'o' else 'o'
                moves = game_rules.getLegalMoves(board,symbol)

            # check for end state
            if len(moves) == 0:
                # so actually, I ran this using self.h1 and super(...).h1, and super gave me quicker test results... weird
                return super(AlphaBetaPlayer,self).h1(board,self.symbol)
            
            # want max
            if isOdd:
                loc_alpha = alpha
                for move in moves:
                    # check if b <= a; if so exit loop
                    if beta <= loc_alpha:
                        return loc_alpha
                    newBoard = game_rules.makeMove(board,move)
                    loc_alpha = max(loc_alpha,alphabeta(newBoard,currentDepth+1,False,loc_alpha,beta))
                return loc_alpha
            # want min
            else:
                loc_beta = beta
                for move in moves:
                    # check if b <= a; if so exit loop
                    if loc_beta <= alpha:
                        return loc_beta
                    newBoard = game_rules.makeMove(board,move)
                    loc_beta = min(loc_beta,alphabeta(newBoard,currentDepth+1,True,alpha,loc_beta))
                return loc_beta

        legalMoves = game_rules.getLegalMoves(board, self.symbol)
        if len(legalMoves) > 0:
            return decision(board,legalMoves)
        else: return None