def minimax(tree: GameStateTree, num_rounds: int, maximizing_player: Player) -> Tuple[int, Action]:
        """
        Called recursively on various nodes of the GameStateTree to determine the maximum move for the maximizing player
        and the minimizing move for any player that is not the maximizing player.
        :param tree: The game state tree that will be examined to determine the optimal moves.
        :param num_rounds: The number of rounds each player will play within the decision algorithm
        :param maximizing_player: The player that is being maximized, all other players will attempt to minimize this player.
        :return: The Tuple(optimal_score, optimal_action) that will take place in this round of the minimax algorithm.
        """

        if num_rounds == 0 and tree.state.current_player.color == maximizing_player.color:
            return tree.state.current_player.score, tree.previous_action
        elif tree.state.phase == GameStatePhase.OVER and tree.state.current_player.color == maximizing_player.color:
            return tree.state.current_player.score, tree.previous_action
            # if we reach the end and it is the maximizing players turn return the maximizing players score
        elif tree.state.phase == GameStatePhase.OVER and tree.state.current_player.color != maximizing_player.color:
            return -inf, None
            # if we reach the end and it is not the maximizing players turn return the -inf so
            # the next node up the chain will be greater than it
        elif tree.state.current_player.color == maximizing_player.color:  # maximize this player
            children = [child for child in tree.get_children()]
            return GenericStrategyComponent.handle_maximum(
                children=children, num_rounds=num_rounds, maximizing_player=maximizing_player)
        elif tree.state.current_player.color != maximizing_player.color:
            # minimize the maximizing player for all other player
            children = [child for child in tree.get_children()]
            return GenericStrategyComponent.handle_minimum(
                children=children, num_rounds=num_rounds, maximizing_player=maximizing_player)
Exemple #2
0
    def test_create_choose_action_recursive_case_is_maximizing_twice(self):
        """
        In a recursive case where 1 round is played and the
        all leave nodes are the maximizing players, the minimum of
        these leave nodes is return.
        """
        state, *_ = self.setup()
        production_tree = GameStateTree(state, previous_action=None)
        expected = Action(
            player_color=production_tree.state.current_player.color,
            start=Coordinate(0, 0),
            end=Coordinate(1, 0))

        children = [child for child in production_tree.get_children()]
        for idx, child in enumerate(children):
            second_players_children = [
                second_players_child
                for second_players_child in child.get_children()
            ]
            for jdx, second_players_child in enumerate(
                    second_players_children):
                if jdx == 1 and idx == 1:
                    score = 0
                    child.previous_action = expected
                else:
                    score = 10
                modified_player = Player.from_dict({
                    **second_players_child.state.current_player._asdict(),
                    **{
                        'score': score
                    }
                })
                second_players_child.state.current_player = modified_player
                second_players_child.get_children = MagicMock(return_value=[])
                second_players_child.state.check_any_player_can_move = MagicMock(
                    return_value=False)

        production_tree.get_children = MagicMock(return_value=children)
        actual = GenericStrategyComponent.choose_action(tree=production_tree,
                                                        num_turns=2)
        self.assertEqual(expected, actual)
Exemple #3
0
    def test_create_choose_action_recursive_case_is_maximizing_once(self):
        """
        In a scenario where all the children of the maximizing player
        are leaves - aka end-games - than a tie-breaker decides which
        action to take.
        """
        state, *_ = self.setup()
        production_tree = GameStateTree(state, previous_action=None)
        expected = Action(
            player_color=production_tree.state.current_player.color,
            start=Coordinate(0, 0),
            end=Coordinate(1, 0))

        children = [child for child in production_tree.get_children()]
        for idx, child in enumerate(children):
            if idx == 1:
                child.previous_action = expected

            child.get_children = MagicMock(return_value=[])

        production_tree.get_children = MagicMock(return_value=children)
        actual = GenericStrategyComponent.choose_action(tree=production_tree,
                                                        num_turns=2)
        self.assertEqual(expected, actual)
Exemple #4
0
    def test_create_choose_action_base_case_0_turns(self):
        """
        If the player has 0 turns and it is the currents player turn,
        then the previous action is return.
        """
        state, *_ = self.setup()
        production_tree = GameStateTree(state, previous_action=None)
        production_tree.get_children = MagicMock(return_value=None)
        expected = Action(
            player_color=production_tree.state.current_player.color,
            start=Coordinate(0, 0),
            end=Coordinate(1, 0))

        production_tree.previous_action = expected
        actual = GenericStrategyComponent.choose_action(tree=production_tree,
                                                        num_turns=0)
        self.assertEqual(expected, actual)
Exemple #5
0
    def test_create_choose_action_base_case_no_children_for_non_maximizing_player(
            self):
        """
        If the player has turns > 0 but has no children - end-game - and this is not
        the maximizing player, then we return None.
        """
        state, *_ = self.setup()
        production_tree = GameStateTree(state, previous_action=None)
        production_tree.get_children = MagicMock(return_value=[])
        production_tree.previous_action = Action(
            player_color=production_tree.state.current_player.color,
            start=Coordinate(0, 0),
            end=Coordinate(1, 0))

        expected = None
        _, actual = GenericStrategyComponent.minimax(tree=production_tree,
                                                     num_rounds=2,
                                                     maximizing_player=Player(
                                                         "white", 0, 0, []))
        self.assertEqual(expected, actual)