def construct_simulation_runner(self, avatars, locations):
     self.avatar_manager = DummyAvatarManager(avatars)
     self.avatar_manager.avatars_by_id = dict(enumerate(avatars))
     self.game_state = MockGameState(InfiniteMap(), self.avatar_manager)
     self.simulation_runner = ConcurrentSimulationRunner(
         game_state=self.game_state, communicator=MockCommunicator())
     for index, location in enumerate(locations):
         self.simulation_runner.add_avatar(index, location)
Esempio n. 2
0
    def __init__(
        self,
        game_state_generator,
        communicator: DjangoCommunicator,
        port,
        worker_manager_class=WorkerManager,
    ):
        super(GameRunner, self).__init__()

        self.worker_manager = worker_manager_class(port=port)
        self.game_state = game_state_generator(AvatarManager())
        self.communicator = communicator
        self.simulation_runner = ConcurrentSimulationRunner(
            communicator=self.communicator, game_state=self.game_state)
        self._end_turn_callback = lambda: None
Esempio n. 3
0
    def __init__(
        self,
        game_state_generator,
        communicator: DjangoCommunicator,
        port,
        turn_collector: "TurnCollector",
    ):
        super(GameRunner, self).__init__()

        self.game_state: "GameState" = game_state_generator(AvatarManager())
        self.communicator = communicator
        self.simulation_runner = ConcurrentSimulationRunner(
            communicator=self.communicator, game_state=self.game_state)
        self.turn_collector = turn_collector
        self.turn_collector.new_turn(self.game_state.turn_count)
        self._end_turn_callback = lambda: None
Esempio n. 4
0
    def __init__(self, worker_manager_class, game_state_generator,
                 django_api_url, port):
        super(GameRunner, self).__init__()

        self.worker_manager = worker_manager_class(port=port)
        self.game_state = game_state_generator(AvatarManager())
        self.communicator = DjangoCommunicator(django_api_url=django_api_url,
                                               completion_url=django_api_url +
                                               'complete/')
        self.simulation_runner = ConcurrentSimulationRunner(
            communicator=self.communicator, game_state=self.game_state)
        self._end_turn_callback = lambda: None
class TestSimulationRunner:
    """
        Key:
            > : Avatar moving eastward
            < : Avatar moving westward
            x : Avatar waiting / blocked
            o : Avatar successfully moved
            ! : Dead avatar (that should be waiting)
    """
    def _generate_grid(self, columns=2, rows=2):
        alphabet = iter(ascii_uppercase)
        grid = {
            Location(x, y): MockCell(Location(x, y), name=next(alphabet))
            for x in range(columns) for y in range(rows)
        }
        return grid

    def assertGridSize(self, world_map, expected_columns, expected_rows=None):
        if expected_rows is None:
            expected_rows = expected_columns
        assert world_map.num_rows == expected_rows
        assert world_map.num_cols == expected_columns
        assert world_map.num_cells == expected_rows * expected_columns
        assert len(list(
            world_map.all_cells())) == expected_rows * expected_columns

    def construct_default_avatar_appearance(self):
        return AvatarAppearance("#000", "#ddd", "#777", "#fff")

    def construct_simulation_runner(self, avatars, locations):
        self.avatar_manager = DummyAvatarManager(avatars)
        self.avatar_manager.avatars_by_id = dict(enumerate(avatars))
        self.game_state = MockGameState(InfiniteMap(), self.avatar_manager)
        self.simulation_runner = ConcurrentSimulationRunner(
            game_state=self.game_state, communicator=MockCommunicator())
        for index, location in enumerate(locations):
            self.simulation_runner.add_avatar(index, location)

    def assert_at(self, avatar, location):
        assert avatar.location == location
        cell = self.game_state.world_map.get_cell(location)
        assert cell.avatar == avatar

    def get_avatar(self, player_id):
        return self.avatar_manager.get_avatar(player_id)

    async def run_turn(self):
        await self.simulation_runner.run_turn(self.avatar_manager.avatars_by_id
                                              )

    def test_add_avatar(self):
        self.construct_simulation_runner([], [])

        self.simulation_runner.add_avatar(7)
        assert 7 in self.game_state.avatar_manager.avatars_by_id

    def test_remove_avatar(self):
        self.construct_simulation_runner(
            [DummyAvatar, DummyAvatar],
            [Location(0, 0), Location(1, 1)])

        avatar = self.get_avatar(1)
        avatar.marked = True

        self.simulation_runner.remove_avatar(0)

        assert not 0 in self.avatar_manager.avatars_by_id
        assert self.game_state.world_map.get_cell(Location(0,
                                                           0)).avatar == None

        assert self.avatar_manager.avatars_by_id[1].marked
        assert self.game_state.world_map.get_cell(Location(1, 1)).avatar.marked

    def test_remove_non_existent_avatar(self):
        self.construct_simulation_runner([], [])
        self.simulation_runner.remove_avatar(10)

    def test_updates_map_with_correct_num_avatars(self):
        self.construct_simulation_runner([], [])

        self.avatar_manager.add_avatar(1)
        self.simulation_runner.update_environment()
        assert len(self.simulation_runner.game_state.avatar_manager.
                   avatars_by_id) == 1

        self.avatar_manager.add_avatar(2)
        self.avatar_manager.add_avatar(3)
        self.simulation_runner.update_environment()
        assert len(self.simulation_runner.game_state.avatar_manager.
                   avatars_by_id) == 3

    def test_grid_expand(self):
        self.construct_simulation_runner([], [])
        settings = SETTINGS.copy()
        settings["TARGET_NUM_CELLS_PER_AVATAR"] = 5
        self.simulation_runner.game_state.world_map = WorldMap(
            self._generate_grid(), settings)
        self.simulation_runner.update(1, self.simulation_runner.game_state)
        print(self.simulation_runner.game_state.world_map)
        assert self.simulation_runner.game_state.world_map.is_on_map(
            Location(-1, -1))
        assert self.simulation_runner.game_state.world_map.is_on_map(
            Location(-1, 2))
        assert self.simulation_runner.game_state.world_map.is_on_map(
            Location(2, 2))
        assert self.simulation_runner.game_state.world_map.is_on_map(
            Location(2, -1))
        self.assertGridSize(self.simulation_runner.game_state.world_map, 4)

        self.simulation_runner.update(4, self.simulation_runner.game_state)
        self.assertGridSize(self.simulation_runner.game_state.world_map, 6)
        assert self.simulation_runner.game_state.world_map.is_on_map(
            Location(0, 3))
        assert self.simulation_runner.game_state.world_map.is_on_map(
            Location(3, 0))
        assert self.simulation_runner.game_state.world_map.is_on_map(
            Location(-2, 0))
        assert self.simulation_runner.game_state.world_map.is_on_map(
            Location(0, -2))

    def test_grid_doesnt_expand(self):
        self.construct_simulation_runner([], [])
        settings = SETTINGS.copy()
        settings["TARGET_NUM_CELLS_PER_AVATAR"] = 4
        self.simulation_runner.game_state.world_map = WorldMap(
            self._generate_grid(), settings)
        self.simulation_runner.update(1, self.simulation_runner.game_state)
        self.assertGridSize(self.simulation_runner.game_state.world_map, 2)

    def test_score_despawn_chance(self):
        self.construct_simulation_runner([], [])
        settings = SETTINGS.copy()
        settings["TARGET_NUM_SCORE_LOCATIONS_PER_AVATAR"] = 0
        grid = self._generate_grid()
        grid[Location(0, 1)].interactable = ScoreLocation(grid[Location(0, 1)])
        self.simulation_runner.game_state.world_map = WorldMap(grid, settings)
        self.simulation_runner.update(1, self.simulation_runner.game_state)
        assert (grid[Location(0, 1)]
                in self.simulation_runner.game_state.world_map.score_cells())
        assert len(
            list(self.simulation_runner.game_state.world_map.score_cells())
        ) == 1

    def test_scores_applied(self):
        self.construct_simulation_runner([], [])
        grid = self._generate_grid()
        avatar = DummyAvatar()
        grid[Location(1, 1)].interactable = ScoreLocation(grid[Location(1, 1)])
        grid[Location(1, 1)].avatar = avatar
        self.simulation_runner.game_state.world_map = WorldMap(grid, SETTINGS)
        self.simulation_runner.update(1, self.simulation_runner.game_state)
        assert avatar.score == 1

    def test_scores_not_added_when_at_target(self):
        self.construct_simulation_runner([], [])
        settings = SETTINGS.copy()
        settings["TARGET_NUM_SCORE_LOCATIONS_PER_AVATAR"] = 1
        grid = self._generate_grid()
        grid[Location(0, 1)].interactable = ScoreLocation(grid[Location(0, 1)])
        self.simulation_runner.game_state.world_map = WorldMap(grid, settings)
        self.simulation_runner.update(1, self.simulation_runner.game_state)
        assert len(
            list(self.simulation_runner.game_state.world_map.score_cells())
        ) == 1
        assert (grid[Location(0, 1)]
                in self.simulation_runner.game_state.world_map.score_cells())

    def test_not_enough_score_space(self):
        self.construct_simulation_runner([], [])
        settings = SETTINGS.copy()
        settings["TARGET_NUM_SCORE_LOCATIONS_PER_AVATAR"] = 1
        grid = self._generate_grid(1, 1)
        grid[Location(0, 0)].avatar = "avatar"
        self.simulation_runner.game_state.world_map = WorldMap(grid, settings)
        self.simulation_runner.update(1, self.simulation_runner.game_state)
        assert len(
            list(self.simulation_runner.game_state.world_map.score_cells())
        ) == 0

    def test_pickups_added(self):
        self.construct_simulation_runner([], [])
        settings = SETTINGS.copy()
        settings["TARGET_NUM_PICKUPS_PER_AVATAR"] = 1
        settings["PICKUP_SPAWN_CHANCE"] = 1
        self.simulation_runner.game_state.world_map = WorldMap(
            self._generate_grid(), settings)
        self.simulation_runner.update(1, self.simulation_runner.game_state)
        assert (len(
            list(self.simulation_runner.game_state.world_map.
                 interactable_cells())) == 1)

        self.simulation_runner.update(2, self.simulation_runner.game_state)
        assert (len(
            list(self.simulation_runner.game_state.world_map.
                 interactable_cells())) == 2)

    def test_pickups_applied(self):
        self.construct_simulation_runner([], [])
        grid = self._generate_grid()
        avatar = DummyAvatar()
        pickup = MockPickup(target=avatar)
        grid[Location(1, 1)].interactable = pickup
        grid[Location(1, 1)].avatar = avatar
        self.simulation_runner.game_state.world_map = WorldMap(grid, SETTINGS)
        self.simulation_runner.update(1, self.simulation_runner.game_state)
        assert pickup.applied_to == avatar

    @patch("simulation.interactables.pickups.Artefact", spec=Artefact)
    def test_pickups_not_added_when_at_target(self, mockPickup):
        self.construct_simulation_runner([], [])
        settings = SETTINGS.copy()
        settings["TARGET_NUM_PICKUPS_PER_AVATAR"] = 1
        grid = self._generate_grid()
        grid[Location(0, 1)].interactable = mockPickup()
        self.simulation_runner.game_state.world_map = WorldMap(grid, settings)
        self.simulation_runner.update(1, self.simulation_runner.game_state)
        assert (len(
            list(self.simulation_runner.game_state.world_map.
                 interactable_cells())) == 1)
        assert (grid[Location(0, 1)] in self.simulation_runner.game_state.
                world_map.interactable_cells())

    def test_not_enough_pickup_space(self):
        self.construct_simulation_runner([], [])
        settings = SETTINGS.copy()
        settings["TARGET_NUM_PICKUPS_PER_AVATAR"] = 1
        grid = self._generate_grid(1, 1)
        grid[Location(0, 0)].interactable = ScoreLocation(grid[Location(0, 0)])
        self.simulation_runner.game_state.world_map = WorldMap(grid, settings)
        self.simulation_runner.update(1, self.simulation_runner.game_state)
        assert (len(
            list(self.simulation_runner.game_state.world_map.pickup_cells()))
                == 0)

    async def test_run_turn(self, loop):
        """
        Given:  > _
        (1)
        Expect: _ o
        """
        self.construct_simulation_runner([MoveEastDummy], [ORIGIN])
        avatar = self.get_avatar(0)

        self.assert_at(avatar, ORIGIN)
        await self.run_turn()
        self.assert_at(avatar, RIGHT_OF_ORIGIN)

    async def test_run_several_turns(self, loop):
        """
        Given:  > _ _ _ _ _
        (5)
        Expect: _ _ _ _ _ o
        """
        self.construct_simulation_runner([MoveEastDummy], [ORIGIN])
        avatar = self.get_avatar(0)

        assert avatar.location == ORIGIN
        [await self.run_turn() for _ in range(5)]
        assert avatar.location == FIVE_RIGHT_OF_ORIGIN

    async def test_run_several_turns_and_avatars(self, loop):
        """
        Given:  > _ _ _ _ _
                > _ _ _ _ _
        (5)
        Expect: _ _ _ _ _ o
                _ _ _ _ _ o
        """
        self.construct_simulation_runner([MoveEastDummy, MoveEastDummy],
                                         [ORIGIN, ABOVE_ORIGIN])
        avatar0 = self.get_avatar(0)
        avatar1 = self.get_avatar(1)

        self.assert_at(avatar0, ORIGIN)
        self.assert_at(avatar1, ABOVE_ORIGIN)
        [await self.run_turn() for _ in range(5)]
        self.assert_at(avatar0, FIVE_RIGHT_OF_ORIGIN)
        self.assert_at(avatar1, FIVE_RIGHT_OF_ORIGIN_AND_ONE_ABOVE)

    async def test_move_chain_succeeds(self, loop):
        """
        Given:  > > > > > _

        Expect: _ o o o o o
        """
        self.construct_simulation_runner([MoveEastDummy for _ in range(5)],
                                         [Location(x, 0) for x in range(5)])
        avatars = [self.get_avatar(i) for i in range(5)]

        [self.assert_at(avatars[x], Location(x, 0)) for x in range(5)]
        await self.run_turn()
        [self.assert_at(avatars[x], Location(x + 1, 0)) for x in range(5)]

    async def test_move_chain_fails_occupied(self, loop):
        """
        Given:  > > x _

        Expect: x x x _
        """
        self.construct_simulation_runner(
            [MoveEastDummy, MoveEastDummy, WaitDummy],
            [Location(x, 0) for x in range(3)],
        )
        avatars = [self.get_avatar(i) for i in range(3)]

        [self.assert_at(avatars[x], Location(x, 0)) for x in range(3)]
        await self.run_turn()
        [self.assert_at(avatars[x], Location(x, 0)) for x in range(3)]

    async def test_move_chain_fails_occupied_by_dead_avatar(self, loop):
        """
        Given: > > ! _

        Expect: x x ! _
        """

        self.construct_simulation_runner(
            [MoveEastDummy, MoveEastDummy, DeadDummy],
            [Location(x, 0) for x in range(3)],
        )
        avatars = [self.get_avatar(i) for i in range(3)]

        [self.assert_at(avatars[x], Location(x, 0)) for x in range(3)]
        await self.run_turn()
        [self.assert_at(avatars[x], Location(x, 0)) for x in range(3)]

    async def test_move_fails_collision(self, loop):
        """
        Given: > _ <
        Expect: x _ x
        """

        self.construct_simulation_runner(
            [MoveEastDummy, MoveWestDummy],
            [Location(0, 0), Location(2, 0)])
        avatars = [self.get_avatar(i) for i in range(2)]

        self.assert_at(avatars[0], Location(0, 0))
        self.assert_at(avatars[1], Location(2, 0))

        await self.run_turn()

        self.assert_at(avatars[0], Location(0, 0))
        self.assert_at(avatars[1], Location(2, 0))

    async def test_move_chain_fails_collision(self, loop):
        """
        Given:  > > > _ <
        (1)
        Expect: x x x _ x
        """
        locations = [
            Location(0, 0),
            Location(1, 0),
            Location(2, 0),
            Location(4, 0)
        ]
        self.construct_simulation_runner(
            [MoveEastDummy, MoveEastDummy, MoveEastDummy, MoveWestDummy],
            locations)
        avatars = [self.get_avatar(i) for i in range(4)]

        [self.assert_at(avatars[i], locations[i]) for i in range(4)]
        await self.run_turn()
        [self.assert_at(avatars[i], locations[i]) for i in range(4)]

    async def test_move_chain_fails_cycle(self, loop):
        """
        Given:  > v
                ^ <
        (1)
        Expect: x x
                x x
        """
        locations = [
            Location(0, 1),
            Location(1, 1),
            Location(1, 0),
            Location(0, 0)
        ]
        self.construct_simulation_runner(
            [MoveEastDummy, MoveSouthDummy, MoveWestDummy, MoveNorthDummy],
            locations)
        avatars = [self.get_avatar(i) for i in range(4)]

        [self.assert_at(avatars[i], locations[i]) for i in range(4)]
        await self.run_turn()
        [self.assert_at(avatars[i], locations[i]) for i in range(4)]

    async def test_move_chain_fails_spiral(self, loop):
        """
        Given:  > > v
                  ^ <
        (1)
        Expect: x x x
                  x x
        """
        locations = [
            Location(0, 1),
            Location(1, 1),
            Location(2, 1),
            Location(2, 0),
            Location(1, 0),
        ]
        self.construct_simulation_runner(
            [
                MoveEastDummy,
                MoveEastDummy,
                MoveSouthDummy,
                MoveWestDummy,
                MoveNorthDummy,
            ],
            locations,
        )
        avatars = [self.get_avatar(i) for i in range(5)]

        [self.assert_at(avatars[i], locations[i]) for i in range(5)]
        await self.run_turn()
        [self.assert_at(avatars[i], locations[i]) for i in range(5)]
Esempio n. 6
0
class GameRunner:
    def __init__(
        self,
        game_state_generator,
        django_api_url,
        port,
        worker_manager_class=WorkerManager,
    ):
        super(GameRunner, self).__init__()

        self.worker_manager = worker_manager_class(port=port)
        self.game_state = game_state_generator(AvatarManager())
        self.communicator = DjangoCommunicator(
            django_api_url=django_api_url, completion_url=django_api_url + "complete/"
        )
        self.simulation_runner = ConcurrentSimulationRunner(
            communicator=self.communicator, game_state=self.game_state
        )
        self._end_turn_callback = lambda: None

    def set_end_turn_callback(self, callback_method):
        self._end_turn_callback = callback_method

    def get_users_to_add(self, game_metadata):
        def player_is_new(_player):
            return _player["id"] not in self.worker_manager.player_id_to_worker.keys()

        return [
            player["id"] for player in game_metadata["users"] if player_is_new(player)
        ]

    def get_users_to_delete(self, game_metadata):
        def player_in_worker_manager_but_not_metadata(pid):
            return pid not in [player["id"] for player in game_metadata["users"]]

        return [
            player_id
            for player_id in self.worker_manager.player_id_to_worker.keys()
            if player_in_worker_manager_but_not_metadata(player_id)
        ]

    def update_main_user(self, game_metadata):
        self.game_state.main_avatar_id = game_metadata["main_avatar"]

    async def update_workers(self):
        game_metadata = self.communicator.get_game_metadata()["main"]

        users_to_add = self.get_users_to_add(game_metadata)
        users_to_delete = self.get_users_to_delete(game_metadata)

        await self.worker_manager.add_workers(users_to_add)
        await self.worker_manager.delete_workers(users_to_delete)
        self.simulation_runner.add_avatars(users_to_add)
        self.simulation_runner.delete_avatars(users_to_delete)
        await self.worker_manager.update_worker_codes(game_metadata["users"])

        self.update_main_user(game_metadata)
        self.worker_manager.fetch_all_worker_data(
            self.game_state.get_serialized_game_states_for_workers()
        )

    async def update_simulation(self, player_id_to_serialized_actions):
        await self.simulation_runner.run_single_turn(player_id_to_serialized_actions)
        await self._end_turn_callback()

    async def update(self):
        with GAME_TURN_TIME():
            await self.update_workers()
            await self.update_simulation(
                self.worker_manager.get_player_id_to_serialized_actions()
            )
            self.worker_manager.clear_logs()
            self.game_state.turn_count += 1

    async def run(self):
        while True:
            await self.update()
Esempio n. 7
0
class GameRunner:
    def __init__(
        self,
        game_state_generator,
        communicator: DjangoCommunicator,
        port,
        turn_collector: "TurnCollector",
    ):
        super(GameRunner, self).__init__()

        self.game_state: "GameState" = game_state_generator(AvatarManager())
        self.communicator = communicator
        self.simulation_runner = ConcurrentSimulationRunner(
            communicator=self.communicator, game_state=self.game_state)
        self.turn_collector = turn_collector
        self.turn_collector.new_turn(self.game_state.turn_count)
        self._end_turn_callback = lambda: None

    def set_end_turn_callback(self, callback_method):
        self._end_turn_callback = callback_method

    def get_users_to_add(self, game_metadata):
        def player_is_new(_player):
            return (_player["id"] not in self.simulation_runner.game_state.
                    avatar_manager.avatars_by_id.keys())

        return [
            player["id"] for player in game_metadata["users"]
            if player_is_new(player)
        ]

    def get_users_to_delete(self, game_metadata):
        def player_in_avatar_manager_but_not_metadata(pid):
            return pid not in [
                player["id"] for player in game_metadata["users"]
            ]

        return [
            player_id for player_id in self.simulation_runner.game_state.
            avatar_manager.avatars_by_id.keys()
            if player_in_avatar_manager_but_not_metadata(player_id)
        ]

    async def update_avatars(self):
        try:
            game_metadata = await self.communicator.get_game_metadata()
            users_to_add = self.get_users_to_add(game_metadata)
            users_to_delete = self.get_users_to_delete(game_metadata)

            self.simulation_runner.add_avatars(users_to_add)
            self.simulation_runner.delete_avatars(users_to_delete)
        except GameMetadataFetchFailedError:
            LOGGER.error(
                "Game metadata fetch failed, not updating avatars this turn")
            pass

    async def update_simulation(self, player_id_to_serialized_actions):
        await self.simulation_runner.run_single_turn(
            player_id_to_serialized_actions)
        await self._end_turn_callback()
        self.game_state.turn_count += 1

    async def update(self):
        with GAME_TURN_TIME():
            await self.update_avatars()
            await self.update_simulation(self.turn_collector.collected_turns)
            self.game_state.avatar_manager.clear_all_avatar_logs()
            self.turn_collector.new_turn(self.game_state.turn_count)

    def _get_task_result_or_stop_loop(self, task):
        try:
            task.result()
        except Exception as e:
            LOGGER.exception(f"Unexpected error, stopping game loop: {e}")
            loop = asyncio.get_event_loop()
            loop.stop()

    async def run(self):
        while True:
            LOGGER.info(f"Starting turn {self.game_state.turn_count}")
            turn = asyncio.ensure_future(self.update())

            turn.add_done_callback(self._get_task_result_or_stop_loop)

            await asyncio.sleep(TURN_TIME)
            await turn
Esempio n. 8
0
class GameRunner:
    def __init__(
        self,
        game_state_generator,
        communicator: DjangoCommunicator,
        port,
        worker_manager_class=WorkerManager,
    ):
        super(GameRunner, self).__init__()

        self.worker_manager = worker_manager_class(port=port)
        self.game_state = game_state_generator(AvatarManager())
        self.communicator = communicator
        self.simulation_runner = ConcurrentSimulationRunner(
            communicator=self.communicator, game_state=self.game_state
        )
        self._end_turn_callback = lambda: None

    def set_end_turn_callback(self, callback_method):
        self._end_turn_callback = callback_method

    def get_users_to_add(self, game_metadata):
        def player_is_new(_player):
            return _player["id"] not in self.worker_manager.player_id_to_worker.keys()

        return [
            player["id"] for player in game_metadata["users"] if player_is_new(player)
        ]

    def get_users_to_delete(self, game_metadata):
        def player_in_worker_manager_but_not_metadata(pid):
            return pid not in [player["id"] for player in game_metadata["users"]]

        return [
            player_id
            for player_id in self.worker_manager.player_id_to_worker.keys()
            if player_in_worker_manager_but_not_metadata(player_id)
        ]

    def update_main_user(self, game_metadata):
        self.game_state.main_avatar_id = game_metadata["main_avatar"]

    async def update_workers(self):
        game_metadata = self.communicator.get_game_metadata()

        users_to_add = self.get_users_to_add(game_metadata)
        users_to_delete = self.get_users_to_delete(game_metadata)

        await self.worker_manager.add_workers(users_to_add)
        await self.worker_manager.delete_workers(users_to_delete)
        self.simulation_runner.add_avatars(users_to_add)
        self.simulation_runner.delete_avatars(users_to_delete)
        await self.worker_manager.update_worker_codes(game_metadata["users"])

        self.update_main_user(game_metadata)
        await self.worker_manager.fetch_all_worker_data(
            self.game_state.get_serialized_game_states_for_workers()
        )

    async def update_simulation(self, player_id_to_serialized_actions):
        await self.simulation_runner.run_single_turn(player_id_to_serialized_actions)
        await self._end_turn_callback()

    async def update(self):
        with GAME_TURN_TIME():
            await self.update_workers()
            await self.update_simulation(
                self.worker_manager.get_player_id_to_serialized_actions()
            )
            self.worker_manager.clear_logs()
            self.game_state.avatar_manager.clear_all_avatar_logs()
            self.game_state.turn_count += 1

    def _get_task_result_or_stop_loop(self, task):
        try:
            task.result()
        except Exception as e:
            LOGGER.exception(f"Unexpected error, stopping game loop: {e}")
            loop = asyncio.get_event_loop()
            loop.stop()

    async def run(self):
        while True:
            LOGGER.info(f"Starting turn {self.game_state.turn_count}")
            turn = asyncio.ensure_future(self.update())

            turn.add_done_callback(self._get_task_result_or_stop_loop)

            await asyncio.sleep(TURN_TIME)
            await turn
Esempio n. 9
0
class TestSimulationRunner(unittest.TestCase):
    """
        Key:
            > : Avatar moving eastward
            < : Avatar moving westward
            x : Avatar waiting / blocked
            o : Avatar successfully moved
            ! : Dead avatar (that should be waiting)
    """
    def construct_default_avatar_appearance(self):
        return AvatarAppearance("#000", "#ddd", "#777", "#fff")

    def construct_simulation_runner(self, avatars, locations):
        self.avatar_manager = DummyAvatarManager(avatars)
        self.avatar_manager.avatars_by_id = dict(enumerate(avatars))
        self.game_state = MockGameState(InfiniteMap(), self.avatar_manager)
        self.simulation_runner = ConcurrentSimulationRunner(
            game_state=self.game_state, communicator=MockCommunicator())
        for index, location in enumerate(locations):
            self.game_state.add_avatar(index, location)

    def assert_at(self, avatar, location):
        self.assertEqual(avatar.location, location)
        cell = self.game_state.world_map.get_cell(location)
        self.assertEqual(cell.avatar, avatar)

    def get_avatar(self, player_id):
        return self.avatar_manager.get_avatar(player_id)

    def run_turn(self):
        loop = asyncio.get_event_loop()
        loop.run_until_complete(
            self.simulation_runner.run_turn(self.avatar_manager.avatars_by_id))

    def test_run_turn(self):
        """
        Given:  > _
        (1)
        Expect: _ o
        """
        self.construct_simulation_runner([MoveEastDummy], [ORIGIN])
        avatar = self.get_avatar(0)

        self.assert_at(avatar, ORIGIN)
        self.run_turn()
        self.assert_at(avatar, RIGHT_OF_ORIGIN)

    def test_run_several_turns(self):
        """
        Given:  > _ _ _ _ _
        (5)
        Expect: _ _ _ _ _ o
        """
        self.construct_simulation_runner([MoveEastDummy], [ORIGIN])
        avatar = self.get_avatar(0)

        self.assertEqual(avatar.location, ORIGIN)
        [self.run_turn() for _ in range(5)]
        self.assertEqual(avatar.location, FIVE_RIGHT_OF_ORIGIN)

    def test_run_several_turns_and_avatars(self):
        """
        Given:  > _ _ _ _ _
                > _ _ _ _ _
        (5)
        Expect: _ _ _ _ _ o
                _ _ _ _ _ o
        """
        self.construct_simulation_runner([MoveEastDummy, MoveEastDummy],
                                         [ORIGIN, ABOVE_ORIGIN])
        avatar0 = self.get_avatar(0)
        avatar1 = self.get_avatar(1)

        self.assert_at(avatar0, ORIGIN)
        self.assert_at(avatar1, ABOVE_ORIGIN)
        [self.run_turn() for _ in range(5)]
        self.assert_at(avatar0, FIVE_RIGHT_OF_ORIGIN)
        self.assert_at(avatar1, FIVE_RIGHT_OF_ORIGIN_AND_ONE_ABOVE)

    def test_move_chain_succeeds(self):
        """
        Given:  > > > > > _

        Expect: _ o o o o o
        """
        self.construct_simulation_runner([MoveEastDummy for _ in range(5)],
                                         [Location(x, 0) for x in range(5)])
        avatars = [self.get_avatar(i) for i in range(5)]

        [self.assert_at(avatars[x], Location(x, 0)) for x in range(5)]
        self.run_turn()
        [self.assert_at(avatars[x], Location(x + 1, 0)) for x in range(5)]

    def test_move_chain_fails_occupied(self):
        """
        Given:  > > x _

        Expect: x x x _
        """
        self.construct_simulation_runner(
            [MoveEastDummy, MoveEastDummy, WaitDummy],
            [Location(x, 0) for x in range(3)])
        avatars = [self.get_avatar(i) for i in range(3)]

        [self.assert_at(avatars[x], Location(x, 0)) for x in range(3)]
        self.run_turn()
        [self.assert_at(avatars[x], Location(x, 0)) for x in range(3)]

    def test_move_chain_fails_occupied_by_dead_avatar(self):
        """
        Given: > > ! _

        Expect: x x ! _
        """

        self.construct_simulation_runner(
            [MoveEastDummy, MoveEastDummy, DeadDummy],
            [Location(x, 0) for x in range(3)])
        avatars = [self.get_avatar(i) for i in range(3)]

        [self.assert_at(avatars[x], Location(x, 0)) for x in range(3)]
        self.run_turn()
        [self.assert_at(avatars[x], Location(x, 0)) for x in range(3)]

    def test_move_fails_collision(self):
        """
        Given: > _ <
        Expect: x _ x
        """

        self.construct_simulation_runner(
            [MoveEastDummy, MoveWestDummy],
            [Location(0, 0), Location(2, 0)])
        avatars = [self.get_avatar(i) for i in range(2)]

        self.assert_at(avatars[0], Location(0, 0))
        self.assert_at(avatars[1], Location(2, 0))

        self.run_turn()

        self.assert_at(avatars[0], Location(0, 0))
        self.assert_at(avatars[1], Location(2, 0))

    def test_move_chain_fails_collision(self):
        """
        Given:  > > > _ <
        (1)
        Expect: x x x _ x
        """
        locations = [
            Location(0, 0),
            Location(1, 0),
            Location(2, 0),
            Location(4, 0)
        ]
        self.construct_simulation_runner(
            [MoveEastDummy, MoveEastDummy, MoveEastDummy, MoveWestDummy],
            locations)
        avatars = [self.get_avatar(i) for i in range(4)]

        [self.assert_at(avatars[i], locations[i]) for i in range(4)]
        self.run_turn()
        [self.assert_at(avatars[i], locations[i]) for i in range(4)]

    def test_move_chain_fails_cycle(self):
        """
        Given:  > v
                ^ <
        (1)
        Expect: x x
                x x
        """
        locations = [
            Location(0, 1),
            Location(1, 1),
            Location(1, 0),
            Location(0, 0)
        ]
        self.construct_simulation_runner(
            [MoveEastDummy, MoveSouthDummy, MoveWestDummy, MoveNorthDummy],
            locations)
        avatars = [self.get_avatar(i) for i in range(4)]

        [self.assert_at(avatars[i], locations[i]) for i in range(4)]
        self.run_turn()
        [self.assert_at(avatars[i], locations[i]) for i in range(4)]

    def test_move_chain_fails_spiral(self):
        """
        Given:  > > v
                  ^ <
        (1)
        Expect: x x x
                  x x
        """
        locations = [
            Location(0, 1),
            Location(1, 1),
            Location(2, 1),
            Location(2, 0),
            Location(1, 0)
        ]
        self.construct_simulation_runner([
            MoveEastDummy, MoveEastDummy, MoveSouthDummy, MoveWestDummy,
            MoveNorthDummy
        ], locations)
        avatars = [self.get_avatar(i) for i in range(5)]

        [self.assert_at(avatars[i], locations[i]) for i in range(5)]
        self.run_turn()
        [self.assert_at(avatars[i], locations[i]) for i in range(5)]