def __perform_simulation(game_service: GameService, action: Action, player: Player): if player.active: try: game_service.visited_cells_by_player[player.id] = \ game_service.get_and_visit_cells(player, action) except InvalidPlayerMoveException: game_service.set_player_inactive(player)
def __create_child(self, player: Player, action: Action, turn_counter: int, max_speed: int): if player.speed == max_speed and action == Action.speed_up: return modified_game = self._game.copy() game_service = GameService(modified_game) game_service.turn.turn_ctr = turn_counter SearchTreeRoot.__perform_simulation(game_service, action, modified_game.get_player_by_id(player.id)) game_service.check_and_set_died_players() return SearchTreeNode(modified_game.copy(), action)
def setUp(self): self.player1 = Player(1, 10, 10, Direction.down, 1, True, "") self.player2 = Player(2, 10, 30, Direction.down, 3, True, "") self.player3 = Player(3, 30, 10, Direction.right, 2, True, "Name 3") players = [self.player1, self.player2, self.player3] cells = [[Cell() for _ in range(40)] for _ in range(40)] cells[self.player1.y][self.player1.x] = Cell([self.player1]) cells[self.player2.y][self.player2.x] = Cell([self.player2]) cells[self.player3.y][self.player3.x] = Cell([self.player3]) time = datetime(2020, 10, 1, 12, 5, 13, 0, timezone.utc) self.game = Game(40, 40, cells, players, 2, True, time) self.sut = GameService(self.game)
def __try_combination(game: Game, player_ids_to_watch: List[int], combination: Tuple[Action], turn_counter: int): modified_game = game.copy() game_service = GameService(modified_game) game_service.turn.turn_ctr = turn_counter players = modified_game.get_players_by_ids(player_ids_to_watch) for j in range(len(combination)): action = combination[j] player = players[j] SearchTreeRoot.__perform_simulation(game_service, action, player) game_service.check_and_set_died_players() return SearchTreeRoot(modified_game.copy())
def find_actions_by_best_path_connection(self, actions: List[Action], game: Game) -> Optional[ List[Tuple[Action, int]]]: """ Calculates for the passed actions how many paths are still accessible after the execution of the action. For this purpose, points are randomly generated on the playing field and an algorithm for finding paths is used to check whether the point can be reached. Args: actions: List of actions to check. game: The game that contains the current state of the game. Returns: List of actions with the accessible paths. """ if actions is None or len(actions) == 0: return None # shuffle the actions, so that different actions are chosen if they have the same quality and the AI is not so # easily predictable. shuffle(actions) actions_with_possible_paths: List[Tuple[Action, int]] = [] free_cells_for_pathfinding = self.get_random_free_cells_from_playground(game) path_finder = BestFirst(diagonal_movement=DiagonalMovement.never) for action in actions: game_copy = game.copy() game_service = GameService(game_copy) try: player = game_service.game.get_player_by_id(self.player.id) game_service.visited_cells_by_player[player.id] = game_service.get_and_visit_cells(player, action) except InvalidPlayerMoveException: continue matrix = game_copy.translate_cell_matrix_to_pathfinding_matrix() current_possible_paths = 0 length_free_cells = len(free_cells_for_pathfinding) for i in range(length_free_cells): grid = Grid(matrix=matrix) start = grid.node(player.x, player.y) end = grid.node(free_cells_for_pathfinding[i][0], free_cells_for_pathfinding[i][1]) path, _ = path_finder.find_path(start, end, grid) if len(path) > 0: # a path exists current_possible_paths += 1 actions_with_possible_paths.append((action, current_possible_paths)) # Action with most accessible paths at index 0 actions_with_possible_paths.sort(key=operator.itemgetter(1), reverse=True) return actions_with_possible_paths
def calc_action_with_max_distance_to_visited_cells( self, game_service: GameService, actions: List[Action]) -> List[Action]: """Calculates a list of actions that have the property to have as many free cells as possible in front of them while running straight after the action has been executed. Args: game_service: The game service used for simulation of actions. actions: The actions to be checked Returns: List of best actions with the property having as many free cells as possible in front of the player. """ max_straight_distance = 0 best_actions: Dict[Action, int] = {} for action in actions: gs_copy = copy.deepcopy(game_service) try: player = gs_copy.game.get_player_by_id(self.player.id) gs_copy.visited_cells_by_player[ player.id] = gs_copy.get_and_visit_cells(player, action) straight_distance = 0 horizontal_multiplier, vertical_multiplier = GameService.get_horizontal_and_vertical_multiplier( player) for i in range(max(gs_copy.game.height, gs_copy.game.width)): x = player.x + (i + 1) * horizontal_multiplier y = player.y + (i + 1) * vertical_multiplier if x in range(gs_copy.game.width) and y in range( gs_copy.game.height) and ( gs_copy.game.cells[y][x].players is None or len(gs_copy.game.cells[y][x].players) == 0): straight_distance += 1 else: break if len(best_actions ) == 0 or straight_distance > max_straight_distance: max_straight_distance = straight_distance best_actions[action] = straight_distance updated_best_actions: Dict[Action, int] = {} for (act, dist) in best_actions.items( ): # new max_straight_distance. Remove worth options if dist >= max_straight_distance - self.__max_worse_distance: updated_best_actions[act] = dist best_actions = updated_best_actions elif straight_distance >= max_straight_distance - self.__max_worse_distance: # still good option best_actions[action] = straight_distance except InvalidPlayerMoveException as ex: logging.warning(ex) continue return list(best_actions.keys())
def play(self): """See base class.""" self._create_game() game_service = GameService(self._game, ignore_deadline=False) self._view.update(self._game) while self._game.running: self._game_round += 1 time_to_react = randint(4, 16) self.__reset_game_deadline(time_to_react) # Read input from user if there is a human player player_action = None if self.__you is not None and self.__you.active: player_action = self._view.read_next_action() if datetime.now(time_zone) > self._game.deadline: player_action = Action.get_default() self.__reset_game_deadline(time_to_react) for ai in self._ais: if ai is not None and ai.player.active: action = self.__choose_ai_action(ai, time_to_react) game_service.do_action(ai.player, action) self.__reset_game_deadline(time_to_react) # Perform action of human player after AIs finished their calculations # Otherwise the AIs would already know the players move if self.__you is not None and player_action is not None: game_service.do_action(self.__you, player_action) self._view.update(self._game)
def create_next_action(self, game: Game, return_value: Value): """See base class.""" self._turn_ctr += 1 surviving_actions = self.create_all_next_surviving_actions(game) if surviving_actions is not None and len(surviving_actions) > 0: return_value.value = choice(surviving_actions).get_index() return_value.value = self.find_actions_by_best_path_connection( surviving_actions, game)[0][0].get_index() else: surviving_pathfinding_actions = self.find_actions_by_best_path_connection( self.find_surviving_actions(GameService(game), 1), game) return_value.value = surviving_pathfinding_actions[0][0].get_index() \ if surviving_pathfinding_actions is not None and len(surviving_pathfinding_actions) > 0 \ else Action.get_default().get_index()
def create_next_actions_ranked(self, game: Game) -> Optional[List[Tuple[Action, int]]]: """Calculates all actions with the number of reachable paths, with which the AI won't lose in the next turn. Args: game: The game object in which the AI is located and which contains the current status of the game. Returns: A list with actions and the corresponding number of accessible paths. """ game_service = GameService(game) game_service.turn.turn_ctr = self._turn_ctr surviving_actions = self.find_surviving_actions_with_best_depth(game_service) return self.find_actions_by_best_path_connection(surviving_actions, game)
def test_ai_should_choose_empty_list_with_depth_three_and_no_surviving_action(self): player1 = Player(1, 1, 2, Direction.up, 1, True, "") player2 = Player(2, 1, 1, Direction.down, 3, True, "") players = [player1, player2] cells = [[Cell(), Cell(), Cell(), Cell(), Cell()], [Cell([player2]), Cell([player2]), Cell([player2]), Cell(), Cell()], [Cell(), Cell([player1]), Cell(), Cell([player2]), Cell()], [Cell([player2]), Cell([player2]), Cell(), Cell([player2]), Cell()], [Cell(), Cell(), Cell([player2]), Cell(), Cell()]] time = datetime(2020, 10, 1, 12, 5, 13, 0, timezone.utc) game = Game(5, 5, cells, players, 2, True, time) game_service = GameService(game) sut = NotKillingItselfAI(player1, [], 3, 0, 3) actions: List[Action] = sut.find_surviving_actions(game_service, 3) self.assertTrue(len(actions) == 0)
def test_ai_should_choose_best_list_of_actions_by_depth(self): player1 = Player(1, 1, 2, Direction.up, 1, True, "") player2 = Player(2, 1, 1, Direction.down, 3, True, "") players = [player1, player2] cells = [[Cell(), Cell(), Cell(), Cell(), Cell()], [Cell([player2]), Cell([player2]), Cell([player2]), Cell(), Cell()], [Cell(), Cell([player1]), Cell(), Cell([player2]), Cell()], [Cell([player2]), Cell(), Cell(), Cell([player2]), Cell()], [Cell(), Cell(), Cell([player2]), Cell(), Cell()]] time = datetime(2020, 10, 1, 12, 5, 13, 0, timezone.utc) game = Game(5, 5, cells, players, 2, True, time) game_service = GameService(game) sut = NotKillingItselfAI(player1, [], 3, 0, 5) actions: List[Action] = sut.find_surviving_actions_with_best_depth(game_service) self.assertTrue(Action.turn_right in actions) self.assertTrue(len(actions) == 1)
def test_ai_should_not_choose_speed_up_if_max_speed_is_allready_reached(self): MAX_SPEED = 3 player1 = Player(1, 0, 4, Direction.up, MAX_SPEED, True, "") player2 = Player(2, 0, 1, Direction.down, 3, True, "") players = [player1, player2] cells = [[Cell(), Cell(), Cell(), Cell(), Cell()], [Cell([player2]), Cell(), Cell(), Cell(), Cell()], [Cell(), Cell(), Cell(), Cell(), Cell()], [Cell(), Cell(), Cell(), Cell(), Cell()], [Cell([player1]), Cell(), Cell(), Cell(), Cell()]] time = datetime(2020, 10, 1, 12, 5, 13, 0, timezone.utc) game = Game(5, 5, cells, players, 2, True, time) game_service = GameService(game) sut = NotKillingItselfAI(player1, [], MAX_SPEED, 0, 3) actions: List[Action] = sut.find_surviving_actions(game_service, 1) self.assertTrue(Action.slow_down in actions) self.assertTrue(Action.turn_right in actions) self.assertTrue(len(actions) == 2)
def test_ai_should_choose_the_correct_list_of_actions_non_killing_itself(self): player1 = Player(1, 0, 1, Direction.up, 1, True, "") player2 = Player(2, 4, 4, Direction.down, 3, True, "") players = [player1, player2] cells = [[Cell(), Cell(), Cell(), Cell(), Cell()], [Cell([player1]), Cell(), Cell(), Cell(), Cell()], [Cell(), Cell(), Cell(), Cell(), Cell()], [Cell(), Cell(), Cell(), Cell(), Cell()], [Cell(), Cell(), Cell(), Cell(), Cell([player2])]] time = datetime(2020, 10, 1, 12, 5, 13, 0, timezone.utc) game = Game(5, 5, cells, players, 2, True, time) game_service = GameService(game) sut = NotKillingItselfAI(player1, [], 3, 0, 3) actions: List[Action] = sut.find_surviving_actions(game_service, 3) self.assertTrue(Action.change_nothing in actions) self.assertTrue(Action.turn_right in actions) self.assertTrue(len(actions) == 2)
def test_ai_should_calc_action_with_max_distance(self): player1 = Player(1, 0, 4, Direction.up, 1, True, "") player2 = Player(2, 0, 1, Direction.down, 3, True, "") players = [player1, player2] cells = [[Cell(), Cell(), Cell(), Cell(), Cell()], [Cell([player2]), Cell(), Cell(), Cell(), Cell()], [Cell(), Cell(), Cell(), Cell(), Cell()], [Cell(), Cell(), Cell(), Cell(), Cell()], [Cell([player1]), Cell(), Cell(), Cell(), Cell()]] time = datetime(2020, 10, 1, 12, 5, 13, 0, timezone.utc) game = Game(5, 5, cells, players, 2, True, time) game_service = GameService(game) sut = NotKillingItselfAI(player1, [], 3, 0, 3) actions: List[Action] = sut.calc_action_with_max_distance_to_visited_cells(game_service, [Action.speed_up, Action.change_nothing, Action.turn_right]) self.assertTrue(Action.turn_right in actions) self.assertTrue(len(actions) == 1)
def create_next_action(self, game: Game, return_value: Value): """See base class.""" self._turn_ctr += 1 game_service = GameService(game) game_service.turn.turn_ctr = self._turn_ctr surviving_actions = self.find_surviving_actions_with_best_depth( game_service) if AIOptions.max_distance in self.__options: max_distance_actions = self.calc_action_with_max_distance_to_visited_cells( game_service, surviving_actions) action = choice( max_distance_actions ) if max_distance_actions is not None and len( max_distance_actions) > 0 else Action.change_nothing else: action = choice( surviving_actions) if surviving_actions is not None and len( surviving_actions) > 0 else Action.change_nothing return_value.value = action.get_index()
class GameTest(unittest.TestCase): def setUp(self): self.player1 = Player(1, 10, 10, Direction.down, 1, True, "") self.player2 = Player(2, 10, 30, Direction.down, 3, True, "") self.player3 = Player(3, 30, 10, Direction.right, 2, True, "Name 3") players = [self.player1, self.player2, self.player3] cells = [[Cell() for _ in range(40)] for _ in range(40)] cells[self.player1.y][self.player1.x] = Cell([self.player1]) cells[self.player2.y][self.player2.x] = Cell([self.player2]) cells[self.player3.y][self.player3.x] = Cell([self.player3]) time = datetime(2020, 10, 1, 12, 5, 13, 0, timezone.utc) self.game = Game(40, 40, cells, players, 2, True, time) self.sut = GameService(self.game) def test_game_should_end_when_less_than_two_players_are_left(self): self.player1.active = False self.player2.active = False self.assertEqual(self.sut.is_game_running(), False) def test_game_should_not_end_when_more_than_one_players_are_left(self): self.player1.active = False self.assertEqual(self.sut.is_game_running(), True) def test_player_should_loose_if_he_did_more_than_one_action_in_one_round(self): self.sut.do_action(self.player1, Action.speed_up) self.sut.do_action(self.player1, Action.speed_up) self.assertEqual(self.player1.active, False) def test_player_should_not_loose_if_he_did_exactly_one_action_in_one_round(self): self.sut.do_action(self.player1, Action.speed_up) self.assertEqual(self.player1.active, True) def test_visited_cells_should_be_calculated_correctly_turn_1_to_5(self): self.player1.direction = Direction.down self.player1.speed = 1 player1_x = self.player1.x player1_y = self.player1.y self.game.cells[self.player1.y][self.player1.x] = Cell([self.player1]) self.player2.direction = Direction.up self.player2.speed = 3 player2_x = self.player2.x player2_y = self.player2.y self.game.cells[self.player2.y][self.player2.x] = Cell([self.player2]) self.player3.direction = Direction.down self.player3.speed = 5 player3_x = self.player3.x player3_y = self.player3.y self.game.cells[self.player3.y][self.player3.x] = Cell([self.player3]) visited_cells_p1_expected = [(player1_x, player1_y + 1), (player1_x, player1_y + 2)] visited_cells_p2_expected = [(player2_x, player2_y - 1), (player2_x, player2_y - 2)] visited_cells_p3_expected = [(player3_x + 1, player3_y), (player3_x + 2, player3_y), (player3_x + 3, player3_y), (player3_x + 4, player3_y), (player3_x + 5, player3_y)] visited_cells_p1 = self.sut.get_and_visit_cells(self.player1, Action.speed_up) visited_cells_p2 = self.sut.get_and_visit_cells(self.player2, Action.slow_down) visited_cells_p3 = self.sut.get_and_visit_cells(self.player3, Action.turn_left) self.assertEqual(visited_cells_p1_expected, visited_cells_p1) self.assertEqual(visited_cells_p2_expected, visited_cells_p2) self.assertEqual(visited_cells_p3_expected, visited_cells_p3) self.assertTrue(self.player1 in self.game.cells[player1_y + 1][player1_x].players) self.assertTrue(self.player1 in self.game.cells[player1_y + 2][player1_x].players) self.assertTrue(self.player2 in self.game.cells[player2_y - 2][player2_x].players) self.assertTrue(self.player2 in self.game.cells[player2_y - 2][player2_x].players) self.assertTrue(self.player3 in self.game.cells[player3_y][player3_x + 1].players) self.assertTrue(self.player3 in self.game.cells[player3_y][player3_x + 2].players) self.assertTrue(self.player3 in self.game.cells[player3_y][player3_x + 3].players) self.assertTrue(self.player3 in self.game.cells[player3_y][player3_x + 4].players) self.assertTrue(self.player3 in self.game.cells[player3_y][player3_x + 5].players) def test_visited_cells_should_be_calculated_correctly_turn_6(self): self.sut.turn.turn_ctr = 12 # 6, 12, 18 should all work self.player1.direction = Direction.down self.player1.speed = 1 player1_x = self.player1.x player1_y = self.player1.y self.game.cells[10][10] = Cell([self.player1]) self.player2.direction = Direction.up self.player2.speed = 5 player2_x = self.player2.x player2_y = self.player2.y self.game.cells[10][30] = Cell([self.player2]) visited_cells_p1_expected = [(player1_x, player1_y + 1), (player1_x, player1_y + 2)] visited_cells_p2_expected = [(player2_x, player2_y - 1), (player2_x, player2_y - 6)] visited_cells_p1 = self.sut.get_and_visit_cells(self.player1, Action.speed_up) visited_cells_p2 = self.sut.get_and_visit_cells(self.player2, Action.speed_up) self.assertEqual(visited_cells_p1_expected, visited_cells_p1) self.assertEqual(visited_cells_p2_expected, visited_cells_p2) self.assertTrue(self.player1 in self.game.cells[player1_y + 1][player1_x].players) self.assertTrue(self.player1 in self.game.cells[player1_y + 2][player1_x].players) self.assertTrue(self.player2 in self.game.cells[player2_y - 1][player2_x].players) self.assertTrue(self.player2 in self.game.cells[player2_y - 6][player2_x].players) def test_game_cells_should_be_correct_after_collision(self): self.player1.direction = Direction.left self.player1.speed = 4 self.player1.x = 2 self.player1.y = 0 self.game.cells[self.player1.y][self.player1.x] = Cell([self.player1]) self.game.cells[self.player1.y][1] = Cell([self.player1]) self.sut.get_and_visit_cells(self.player1, Action.speed_up) self.assertEqual(self.player1.x, 0) self.assertEqual(self.player1.y, 0) self.assertTrue(self.player1 in self.game.cells[0][0].players) self.assertTrue(self.player1 in self.game.cells[0][1].players) self.assertTrue(self.player1 in self.game.cells[0][2].players) def test_visited_cells_should_be_correct_after_collision(self): self.player1.direction = Direction.left self.player1.speed = 4 self.player1.x = 2 self.player1.y = 0 self.game.cells[self.player1.y][self.player1.x] = Cell([self.player1]) self.game.cells[self.player1.y][1] = Cell([self.player1]) visited_cells = self.sut.get_and_visit_cells(self.player1, Action.speed_up) self.assertEqual(self.player1.x, 0) self.assertEqual(self.player1.y, 0) self.assertTrue((0, 0) in visited_cells) self.assertTrue((1, 0) in visited_cells) def test_playerX_playerY_should_be_correct_after_collision(self): self.player1.direction = Direction.left self.player1.speed = 2 self.player1.x = 1 self.player1.y = 0 self.game.cells[self.player1.y][self.player1.x] = Cell([self.player1]) self.sut.get_and_visit_cells(self.player1, Action.speed_up) self.assertEqual(self.player1.x, 0) self.assertEqual(self.player1.y, 0) def test_playerX_playerY_should_be_correct_without_collision(self): self.player1.direction = Direction.left self.player1.speed = 2 self.player1.x = 10 self.player1.y = 0 self.game.cells[self.player1.y][self.player1.x] = Cell([self.player1]) self.sut.get_and_visit_cells(self.player1, Action.change_nothing) self.assertEqual(self.player1.x, 8) self.assertEqual(self.player1.y, 0) def test_correct_multiplier_should_be_returned_direction_up(self): self.player1.direction = Direction.up self.assertEqual((0, -1), self.sut.get_horizontal_and_vertical_multiplier(self.player1)) def test_correct_multiplier_should_be_returned_direction_down(self): self.player1.direction = Direction.down self.assertEqual((0, 1), self.sut.get_horizontal_and_vertical_multiplier(self.player1)) def test_correct_multiplier_should_be_returned_direction_left(self): self.player1.direction = Direction.left self.assertEqual((-1, 0), self.sut.get_horizontal_and_vertical_multiplier(self.player1)) def test_correct_multiplier_should_be_returned_direction_right(self): self.player1.direction = Direction.right self.assertEqual((1, 0), self.sut.get_horizontal_and_vertical_multiplier(self.player1))