def search(self, state, depth, max_player):
     """Start the MiniMax algorithm.
     :param state: The state to start from.
     :param depth: The maximum allowed depth for the algorithm.
     :param max_player: Whether this is a max node (True) or a min node (False).
     :return: A tuple: (The min max algorithm value, The direction in case of max node or None in min mode)
     """
     self.throw_exception_if_timeout(state)
     if self.goal and self.goal(state, max_player):
         return (self.utility(state, max_player), state.direction)
     if depth == 0:
         return (self.utility(state, max_player), state.direction)
     childrens = self.succ(state, max_player)
     if max_player:
         currMax = State(None, None, None, None, None, None)
         currMax.value = -np.inf
         for c in childrens:
             v = self.search(c, depth - 1, not max_player)
             c.value = v[0]
             currMax = max(currMax, c)
         return (currMax.value, currMax.direction)
     else:
         currMin = State(None, None, None, None, None, None)
         currMin.value = np.inf
         for c in childrens:
             v = self.search(c, depth - 1, not max_player)
             c.value = v[0]
             currMin = min(currMin, c)
         return (currMin.value, currMin.direction)
    def search(self,
               state,
               depth,
               is_father_max,
               alpha=ALPHA_VALUE_INIT,
               beta=BETA_VALUE_INIT):
        """Start the AlphaBeta algorithm.
        :param state: The state to start from.
        :param depth: The maximum allowed depth for the algorithm.
        :param is_father_max: Whether this is a max node (True) or a min node (False).
        :param alpha: alpha value
        :param beta: beta value
        :return: A tuple: (The min max algorithm value, The direction in case of max node or None in min mode)
        """

        self.throw_exception_if_timeout(state)
        if self.goal and self.goal(state):
            return (self.utility(state, is_father_max), state.direction)
        if depth == 0:
            return (self.utility(state, is_father_max), state.direction)
        children = self.succ(state, not is_father_max)
        if is_father_max:
            currMax = State(None, None, None, None, None, None, None, None,
                            None, None, None)
            currMax.value = -np.inf
            for c in children:
                v = self.search(c, depth - 1, not is_father_max, alpha, beta)
                c.value = v[0]
                currMax = max(currMax, c)
                alpha = max(currMax.value, alpha)
                if currMax.value >= beta:
                    return np.inf, currMax.direction
            # self.restore_father(is_father_max, state, children)
            return currMax.value, currMax.direction
        else:
            currMin = State(None, None, None, None, None, None, None, None,
                            None, None, None)
            currMin.value = np.inf
            for c in children:
                v = self.search(c, depth - 1, not is_father_max, alpha, beta)
                c.value = v[0]
                currMin = min(currMin, c)
                beta = min(currMin.value, beta)
                if currMin.value <= alpha:
                    return -np.inf, currMin.direction
            # self.restore_father(is_father_max, state, children)

            return (currMin.value, currMin.direction)