示例#1
0
    def __fire_game_state_changed(self):
        """
        Signals that the game state has changed and it is time to update the game tree, sync all the players and
        notify all subscribed observers about the new state. It notifies observers so by calling their provided
        callbacks on a copy of the latest game state.
        """
        # Update game tree
        self.__game_tree = GameTree(self.__state)

        # Notify all parties subscribed for game updates
        state_to_broadcast = self.__state.deepcopy()

        # Cycle over players and sync them
        for p in self.__players:
            p.sync(state_to_broadcast)

        # Cycle over game update callbacks and call each one
        # with a copy of the latest state
        for callback in self.__game_update_callbacks:
            try:
                callback(state_to_broadcast)
            except AssertionError as e:
                # Raise assertion exceptions are these are used for testing
                raise e
            except Exception as e:
                print(f'Exception occurred, removing observer: {e}')
示例#2
0
    def play_game(self):
        """
        Plays a game of Othello.
        -> Void
        """
        self.__black_player.set_gametree(GameTree(Board(), BLACK))
        self.__white_player.set_gametree(GameTree(Board(), BLACK))

        while (not self.__game_tree.is_game_over()):
            if (self.__game_tree.curr_turn == BLACK):
                move = self.__black_player.get_move(self.__game_tree.board)
            else:
                move = self.__white_player.get_move(self.__game_tree.board)
            valid_actions = self.__game_tree.get_actions()
            if (move == SKIP
                    and SKIP in valid_actions) or move in valid_actions:
                self.update_players(move)
                cur = self.__game_tree.curr_turn
                self.__game_tree = self.__game_tree.apply_move(move)
                if DEBUG:
                    print("%s MOVED %s" % (cur, move))
                    self.__game_tree.board.render()
        return {
            BLACK: self.__game_tree.get_score(BLACK),
            WHITE: self.__game_tree.get_score(WHITE)
        }
示例#3
0
    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)
示例#4
0
def _find_action_to_neighboring_tile(state: State, to_pos: Position) -> Action:
    """
    Finds the action the second player can take to move to one of the tiles
    that neighbors the first player's ending position if it exists.  
    Otherwise, None is returned. 
    
    Actions will be searched for by checking neighboring
    tiles in the following order: North, Northeast, Southeast, South, Southwest, Northwest.
    
    If there are multiple avatars that can reach neighboring tiles, a tie breaking algorithm
    will choose the action with the top-most row of the from position, the left-most column
    of the from position, the top-most row of the to position, and the left-most column
    of the to position in that order.

    :param state: the current state resulting from the first player successfully executing their desired action
    :param to_pos: the position of the tile that the first player landed on after executing their action
    :return: an Action the second player can take to get to a neighboring tile or None if no such action exists
    """
    if not isinstance(state, State):
        raise TypeError("Expected State for state.")
    if not isinstance(to_pos, Position):
        raise TypeError("Expected Position for to_pos.")

    # Get player placements from state
    player_placements = state.placements

    # Second player's color
    second_player_color = state.player_order[0]

    # Search each direction and see if any of player 2's positions can reach a neighboring tile
    # in that direction
    for direction in directions_to_try:
        # The new target position for player 2 in the current direction
        new_pos = _get_next_position(to_pos, direction)

        # List to store the potential valid actions to reach the neighboring tile in the current direction
        valid_actions = []

        for pos in player_placements[second_player_color]:
            try:
                # The action to be tried
                new_action = Action(pos, new_pos)

                # Try the action
                gt = GameTree(state)
                gt.try_action(new_action)

                # If this point is reached with no exception, it is a valid action.
                valid_actions.append(new_action)
            except InvalidActionException:
                pass
            except GameNotRunningException:
                return None

        # If any valid actions were found, either return one of them or tiebreak
        if len(valid_actions) > 0:
            return min(valid_actions)

    return None
    def test_apply_to_child_states_success3(self):
        # Tests successful apply_to_child_states where each child state maps to
        # that a given player's score (heterogeneous board)
        result = GameTree.apply_to_child_states(
            GameTree(self.__state5),
            lambda state: state.get_player_score(Color.WHITE))

        self.assertSequenceEqual(result, [3, 3, 3, 2, 1, 1, 1, 1])
示例#6
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"
    def test_apply_to_child_states_success1(self):
        # Tests successful apply_to_child_states where each child state maps to
        # that state's current player
        result = GameTree.apply_to_child_states(
            GameTree(self.__state2), lambda state: state.current_player)

        # Resulting array should consist of the next player's id the same number of times
        # as there are reachable states
        self.assertSequenceEqual(result, [Color.WHITE] * 7)
    def test_apply_to_child_states_success2(self):
        # Tests successful apply_to_child_states where each child state maps to
        # that a given player's score (homogeneous board)
        result = GameTree.apply_to_child_states(
            GameTree(self.__state2),
            lambda state: state.get_player_score(Color.RED))

        # Score should be the same (5) all around since this is a homogeneous board with
        # 5 fish to each tile
        self.assertSequenceEqual(result, 7 * [5])
示例#9
0
 def __init_game(self, players):
     """
     Sets up an initial game of Othello with the given players (must be exactly 2).
     The first player in the list is assigned the black tokens, and the second white
     Initializes the game tree.
     """
     self.__black_player = players[0]
     self.__white_player = players[1]
     self.__black_player.set_color(BLACK)
     self.__white_player.set_color(WHITE)
     self.__game_tree = GameTree(Board(), BLACK)
示例#10
0
文件: sim.py 项目: reutzee/Intro-AI
 def __init__(self,
              graph_file,
              agent_file,
              cutoff_depth,
              ping_pong,
              mode='adversarial'):
     self.ping_pong = ping_pong
     self.graph_file = graph_file
     self.time = 0  # track time of the world
     self.evacuated = 0  # total number of people evacuated
     self.agents_history = []  # search paths of smart agents
     self.cutoff_depth = int(cutoff_depth)
     self.world = World(graph_file=graph_file, k=self.prompt_k())
     self.deadline = self.world.get_deadline()
     self.agents = self.get_agents_data(agent_file=agent_file,
                                        world=self.world)
     self.init_state = self.create_init_states(self.world, self.agents)
     self.mode = mode
     if not ping_pong:
         self.game_tree = GameTree(self.init_state,
                                   self.world,
                                   self.agents,
                                   mode=mode,
                                   cutoff_depth=self.cutoff_depth)
     super(HurricaneGameSimulator, self).__init__(state=self.world,
                                                  agents=self.agents)
示例#11
0
class Referee:
    def __init__(self, players):
        self.__observers = []
        self.__init_game(players)

    def __init_game(self, players):
        """
        Sets up an initial game of Othello with the given players (must be exactly 2).
        The first player in the list is assigned the black tokens, and the second white
        Initializes the game tree.
        """
        self.__black_player = players[0]
        self.__white_player = players[1]
        self.__black_player.set_color(BLACK)
        self.__white_player.set_color(WHITE)
        self.__game_tree = GameTree(Board(), BLACK)

    def play_game(self):
        """
        Plays a game of Othello.
        -> Void
        """
        self.__black_player.set_gametree(GameTree(Board(), BLACK))
        self.__white_player.set_gametree(GameTree(Board(), BLACK))

        while (not self.__game_tree.is_game_over()):
            if (self.__game_tree.curr_turn == BLACK):
                move = self.__black_player.get_move(self.__game_tree.board)
            else:
                move = self.__white_player.get_move(self.__game_tree.board)
            valid_actions = self.__game_tree.get_actions()
            if (move == SKIP
                    and SKIP in valid_actions) or move in valid_actions:
                self.update_players(move)
                cur = self.__game_tree.curr_turn
                self.__game_tree = self.__game_tree.apply_move(move)
                if DEBUG:
                    print("%s MOVED %s" % (cur, move))
                    self.__game_tree.board.render()
        return {
            BLACK: self.__game_tree.get_score(BLACK),
            WHITE: self.__game_tree.get_score(WHITE)
        }

    def update_players(self, move):
        self.__black_player.update_move(move)
        self.__white_player.update_move(move)
 def __init__(
     self, exploration_chance: float,
     game_tree: GameTree = GameTree()) -> None:
     self.name = 'Learning Strategy'
     self.desc = 'Chooses decisions based on what it believes will net it the most points.'
     self._game_tree = game_tree
     self._temp_tree = self._game_tree
     self._exploration_chance = exploration_chance
示例#13
0
def _get_next_state(state: State, from_pos: Position,
                    to_pos: Position) -> State:
    """
    Produces the subsequent game state from trying to move the first player's avatars
    from the given start position to the given end position. If the given action is illegal,
    an exception will be thrown. If the game has not been started fully yet, None will be returned.

    :param state: state containing starting player list and board as extracted by initialize_state
    :param from_pos: the starting position for the first player's desired action
    :param to_pos: the ending position for the first player's desired action
    :return: resulting state from executing Action(from_pos, to_pos)
    """
    if not isinstance(state, State):
        raise TypeError("Expected State for state.")
    if not isinstance(from_pos, Position):
        raise TypeError("Expected Position for from_pos.")
    if not isinstance(to_pos, Position):
        raise TypeError("Expected Position for to_pos.")

    # Initialize player placements
    player_placements = state.placements

    # Get first player color (it's currently the first player's turn)
    first_player_color = state.current_player

    # Verify that from_pos is one of player 1's current positions
    if from_pos not in player_placements[first_player_color]:
        raise InvalidActionException(
            "Expected from_pos to be one of player 1's current placements")

    # Attempt action
    try:
        new_state = GameTree.try_action(GameTree(state),
                                        Action(from_pos, to_pos))
        return new_state
    except InvalidActionException as e:
        raise e
    except GameNotRunningException:
        pass

    return None
示例#14
0
    def _minimax(self, root_move: tuple[int, int], depth: int,
                 game: ReversiGame, alpha: float, beta: float) -> GameTree:
        """
        _minimax is a minimax function with alpha-beta pruning implemented that returns
        a full GameTree where each node stores the given evaluation

        Preconditions
            - depth >= 0
        """
        white_move = (game.get_current_player() == -1)
        ret = GameTree(move=root_move, is_white_move=white_move)
        # early return at max depth
        if depth == self.depth:
            ret.evaluation = heuristic(game, self.heuristic_list)
            return ret
        possible_moves = list(game.get_valid_moves())
        if not possible_moves:
            if game.get_winner() == 'white':
                ret.evaluation = 10000
            elif game.get_winner() == 'black':
                ret.evaluation = -10000
            else:
                ret.evaluation = 0
            return ret
        random.shuffle(possible_moves)
        best_value = float('-inf')
        if not white_move:
            best_value = float('inf')
        for move in possible_moves:
            new_game = game.copy_and_make_move(move)
            new_tree = self._minimax(move, depth + 1, new_game, alpha, beta)
            ret.add_subtree(new_tree)
            # we update the alpha value when the maximizer is playing (white)
            if white_move and best_value < new_tree.evaluation:
                best_value = new_tree.evaluation
                alpha = max(alpha, best_value)
                if beta <= alpha:
                    break
            # we update the beta value when the minimizer is playing (black)
            elif not white_move and best_value > new_tree.evaluation:
                best_value = new_tree.evaluation
                beta = min(beta, best_value)
                if beta <= alpha:
                    break
        ret.evaluation = best_value
        return ret
示例#15
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]
示例#16
0
 def _minimax(self, root_move: tuple[int, int], game: ReversiGame,
              depth: int) -> GameTree:
     """
     _minimax is a function that returns a tree where each node has a value determined by
     the minimax search algorithm
     """
     white_move = (game.get_current_player() == -1)
     ret = GameTree(move=root_move, is_white_move=white_move)
     # early return if we have reached max depth
     if depth == self.depth:
         ret.evaluation = heuristic(game, self.heuristic_list)
         return ret
     possible_moves = list(game.get_valid_moves())
     # game is over if there are no possible moves in a position
     if not possible_moves:
         # if there are no moves, then the game is over so we check for the winner
         if game.get_winner() == 'white':
             ret.evaluation = 10000
         elif game.get_winner() == 'black':
             ret.evaluation = -10000
         else:
             ret.evaluation = 0
         return ret
     # shuffle for randomness
     random.shuffle(possible_moves)
     # best_value tracks the best possible move that the player can make
     # this value is maximized by white and minimized by black
     best_value = float('-inf')
     if not white_move:
         best_value = float('inf')
     for move in possible_moves:
         new_game = game.copy_and_make_move(move)
         new_subtree = self._minimax(move, new_game, depth + 1)
         if white_move:
             best_value = max(best_value, new_subtree.evaluation)
         else:
             best_value = min(best_value, new_subtree.evaluation)
         ret.add_subtree(new_subtree)
     # update the evaluation value of the tree once all subtrees are added
     ret.evaluation = best_value
     return ret
示例#17
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)))
示例#18
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
示例#19
0
    def start(self) -> None:
        """
        This method starts the game by first running a series of placement rounds and then
        prompting each player to make a move until game end. At game end, it provides all pertinent
        parties with a copy of the game report.

        :return: None
        """
        # Return if we already started
        if self.__started:
            return

        # RUN ON A SEPARATE THREAD
        # Indicate that game has started
        self.__started = True
        # Run placement rounds
        if self.__run_placements():
            # Initialize game tree for rule checking
            self.__game_tree = GameTree(self.__state)
            # Run game
            self.__run_game()

        # End game
        self.__fire_game_over()
示例#20
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)))
示例#21
0
def main():

    d = 5
    bf = 2
    data_size = 1
    rollout_num = 50

    data_set = GameTree(probability=[0.8, 0.6],
                        d=d,
                        bf=bf,
                        data_size=data_size,
                        tree_name='kocsis')

    results = [[] for i in range(data_size)]
    accuracy = [[] for i in range(data_size)]
    for i in range(data_size):
        ans_path = mini_max.get_minimax_path(tree=data_set.tree[i],
                                             d=d,
                                             bf=bf,
                                             draw=True)
        results[i], accuracy[i] = mcts(tree=data_set.tree[i],
                                       n=rollout_num,
                                       ans_path=ans_path,
                                       algo_name='UCT')

        print("{}%done, ans={},results={}".format((float(i) / data_size) * 100,
                                                  ans_path, results[i]))

    utils.print_tree(tree=data_set.tree[-1], d=d, bf=bf, data_name='ucb')

    #print("ans={},minimax_ans={}".format(ans, minimax_ans))

    #correct_rate = accuracy(ans, minimax_ans)
    #print(accuracy)

    means = np.zeros(rollout_num)
    accuracy = np.array(accuracy)
    for i in range(rollout_num):
        means[i] = np.mean(accuracy[:, i])

    print("result = {}".format(results[-1]))
    print("means = {}".format(means))
示例#22
0
    def get_best_action(state: State, depth: int) -> Action:
        """
        This method determines the best action for the current player by looking ahead at most
        depth number of current-player turns and considering the most detrimental move an opponent
        can make in each of them. It then returns the action tied to the maximum score for the provided
        state's current player based on those scenarios in which its opponents make the "worst" (most
        score minimizing) moves for the player.

        If there are multiple best action leading to the same score, then the one with the smallest
        source row, source column, destination row or destination column is picked (in that order). If the current
        player gets stuck during the tree traversal and the provided depth has not been reached, the traversal is
        aborted and the best running move is returned.

        :param state: state which to determine best move for current player for
        :param depth: how many the current player in the provided states gets to go at most
        :return: best Action current player can make best on mini-max strategy
        """
        # Validate parameters
        if not isinstance(state, State):
            raise TypeError('Expected State object for state!')

        if not isinstance(depth, int) or depth <= 0:
            raise TypeError('Expected integer >= 0 for depth!')

        # Make up a game tree for the state
        tree = GameTree(state)

        # Determine min-max score for current child state
        score, best_move = Strategy.__mini_max_search(tree,
                                                      state.current_player,
                                                      depth)

        if Strategy.DEBUG:
            print(f'  [depth={depth}] max score: {score} {best_move}')
            print(
                f'  Score [before action]: {state.get_player_by_color(state.current_player).score}'
            )
        # Return "best" action associated with the best score
        return best_move
示例#23
0
def generate_game_tree(game_state: dict, level: int, player: str, parent: GameTree = None) -> GameTree:
    """

    :param game_state:
    :param level:
    :param player:
    :param parent:
    :return:
    """
    if level == 0:
        return None
    possible_states = generate_possible_states(game_state, player)
    current_state = GameTree(game_state, parent=parent, player=player)
    for state in possible_states:
        child = generate_game_tree(state, level - 1, "C" if player == "H" else "H", parent=current_state)
        current_state.add_children(child)
    current_state.evaluate_current_state()
    return current_state
示例#24
0
# game continues until someone wins or user quits with '-1'
while True:
    if current_player['type'] == 1:
        move = input("enter a column number to place chip (0-{}, or -1 to quit)".format(width - 1))
        while True:
            if move == -1:
                exit(0)
            is_valid = board.place_chip(move, current_player)
            if is_valid:
                break
            else:
                move = input("invalid move (out of width or height), try again (0-{}, or -1 to quit)".format(width - 1))
    else:
        # I realize now my heuristic should've handled this, but at the time I was thinking
        # of this as forward pruning.  I know that the best first two moves are in the middle
        # column, so I just said, if it's within the first 2 moves put my chip in the middle,
        # no need to examine further
        if len(board.data) < 2:
            board.place_chip(board.width/2, current_player)
        else:
            print "thinking...\n"
            game_tree = GameTree(board, current_player, time_limit)
            move = game_tree.calc_move()
            board.place_chip(move[1], current_player)
    board.display_board()
    if board.winner(current_player):
        print "player {} wins!".format(current_player['symbol'][0])
        break
    current_player = players.next()
 def test_try_action_fail5(self):
     # Tests a failing try_action due to action involves moving to an already occupied tile
     with self.assertRaises(InvalidActionException):
         GameTree.try_action(GameTree(self.__state2),
                             Action(Position(4, 0), Position(3, 0)))
 def test_init_success(self):
     # Tests successful game tree constructor
     GameTree(self.__state2)
 def test_try_action_fail4(self):
     # Tests a failing try_action due to action involves moving thru another character
     with self.assertRaises(InvalidActionException):
         GameTree.try_action(GameTree(self.__state2),
                             Action(Position(4, 0), Position(2, 1)))
 def test_try_action_fail3(self):
     # Tests a failing try_action due to action being out of turn (it involves moving someone
     # else but the current player's avatar, despite otherwise being legal)
     with self.assertRaises(InvalidActionException):
         GameTree.try_action(GameTree(self.__state2),
                             Action(Position(0, 1), Position(2, 1)))
 def test_try_action_fail2(self):
     # Tests a failing try_action due to action being invalid (not accessible via a straight
     # line path)
     with self.assertRaises(InvalidActionException):
         GameTree.try_action(GameTree(self.__state2),
                             Action(Position(3, 0), Position(0, 0)))
    def __init__(self, *args, **kwargs):
        super(GameTreeTests, self).__init__(*args, **kwargs)

        # Initialize boards
        self.__board1 = Board.homogeneous(5, 5, 3)
        self.__board2 = Board.homogeneous(3, 5, 2)
        self.__board3 = Board({
            Position(0, 0): Tile(5),
            Position(0, 1): Tile(3),
            Position(0, 2): Tile(2),
            Position(1, 0): Tile(2),
            Position(1, 1): Tile(3),
            Position(1, 2): Tile(2),
            Position(2, 0): Tile(3),
            Position(2, 1): Tile(4),
            Position(2, 2): Tile(1),
            Position(3, 0): Tile(1),
            Position(3, 1): Tile(1),
            Position(3, 2): Tile(5),
            Position(4, 0): Tile(2),
            Position(4, 1): Tile(3),
            Position(4, 2): Tile(4)
        })

        # Initialize some players for testing
        self.__p1 = PlayerEntity("John", Color.RED)
        self.__p2 = PlayerEntity("George", Color.WHITE)
        self.__p3 = PlayerEntity("Gary", Color.BLACK)
        self.__p4 = PlayerEntity("Jeanine", Color.BROWN)
        self.__p5 = PlayerEntity("Obama", Color.RED)
        self.__p6 = PlayerEntity("Fred", Color.BROWN)
        self.__p7 = PlayerEntity("Stewart", Color.WHITE)
        self.__p8 = PlayerEntity("Bobby Mon", Color.BLACK)
        self.__p9 = PlayerEntity("Bob Ross", Color.WHITE)
        self.__p10 = PlayerEntity("Eric Khart", Color.BROWN)
        self.__p11 = PlayerEntity("Ionut", Color.RED)

        # ========================== STATE 1 ==========================

        # Initialize a premature state
        self.__state1 = State(self.__board1,
                              [self.__p1, self.__p2, self.__p3, self.__p4])

        # ========================== STATE 2 ==========================

        # Initialize a finalized state where at least two more rounds are possible
        self.__state2 = State(self.__board1, [self.__p1, self.__p2, self.__p3])
        # Place all avatars
        # Player 1 place
        self.__state2.place_avatar(Color.RED, Position(4, 0))
        # Player 2 place
        self.__state2.place_avatar(Color.WHITE, Position(0, 1))
        # Player 3 place
        self.__state2.place_avatar(Color.BLACK, Position(2, 2))
        # Player 1 place
        self.__state2.place_avatar(Color.RED, Position(1, 0))
        # Player 2 place
        self.__state2.place_avatar(Color.WHITE, Position(2, 0))
        # Player 3 place
        self.__state2.place_avatar(Color.BLACK, Position(3, 2))
        # Player 1 place
        self.__state2.place_avatar(Color.RED, Position(1, 1))
        # Player 2 place
        self.__state2.place_avatar(Color.WHITE, Position(4, 1))
        # Player 3 place
        self.__state2.place_avatar(Color.BLACK, Position(3, 0))

        # Make up tree for this state
        self.__tree2 = GameTree(self.__state2)

        # ========================== STATE 3 ==========================
        # Setup state that is one move away from game over
        self.__state3 = State(
            self.__board2,
            players=[self.__p5, self.__p6, self.__p7, self.__p8])

        # Set up the board with placements s.t. only 2 moves can be made
        # Player 1
        self.__state3.place_avatar(Color.RED, Position(3, 0))
        # Player 2
        self.__state3.place_avatar(Color.BROWN, Position(0, 0))
        # Player 3
        self.__state3.place_avatar(Color.WHITE, Position(1, 0))
        # Player 4
        self.__state3.place_avatar(Color.BLACK, Position(2, 0))
        # Player 1
        self.__state3.place_avatar(Color.RED, Position(3, 1))
        # Player 2
        self.__state3.place_avatar(Color.BROWN, Position(0, 1))
        # Player 3
        self.__state3.place_avatar(Color.WHITE, Position(1, 1))
        # Player 4
        self.__state3.place_avatar(Color.BLACK, Position(2, 1))
        # Make move 1 for p1
        self.__state3.move_avatar(Position(3, 1), Position(4, 1))

        # Make up tree for this state
        self.__tree3 = GameTree(self.__state3)

        # ========================== STATE 4 ==========================
        # Setup state that is game over
        self.__state4 = copy.deepcopy(self.__state3)

        # Make final move
        self.__state4.move_avatar(Position(2, 0), Position(4, 0))

        # Make up tree for this state
        self.__tree4 = GameTree(self.__state4)

        # ========================== STATE 5 ==========================
        # Setup state that includes heterogeneous board
        self.__state5 = State(self.__board3,
                              players=[self.__p9, self.__p10, self.__p11])

        # Player 1
        self.__state5.place_avatar(Color.WHITE, Position(2, 0))
        # Player 2
        self.__state5.place_avatar(Color.BROWN, Position(0, 1))
        # Player 3
        self.__state5.place_avatar(Color.RED, Position(0, 2))
        # Player 1
        self.__state5.place_avatar(Color.WHITE, Position(1, 0))
        # Player 2
        self.__state5.place_avatar(Color.BROWN, Position(1, 2))
        # Player 3
        self.__state5.place_avatar(Color.RED, Position(0, 0))
        # Player 1
        self.__state5.place_avatar(Color.WHITE, Position(3, 1))
        # Player 2
        self.__state5.place_avatar(Color.BROWN, Position(2, 1))
        # Player 3
        self.__state5.place_avatar(Color.RED, Position(3, 2))

        # Make up tree for this state
        self.__tree5 = GameTree(self.__state5)
class GameTreeTests(unittest.TestCase):
    def __init__(self, *args, **kwargs):
        super(GameTreeTests, self).__init__(*args, **kwargs)

        # Initialize boards
        self.__board1 = Board.homogeneous(5, 5, 3)
        self.__board2 = Board.homogeneous(3, 5, 2)
        self.__board3 = Board({
            Position(0, 0): Tile(5),
            Position(0, 1): Tile(3),
            Position(0, 2): Tile(2),
            Position(1, 0): Tile(2),
            Position(1, 1): Tile(3),
            Position(1, 2): Tile(2),
            Position(2, 0): Tile(3),
            Position(2, 1): Tile(4),
            Position(2, 2): Tile(1),
            Position(3, 0): Tile(1),
            Position(3, 1): Tile(1),
            Position(3, 2): Tile(5),
            Position(4, 0): Tile(2),
            Position(4, 1): Tile(3),
            Position(4, 2): Tile(4)
        })

        # Initialize some players for testing
        self.__p1 = PlayerEntity("John", Color.RED)
        self.__p2 = PlayerEntity("George", Color.WHITE)
        self.__p3 = PlayerEntity("Gary", Color.BLACK)
        self.__p4 = PlayerEntity("Jeanine", Color.BROWN)
        self.__p5 = PlayerEntity("Obama", Color.RED)
        self.__p6 = PlayerEntity("Fred", Color.BROWN)
        self.__p7 = PlayerEntity("Stewart", Color.WHITE)
        self.__p8 = PlayerEntity("Bobby Mon", Color.BLACK)
        self.__p9 = PlayerEntity("Bob Ross", Color.WHITE)
        self.__p10 = PlayerEntity("Eric Khart", Color.BROWN)
        self.__p11 = PlayerEntity("Ionut", Color.RED)

        # ========================== STATE 1 ==========================

        # Initialize a premature state
        self.__state1 = State(self.__board1,
                              [self.__p1, self.__p2, self.__p3, self.__p4])

        # ========================== STATE 2 ==========================

        # Initialize a finalized state where at least two more rounds are possible
        self.__state2 = State(self.__board1, [self.__p1, self.__p2, self.__p3])
        # Place all avatars
        # Player 1 place
        self.__state2.place_avatar(Color.RED, Position(4, 0))
        # Player 2 place
        self.__state2.place_avatar(Color.WHITE, Position(0, 1))
        # Player 3 place
        self.__state2.place_avatar(Color.BLACK, Position(2, 2))
        # Player 1 place
        self.__state2.place_avatar(Color.RED, Position(1, 0))
        # Player 2 place
        self.__state2.place_avatar(Color.WHITE, Position(2, 0))
        # Player 3 place
        self.__state2.place_avatar(Color.BLACK, Position(3, 2))
        # Player 1 place
        self.__state2.place_avatar(Color.RED, Position(1, 1))
        # Player 2 place
        self.__state2.place_avatar(Color.WHITE, Position(4, 1))
        # Player 3 place
        self.__state2.place_avatar(Color.BLACK, Position(3, 0))

        # Make up tree for this state
        self.__tree2 = GameTree(self.__state2)

        # ========================== STATE 3 ==========================
        # Setup state that is one move away from game over
        self.__state3 = State(
            self.__board2,
            players=[self.__p5, self.__p6, self.__p7, self.__p8])

        # Set up the board with placements s.t. only 2 moves can be made
        # Player 1
        self.__state3.place_avatar(Color.RED, Position(3, 0))
        # Player 2
        self.__state3.place_avatar(Color.BROWN, Position(0, 0))
        # Player 3
        self.__state3.place_avatar(Color.WHITE, Position(1, 0))
        # Player 4
        self.__state3.place_avatar(Color.BLACK, Position(2, 0))
        # Player 1
        self.__state3.place_avatar(Color.RED, Position(3, 1))
        # Player 2
        self.__state3.place_avatar(Color.BROWN, Position(0, 1))
        # Player 3
        self.__state3.place_avatar(Color.WHITE, Position(1, 1))
        # Player 4
        self.__state3.place_avatar(Color.BLACK, Position(2, 1))
        # Make move 1 for p1
        self.__state3.move_avatar(Position(3, 1), Position(4, 1))

        # Make up tree for this state
        self.__tree3 = GameTree(self.__state3)

        # ========================== STATE 4 ==========================
        # Setup state that is game over
        self.__state4 = copy.deepcopy(self.__state3)

        # Make final move
        self.__state4.move_avatar(Position(2, 0), Position(4, 0))

        # Make up tree for this state
        self.__tree4 = GameTree(self.__state4)

        # ========================== STATE 5 ==========================
        # Setup state that includes heterogeneous board
        self.__state5 = State(self.__board3,
                              players=[self.__p9, self.__p10, self.__p11])

        # Player 1
        self.__state5.place_avatar(Color.WHITE, Position(2, 0))
        # Player 2
        self.__state5.place_avatar(Color.BROWN, Position(0, 1))
        # Player 3
        self.__state5.place_avatar(Color.RED, Position(0, 2))
        # Player 1
        self.__state5.place_avatar(Color.WHITE, Position(1, 0))
        # Player 2
        self.__state5.place_avatar(Color.BROWN, Position(1, 2))
        # Player 3
        self.__state5.place_avatar(Color.RED, Position(0, 0))
        # Player 1
        self.__state5.place_avatar(Color.WHITE, Position(3, 1))
        # Player 2
        self.__state5.place_avatar(Color.BROWN, Position(2, 1))
        # Player 3
        self.__state5.place_avatar(Color.RED, Position(3, 2))

        # Make up tree for this state
        self.__tree5 = GameTree(self.__state5)

    def test_init_fail1(self):
        # Tests failing constructor due to an invalid state
        with self.assertRaises(TypeError):
            GameTree("mickey mouse")

    @unittest.skip(
        "state does not check for the number of remaining avatars to place anymore"
    )
    def test_init_fail2(self):
        # Tests failing constructor due to state not being 'started' (or due
        # to not everyone having placed their avatars).
        with self.assertRaises(GameNotRunningException):
            GameTree(self.__state1)

    def test_init_success(self):
        # Tests successful game tree constructor
        GameTree(self.__state2)

    def test_get_next1(self):
        # Tests the get_next() of a GameTree where at least two more rounds
        # are possible.
        # It's player 1's turn in the state corresponding to this tree
        self.assertEqual(self.__tree2.state.current_player, Color.RED)

        # Make sure it has generated all possible connecting trees
        self.assertSequenceEqual(self.__state2.get_possible_actions(),
                                 self.__tree2.all_possible_actions)

        # Cycle over child trees, check current player, and check move log to make sure
        # move has been made for the state.
        for action, tree in self.__tree2.get_next():
            # Make sure it's player 2's turn in the state corresponding to this tree
            self.assertEqual(tree.state.current_player, Color.WHITE)

            # Make sure action was the last action that happened
            self.assertEqual(action, tree.state.move_log[-1])

    def test_get_next2(self):
        # Tests the get_next() out of a GameTree where one more round is possible
        # It's player 4's turn in the state corresponding to this tree
        self.assertEqual(self.__tree3.state.current_player, Color.BLACK)

        # Make sure it has generated all possible connecting trees
        self.assertSequenceEqual(self.__tree3.all_possible_actions,
                                 [Action(Position(2, 0), Position(4, 0))])

        # Cycle over child trees, check current player, and check move log to make sure
        # move has been made for the state.
        for action, tree in self.__tree3.get_next():
            # Make sure it's player 11's turn in the state corresponding to this tree
            self.assertEqual(tree.state.current_player, Color.BLACK)

            # Make sure action was the last action that happened
            self.assertEqual(action, tree.state.move_log[-1])

            # Make sure it's game over
            self.assertFalse(tree.state.can_anyone_move())

            # Make sure no more child states are possible for it is game
            # over
            self.assertSequenceEqual(tree.all_possible_actions, [])

    def test_get_next3(self):
        # Tests the get_next() of a GameTree where no more rounds is possible (aka
        # game over state).
        for _, _ in self.__tree4.get_next():
            self.assertTrue(False)

        # Make sure is has not generated any more trees (as there are no more
        # possible moves)
        self.assertSequenceEqual(self.__tree4.all_possible_actions, [])

    def test_get_next4(self):
        # Tests the get_next() out of a GameTree and that of its children
        # It's player 1's turn in the state corresponding to this tree
        self.assertEqual(self.__tree2.state.current_player, Color.RED)

        # Cycle over child trees, check current player, and check move log to make sure
        # move has been made for the state.
        for action1, tree1 in self.__tree2.get_next():
            # Make sure it has generated all possible connecting edges
            self.assertSequenceEqual(tree1.state.get_possible_actions(),
                                     tree1.all_possible_actions)

            for action2, tree2 in self.__tree2.get_next():
                # Make sure it has generated all possible connecting edges for current
                # child tree
                self.assertSequenceEqual(tree2.state.get_possible_actions(),
                                         tree2.all_possible_actions)

                # Make sure it's player 2's turn in the state corresponding to this tree
                self.assertEqual(tree2.state.current_player, Color.WHITE)

                # Make sure action was the last action that happened
                self.assertEqual(action2, tree2.state.move_log[-1])

    def test_try_action_fail1(self):
        # Tests a failing try_action due to action being invalid (type-wise)
        with self.assertRaises(TypeError):
            self.__tree2.try_action(Position(0, 0), Position(1, 0))

    def test_try_action_fail2(self):
        # Tests a failing try_action due to action being invalid (not accessible via a straight
        # line path)
        with self.assertRaises(InvalidActionException):
            GameTree.try_action(GameTree(self.__state2),
                                Action(Position(3, 0), Position(0, 0)))

    def test_try_action_fail3(self):
        # Tests a failing try_action due to action being out of turn (it involves moving someone
        # else but the current player's avatar, despite otherwise being legal)
        with self.assertRaises(InvalidActionException):
            GameTree.try_action(GameTree(self.__state2),
                                Action(Position(0, 1), Position(2, 1)))

    def test_try_action_fail4(self):
        # Tests a failing try_action due to action involves moving thru another character
        with self.assertRaises(InvalidActionException):
            GameTree.try_action(GameTree(self.__state2),
                                Action(Position(4, 0), Position(2, 1)))

    def test_try_action_fail5(self):
        # Tests a failing try_action due to action involves moving to an already occupied tile
        with self.assertRaises(InvalidActionException):
            GameTree.try_action(GameTree(self.__state2),
                                Action(Position(4, 0), Position(3, 0)))

    def test_try_action_success(self):
        # Tests a successful try_action where a valid action gets executed
        valid_action = Action(Position(1, 0), Position(4, 2))
        new_state = self.__tree2.try_action(valid_action)

        # Make sure the valid action we gave it got executed and is at the top
        # of the move log
        self.assertEqual(valid_action, new_state.move_log[-1])
        # Make sure it's second player's turn now
        self.assertEqual(new_state.current_player, Color.WHITE)

    def test_apply_to_child_states_fail1(self):
        # Tests apply_to_child_states failing due to invalid function (type-wise)
        with self.assertRaises(TypeError):
            self.__tree2.apply_to_child_states(lambda state, _: state)

    def test_apply_to_child_states_success1(self):
        # Tests successful apply_to_child_states where each child state maps to
        # that state's current player
        result = GameTree.apply_to_child_states(
            GameTree(self.__state2), lambda state: state.current_player)

        # Resulting array should consist of the next player's id the same number of times
        # as there are reachable states
        self.assertSequenceEqual(result, [Color.WHITE] * 7)

    def test_apply_to_child_states_success2(self):
        # Tests successful apply_to_child_states where each child state maps to
        # that a given player's score (homogeneous board)
        result = GameTree.apply_to_child_states(
            GameTree(self.__state2),
            lambda state: state.get_player_score(Color.RED))

        # Score should be the same (5) all around since this is a homogeneous board with
        # 5 fish to each tile
        self.assertSequenceEqual(result, 7 * [5])

    def test_apply_to_child_states_success3(self):
        # Tests successful apply_to_child_states where each child state maps to
        # that a given player's score (heterogeneous board)
        result = GameTree.apply_to_child_states(
            GameTree(self.__state5),
            lambda state: state.get_player_score(Color.WHITE))

        self.assertSequenceEqual(result, [3, 3, 3, 2, 1, 1, 1, 1])