from game import MancalaNode, GameTreeSearcher, MancalaPlayer, play_fairkalah_game, GameNode


class FixedDepthDemo1MancalaNode(MancalaNode):

    def utility(self):
        """Return an estimation of game node utility. However, if the game is over, return the actual utility."""
        # TODO: In your implementation, replace UniqueID with your unique ID,
        # and implement a different utility function.
        return self.state[MancalaNode.MAX_SCORE_PIT] - self.state[MancalaNode.MIN_SCORE_PIT]


class FixedDepthDemo2MancalaNode(MancalaNode):

    def utility(self):
        """Return an estimation of game node utility. However, if the game is over, return the actual utility."""
        # TODO: In your implementation, replace UniqueID with your unique ID,
        # and implement a different utility function..
        return self.state[MancalaNode.MAX_SCORE_PIT] - self.state[MancalaNode.MIN_SCORE_PIT]


# TODO This is not alpha-beta - just depth-limited minimax. You need to implement alpha-beta pruning.
class FixedDepthDemoAlphaBetaSearcher(GameTreeSearcher):
    """Actually just a depth-limited minimax game tree searcher _without_ alpha-beta pruning."""
    def __init__(self, depth_limit):
        """Create a minimax searcher with a given depth limit."""
        self.depth_limit = depth_limit
        self.node_count = 0
        self.best_move = GameNode.UNDEFINED_MOVE

    def eval(self, node):
        """Return the depth-limited minimax value of the given node."""
        self.node_count = 0
        return self.minimax_eval(node, self.depth_limit)

    def minimax_eval(self, node, depth_left):
        """Return recursive node evaluation of minimax algorithm."""
        local_best_move = GameNode.UNDEFINED_MOVE
        maximizing = (node.get_player() == GameNode.MAX)
        best_utility = float('-inf') if maximizing else float('inf')
        self.node_count += 1

        # Return utility if game over or depth limit reached
        if node.is_game_over() or depth_left == 0:
            return node.utility()

        # Otherwise, generate children
        children = node.expand()

        # Evaluate the depth-limited minimax value for each
        # child, keeping track of the best
        for child in children:
            child_utility = self.minimax_eval(child, depth_left - 1)
            # update best utility and move if appropriate
            if (maximizing and child_utility > best_utility) or (not maximizing and child_utility < best_utility):
                best_utility = child_utility
                local_best_move = child.prev_move

        # Before returning utility, assign local best move to
        # instance variable.  The last value assigned in the
        # recursive evaluation will be the best move from the
        # root node.
        self.best_move = local_best_move
        return best_utility

    def get_best_move(self):
        return self.best_move

    def get_node_count(self):
        return self.node_count


# TODO: Create your own player with a choose_move that (1) manages time better, (2) uses a better heuristic you've
# developed, and (3) uses your correct implementation of alpha-beta pruning.
class FixedDepthDemoMancalaPlayer(MancalaPlayer):
    def choose_move(self, node, ms_remaining):
        """This is where your code takes over as a MancalaPlayer.  You must implement this method.
        See SimpleMancalaPlayer for an example implementation with minimax.  You are given the current MancalaNode
        and the time remaining in milliseconds.  You are to return a legal move integer.  Your game clock runs until you
        return your legal move, so budget your time well.  You can need to first create a new node of your own type (say
        UniqueIDMancalaNode), and have your entire search work with your own node types and thus your own evaluation
        function."""
        # This is a poor implementation with no time management, just to test the use of the template file UniqueID
        fixed_depth_limit = 8
        root = FixedDepthDemo1MancalaNode(other=node)
        searcher = FixedDepthDemoAlphaBetaSearcher(fixed_depth_limit)
        searcher.eval(root)
        return searcher.get_best_move()


if __name__ == "__main__":
    play_fairkalah_game(player_max=FixedDepthDemoMancalaPlayer(), player_min=FixedDepthDemoMancalaPlayer(),
                        game_index=0)
