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}')
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 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)
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])
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])
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 __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)
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
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
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
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]
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
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)))
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
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()
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)))
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))
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
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
# 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])