Exemplo n.º 1
0
 def test_game_tree_complete_depth(self):
     # tests that the depth of the complete game tree is as expected
     board = Board(2, 5, {}, num_of_fish_per_tile=2)
     state = GameState(board, {1: "black", 2: "white"}, [2, 1],
                       penguin_posns={1: [[0, 0], [0, 1], [0, 2], [1, 3]], 2: [[1, 0], [1, 1], [1, 2], [1, 4]]})
     game = GameTree(state)
     game.next_layer()
     game.next_layer()
     assert game.next_layer() == "Game Done"
Exemplo n.º 2
0
    def test_execute_action_illegal(self):
        # tests the behavior of execute_action with regards to an illegal move
        board = Board(4, 5, {}, num_of_fish_per_tile=2)
        state = GameState(board, {1: "black", 2: "white"}, [2, 1],
                          penguin_posns={1: [[0, 0], [0, 1], [0, 2], [1, 3]], 2: [[1, 0], [1, 1], [1, 2], [1, 4]]})
        game = GameTree(state)

        game.next_layer()
        assert not game.execute_action(((-1,0), (3,0)))
        assert not game.execute_action(((1, 0), (4, 0)))
Exemplo n.º 3
0
    def test_execute_action_legal(self):
        # tests the behavior of execute_action with regards to a legal move
        board = Board(4, 5, {}, num_of_fish_per_tile=2)
        state = GameState(board, {1: "black", 2: "white"}, [2, 1],
                          penguin_posns={1: [[0, 0], [0, 1], [0, 2], [1, 3]], 2: [[1, 0], [1, 1], [1, 2], [1, 4]]})
        game = GameTree(state)


        game.next_layer()

        assert game.execute_action(((1, 0), (3, 0)))

        game.next_layer()

        game_tree_child_1 = game.get_map_action_to_child_nodes()[((1, 0), (3, 0))]

        assert game_tree_child_1.execute_action(((0,0), (2,0)))
Exemplo n.º 4
0
    def test_apply_to_all_children(self):
        # tests the apply to all children method
        board = Board(2, 5, {}, num_of_fish_per_tile=2)
        state = GameState(board, {1: "black", 2: "white"}, [2, 1],
                          penguin_posns={1: [[0, 0], [0, 1], [0, 2], [1, 3]], 2: [[1, 0], [1, 1], [1, 2], [1, 4]]})
        game = GameTree(state)

        game.next_layer()

        assert game.apply_to_all_children(game.score_at_state) == [2, 2]

        game.next_layer()

        game_tree_child_1 = game.get_map_action_to_child_nodes()[((1, 2), (0, 3))]
        assert game_tree_child_1.apply_to_all_children(game.score_at_state) == [2]

        game_tree_child_1 = game.get_map_action_to_child_nodes()[((1, 4), (0, 4))]
        assert game_tree_child_1.apply_to_all_children(game.score_at_state) == [2]
Exemplo n.º 5
0
    def test_game_tree_one_layer(self):
        # tests that the child nodes have the right turn and have added holes in the board, indicating a move being made
        board = Board(4, 5, {}, num_of_fish_per_tile=2)
        state = GameState(board, {1: "black", 2: "white"}, [2, 1],
                          penguin_posns={1: [[0, 0], [0, 1], [0, 2], [1, 3]], 2: [[1, 0], [1, 1], [1, 2], [1, 4]]})
        game = GameTree(state)

        game.next_layer()

        assert len(game.get_map_action_to_child_nodes()) == 19

        for action, child_node in game.get_map_action_to_child_nodes().items():
            assert child_node.get_map_action_to_child_nodes() == {}

        game.next_layer()

        game_tree_child_1 = game.get_map_action_to_child_nodes()[((1, 0), (2, 1))]

        assert game_tree_child_1.get_game_state().get_player_order() == [1, 2]
        assert len(game_tree_child_1.get_game_state().get_board().get_holes()) == 1

        game_tree_child_2 = game.get_map_action_to_child_nodes()[((1, 0), (3, 1))]
        assert game_tree_child_2.get_game_state().get_player_order() == [1, 2]
        assert len(game_tree_child_2.get_game_state().get_board().get_holes()) == 1
Exemplo n.º 6
0
class Strategy:
    def __init__(self, game_state, player_id):
        self.__game_state = game_state
        self.__game_tree = None
        self.__player_id = player_id

    """
    Nothing -> Nothing
    
    Returns the next free spot available on the board, following a zig zag pattern that
    starts at the top left corner (when you reach the last column of a row, move to the row directly below). I interpret
    the top left corner as position [0,0] (row 0, column 0), the position directly to the right of it is [0,1]. The position
    directly below [0,0] is [1,0], etc. Please see board.py for more details on my coordinate system (which follows what I just described).
    
    There are no side effects because no game state is passed in; the referee will end up doing the placing based on
    what is returned by this function. 
    
    Assumption: The board has enough free slots for which penguins can be placed.
    """

    def zig_zag(self):
        board = self.__game_state.get_board()
        # Python ranges are exclusive at the end
        # i=0 i < len(board.get_tiles()) i++
        for i in range(0, len(board.get_tiles())):
            # j=0 j < (board.get_tiles())[i] j++
            for j in range(0, len(board.get_tiles()[i])):
                desired_posn = [i, j]
                if self.__game_state.is_unoccupied(desired_posn) and \
                        not self.__game_state.is_hole(desired_posn):
                    return desired_posn

    """
    int -> Action
    
    Picks the action that will realize the "best gain" (highest score/fish count) for the player whose turn it currently is,
    after looking ahead n > 0 turns in the tree. I return an Action because at the end of the day, the spec says that it 
    is a player's CHOICE OF ACTION, NOT THAT THE PLAYER HAS TO PERFORM THAT ACTION. If the game terminates before n turns
    can be executed, then it just takes the player's score at the node where the game ends. 
    
    If there is no Action to take, then it will return the empty Action that I described in the Action data def at the
    top of this file. Basically just an empty tuple ().
    
    """

    def which_action_to_take(self, n):
        if n == 0:
            raise ValueError("N must be greater than 0")

        # instantiates the game tree because now we need it (next_layer must still be called to kick off the generator)
        self.__game_tree = GameTree(self.__game_state)
        self.__game_tree.next_layer()

        actions_to_scores = {}
        for action, node in self.__game_tree.get_map_action_to_child_nodes(
        ).items():
            actions_to_scores[action] = (self.__minimax(node, n - 1))

        # game over
        if actions_to_scores == {}:
            return ()

        # this is the best gain
        optimal_score = max(actions_to_scores.values())

        optimal_actions = [
            action for action in actions_to_scores
            if actions_to_scores[action] == optimal_score
        ]

        return self.__get_optimal_action(optimal_actions)

    """
    GameTree int -> int

    Finds the best gain (highest score) that this Player can obtain after n turns, assuming that all other Players want to
    minimize this Player's gain, via the minimax algorithm. This will terminate because you can see that I have a base
    case where I stop if the appropriate number of turns has been played or if the game is over
    at the given state.
    """

    def __minimax(self, node, n):
        # base case, either the player has finished all their turns or there are no moves left after this move
        if n == 0 or node.get_game_state().is_game_over():
            return node.get_game_state().get_player_score(self.__player_id)
        # it is the maximizing player's turn
        if node.get_game_state().get_player_order()[0] == self.__player_id:
            value = float(
                '-inf'
            )  # because we need the absolute lowest possible value to compare to
            node.next_layer()
            for action, child_node in node.get_map_action_to_child_nodes(
            ).items():
                value = max(value, self.__minimax(child_node, n - 1))
            return value
        # it is all opponents' turn
        else:
            value = float(
                '+inf'
            )  # because we need the absolute highest possible value to compare to
            node.next_layer()
            for action, child_node in node.get_map_action_to_child_nodes(
            ).items():
                value = min(value, self.__minimax(child_node, n))
            return value

    """
    [int] [Action] -> Action
    
    Finds the Action, out of all the possible candidate Actions leading to the best gain (highest fish count),
    that the player should take in order to obtain the best gain. If there are multiple Actions that can
    lead to the same gain, tiebreaker functionality is executed.
    """

    def __get_optimal_action(self, optimal_actions):
        # this means there are multiple optimal actions that can lead to the same best gain
        if len(optimal_actions) > 1:
            # tiebreaker for top most row of from position, top most col of from position
            optimal_actions = self.__tiebreaker(optimal_actions, 0)
            # tiebreaker top most row of to position, top most col of to position. It will terminate because there
            # cannot be two distinct Actions at the same time with the same from and to position: that would indicate duplicate Actions.
            if len(optimal_actions) > 1:
                optimal_actions = self.__tiebreaker(optimal_actions, 1)
            optimal_action = optimal_actions[0]
        else:
            optimal_action = optimal_actions[0]
        return optimal_action

    """
    [Action] int -> [Action]
    The which_pos arg is one of the indexes in an Action for either a start position or destination position.
    The start position is the first tuple in the Action, so index would be 0, and the destination position is the second tuple 
    in the Action, so index would be 1.
    
    Applies tiebreaker functionality to Actions.
    """

    @staticmethod
    def __tiebreaker(lo_actions, which_pos):
        # the list of different positions, either from or to positions
        lo_pos = []
        for action in lo_actions:
            lo_pos.append(action[which_pos])
        # finding the lowest row/ lowest column
        final_pos = (sorted(lo_pos))[0]
        final_actions = []
        for action in lo_actions:
            if action[which_pos] == final_pos:
                final_actions.append(action)
        return final_actions

    """
    Nothing -> GameState
    Getter method for the game state class attribute. 
    """

    def get_game_state(self):
        return self.__game_state

    """
    Nothing -> GameTree
    Getter method for the game tree class attribute. 
    """

    def get_game_tree(self):
        return self.__game_tree

    """
    Nothing -> int
    Getter method for the player id class attribute. 
    """

    def get_player_id(self):
        return self.__player_id
Exemplo n.º 7
0
 def __is_illegal_action(self, action):
     game_tree = GameTree(self.__current_game_state)
     game_tree.next_layer()
     return action not in game_tree.get_map_action_to_child_nodes()