예제 #1
0
    def __init__(
        self, raw_game_data: Any, raw_game_info: Any, raw_observation: Any
    ) -> None:
        """
        Set up variables for use within Creeper.

        Args:
            raw_game_data (Any): self.game_data from main instance
            raw_game_info (Any): self.game_info from main instance
            raw_observation (Any): self.game_state from main instance

        Returns:
            None
        """
        self.bot = BotAI()
        game_data = GameData(raw_game_data.data)
        game_info = GameInfo(raw_game_info.game_info)
        game_state = GameState(raw_observation)
        self.bot._initialize_variables()
        self.bot._prepare_start(
            client=None, player_id=1, game_info=game_info, game_data=game_data
        )
        self.bot._prepare_step(state=game_state, proto_game_info=raw_game_info)
        self.pathing = PathManager(raw_game_data, raw_game_info, raw_observation)
        self.pf = PathFind(self.pathing.map_grid)
예제 #2
0
    def __init__(
        self, raw_game_data: Any, raw_game_info: Any, raw_observation: Any
    ) -> None:
        """
        Set up variables for use within PathMangager.

        Args:
            raw_game_data (Any): self.game_data from main instance
            raw_game_info (Any): self.game_info from main instance
            raw_observation (Any): self.game_state from main instance

        Returns:
            None
        """
        self.bot = BotAI()
        game_data = GameData(raw_game_data.data)
        game_info = GameInfo(raw_game_info.game_info)
        game_state = GameState(raw_observation)
        self.bot._initialize_variables()
        self.bot._prepare_start(
            client=None, player_id=1, game_info=game_info, game_data=game_data
        )
        self.bot._prepare_step(state=game_state, proto_game_info=raw_game_info)
        map_name = game_info.map_name
        self.pathing_dict: Dict[int, PathDict] = {}
        map_width = game_info.map_size.width
        map_height = game_info.map_size.height
        raw_grid = np.zeros((map_width, map_height))
        for x in range(map_width):
            for y in range(map_height):
                pos = Point2((x, y))
                raw_grid[x][y] = game_info.pathing_grid[pos]
        self.map_grid = np.rot90(raw_grid.astype(int))
        np.save(f"map_grids/{map_name}_grid", self.map_grid)
        self.pf = PathFind(self.map_grid)
    def test_bot_ai(self, bot: BotAI):
        bot._game_info.map_ramps, bot._game_info.vision_blockers = bot._game_info._find_ramps_and_vision_blockers(
        )
        assert bot.main_base_ramp  # Test if any ramp was found

        # Clear cache for expansion locations, recalculate and time it
        if hasattr(bot, "_cache_expansion_locations"):
            delattr(bot, "_cache_expansion_locations")
        t0 = time.perf_counter()
        bot._find_expansion_locations()
        t1 = time.perf_counter()
        print(f"Time to calculate expansion locations: {t1-t0} s")

        # TODO: Cache all expansion positions for a map and check if it is the same
        # BelShirVestigeLE has only 10 bases - perhaps it should be removed since it was a WOL / HOTS map
        assert len(
            bot.expansion_locations_list
        ) >= 10, f"Too few expansions found: {len(bot.expansion_locations_list)}"
        # Honorgrounds LE has 24 bases
        assert (
            len(bot.expansion_locations_list) <= 24
        ), f"Too many expansions found: {len(bot.expansion_locations_list)}"
        # On N player maps, it is expected that there are N*X bases because of symmetry, at least for maps designed for 1vs1
        assert (len(bot.expansion_locations_list) %
                (len(bot.enemy_start_locations) +
                 1) == 0), f"{bot.expansion_locations_list}"
        # Test if bot start location is in expansion locations
        assert bot.townhalls.random.position in set(
            bot.expansion_locations_list
        ), f'This error might occur if you are running the tests locally using command "pytest test/", possibly because you are using an outdated cache.py version, but it should not occur when using docker and pipenv.\n{bot.townhalls.random.position}, {bot.expansion_locations_list}'
        # Test if enemy start locations are in expansion locations
        for location in bot.enemy_start_locations:
            assert location in set(
                bot.expansion_locations_list
            ), f"{location}, {bot.expansion_locations_list}"
        # Each expansion is supposed to have at least one geysir and 6-12 minerals
        for expansion, resource_positions in bot.expansion_locations_dict.items(
        ):
            assert isinstance(expansion, Point2)
            assert isinstance(resource_positions, Units)
            if resource_positions:
                assert isinstance(resource_positions[0], Unit)
            # Neon violet has bases with just 6 resources. I think that was the back corner base with 4 minerals and 2 vespene
            # Odyssey has bases with 10 mineral patches and 2 geysirs
            # Blood boil returns 21?
            assert (
                6 <= len(resource_positions) <= 12
            ), f"{len(resource_positions)} resource fields in one base on map {bot.game_info.map_name}"

        assert bot.owned_expansions == {
            bot.townhalls.first.position: bot.townhalls.first
        }
예제 #4
0
def _abilities_all_units(bot_object : BotAI) -> Counter:
    """ Cache for the already_pending function """
    abilities_amount = Counter()
    for unit in bot_object.units({UnitTypeId.SCV, UnitTypeId.DRONE, UnitTypeId.PROBE}):
        for order in unit.orders:
            abilities_amount[order.ability] += 1

    return abilities_amount
예제 #5
0
def get_map_specific_bot(map_path: Path) -> BotAI:
    assert map_path in MAPS
    with lzma.open(str(map_path.absolute()), "rb") as f:
        raw_game_data, raw_game_info, raw_observation = pickle.load(f)

    # Build fresh bot object, and load the pickle'd data into the bot object
    bot = BotAI()
    game_data = GameData(raw_game_data.data)
    game_info = GameInfo(raw_game_info.game_info)
    game_state = GameState(raw_observation)
    bot._initialize_variables()
    bot._prepare_start(client=None, player_id=1, game_info=game_info, game_data=game_data)
    bot._prepare_step(state=game_state, proto_game_info=raw_game_info)

    return bot
예제 #6
0
def get_map_specific_bots() -> Iterable[BotAI]:
    folder = os.path.dirname(__file__)
    subfolder_name = "pickle_data"
    pickle_folder_path = os.path.join(folder, subfolder_name)
    files = os.listdir(pickle_folder_path)
    for file in (f for f in files if f.endswith(".xz")):
        with lzma.open(os.path.join(folder, subfolder_name, file), "rb") as f:
            raw_game_data, raw_game_info, raw_observation = pickle.load(f)

        # Build fresh bot object, and load the pickle'd data into the bot object
        bot = BotAI()
        game_data = GameData(raw_game_data.data)
        game_info = GameInfo(raw_game_info.game_info)
        game_state = GameState(raw_observation)
        bot._initialize_variables()
        bot._prepare_start(client=None, player_id=1, game_info=game_info, game_data=game_data)
        bot._prepare_step(state=game_state, proto_game_info=raw_game_info)

        yield bot
예제 #7
0
class Creeper:
    """Spread creep."""

    def __init__(
        self, raw_game_data: Any, raw_game_info: Any, raw_observation: Any
    ) -> None:
        """
        Set up variables for use within Creeper.

        Args:
            raw_game_data (Any): self.game_data from main instance
            raw_game_info (Any): self.game_info from main instance
            raw_observation (Any): self.game_state from main instance

        Returns:
            None
        """
        self.bot = BotAI()
        game_data = GameData(raw_game_data.data)
        game_info = GameInfo(raw_game_info.game_info)
        game_state = GameState(raw_observation)
        self.bot._initialize_variables()
        self.bot._prepare_start(
            client=None, player_id=1, game_info=game_info, game_data=game_data
        )
        self.bot._prepare_step(state=game_state, proto_game_info=raw_game_info)
        self.pathing = PathManager(raw_game_data, raw_game_info, raw_observation)
        self.pf = PathFind(self.pathing.map_grid)

    def check_tumor_position(
        self, possible_position: Point2, combined_grid: Any
    ) -> int:
        """
        Calculate the number of tiles the tumor position would cover.

        Arg:
            possible_position (Point2): the point being checked.
            combined_grid (Any): ndarray that's the pathfinding lib's path map and the
                                 creep map added together

        Returns:
            int: the number of tiles that would be covered.
        """
        x, y = floor(possible_position[0]), floor(possible_position[1])
        future_tiles = 0
        for i in range(-10, 11):
            for j in range(-10, 11):
                if 8 <= abs(j) <= 10:
                    if abs(i) <= 6 - 2 * (abs(j) - 8):
                        future_tiles += combined_grid[Point2((x + i, y + j))] % 2
                elif abs(j) == 7:
                    if abs(i) <= 7:
                        future_tiles += combined_grid[Point2((x + i, y + j))] % 2
                elif 3 <= abs(j) <= 6:
                    if abs(i) <= 9 - floor(abs(j) / 5):
                        future_tiles += combined_grid[Point2((x + i, y + j))] % 2
                else:
                    if combined_grid[Point2((x + i, y + j))] == 1:
                        future_tiles += 1
        return future_tiles

    async def find_position(
        self,
        tumor: Unit,
        tumor_positions: Set[Point2],
        creep_grid: Any,
        pathable_map: Any,
    ) -> Point2:
        """
        Find the location to spread the tumor to.

        Args:
            tumor (Unit): the creep tumor ready to be spread
            tumor_positions (Set[Point2]): list of existing tumor locations
            creep_grid (ndarray): the creep grid from the main bot
            pathable_map (ndarray): the pathfinding lib's pathable map

        Returns:
            Point2: where to spread the tumor.
        """
        path_creep_map = creep_grid + pathable_map
        tposx, tposy = tumor.position.x, tumor.position.y
        max_tiles = 0
        location = None
        for i in range(-10, 11):
            for j in range(-10, 11):
                if 81 <= i ** 2 + j ** 2 <= 105:
                    pos = Point2((tposx + i, tposy + j))
                    if pos not in tumor_positions:
                        if path_creep_map[floor(pos[0])][floor(pos[1])] != 2:
                            continue
                        tiles = self.check_tumor_position((pos), path_creep_map)
                        if tiles > max_tiles:
                            max_tiles = tiles
                            location = pos
        if max_tiles < 75:
            floored_unit_pos = Point2((floor(tposx), floor(tposy)))
            floored_e_base = Point2(
                (
                    floor(self.bot.enemy_start_locations[0].position.x),
                    floor(self.bot.enemy_start_locations[0].position.y),
                )
            )
            path_to_e_base = self.pf.find_path(floored_unit_pos, floored_e_base)[0]
            if path_to_e_base:
                for k in range(9, 5, -1):
                    pos = path_to_e_base[k]
                    if path_creep_map[pos[0]][pos[1]] == 2:
                        location = Point2(pos)
                        break
        if location:
            return location
        else:
            return tumor.position
예제 #8
0
async def _play_game_ai(
    client: Client, player_id: int, ai: BotAI, realtime: bool, game_time_limit: Optional[int]
) -> Result:
    gs: GameState = None

    async def initialize_first_step() -> Optional[Result]:
        nonlocal gs
        ai._initialize_variables()

        game_data = await client.get_game_data()
        game_info = await client.get_game_info()
        ping_response = await client.ping()

        # This game_data will become self.game_data in botAI
        ai._prepare_start(
            client, player_id, game_info, game_data, realtime=realtime, base_build=ping_response.ping.base_build
        )
        state = await client.observation()
        # check game result every time we get the observation
        if client._game_result:
            await ai.on_end(client._game_result[player_id])
            return client._game_result[player_id]
        gs = GameState(state.observation)
        proto_game_info = await client._execute(game_info=sc_pb.RequestGameInfo())
        try:
            ai._prepare_step(gs, proto_game_info)
            await ai.on_before_start()
            ai._prepare_first_step()
            await ai.on_start()
        # TODO Catching too general exception Exception (broad-except)
        # pylint: disable=W0703
        except Exception:
            logger.exception("AI on_start threw an error")
            logger.error("Resigning due to previous error")
            await ai.on_end(Result.Defeat)
            return Result.Defeat

    result = await initialize_first_step()
    if result is not None:
        return result

    async def run_bot_iteration(iteration: int):
        nonlocal gs
        logger.debug(f"Running AI step, it={iteration} {gs.game_loop / 22.4:.2f}s")
        # Issue event like unit created or unit destroyed
        await ai.issue_events()
        await ai.on_step(iteration)
        await ai._after_step()
        logger.debug("Running AI step: done")

    # Only used in realtime=True
    previous_state_observation = None
    for iteration in range(10**10):
        if realtime and gs:
            # On realtime=True, might get an error here: sc2.protocol.ProtocolError: ['Not in a game']
            with suppress(ProtocolError):
                requested_step = gs.game_loop + client.game_step
                state = await client.observation(requested_step)
                # If the bot took too long in the previous observation, request another observation one frame after
                if state.observation.observation.game_loop > requested_step:
                    logger.debug("Skipped a step in realtime=True")
                    previous_state_observation = state.observation
                    state = await client.observation(state.observation.observation.game_loop + 1)
        else:
            state = await client.observation()

        # check game result every time we get the observation
        if client._game_result:
            await ai.on_end(client._game_result[player_id])
            return client._game_result[player_id]
        gs = GameState(state.observation, previous_state_observation)
        previous_state_observation = None
        logger.debug(f"Score: {gs.score.score}")

        if game_time_limit and gs.game_loop / 22.4 > game_time_limit:
            await ai.on_end(Result.Tie)
            return Result.Tie
        proto_game_info = await client._execute(game_info=sc_pb.RequestGameInfo())
        ai._prepare_step(gs, proto_game_info)

        await run_bot_iteration(iteration)  # Main bot loop

        if not realtime:
            if not client.in_game:  # Client left (resigned) the game
                await ai.on_end(client._game_result[player_id])
                return client._game_result[player_id]

            # TODO: In bot vs bot, if the other bot ends the game, this bot gets stuck in requesting an observation when using main.py:run_multiple_games
            await client.step()
    return Result.Undecided
예제 #9
0
def import_bot_instance(
    raw_game_data: Response,
    raw_game_info: Response,
    raw_observation: ResponseObservation,
) -> BotAI:
    """
    import_bot_instance DocString
    """
    bot = BotAI()
    game_data = GameData(raw_game_data.data)
    game_info = GameInfo(raw_game_info.game_info)
    game_state = GameState(raw_observation)
    # noinspection PyProtectedMember
    bot._initialize_variables()
    # noinspection PyProtectedMember
    bot._prepare_start(client=None,
                       player_id=1,
                       game_info=game_info,
                       game_data=game_data)
    # noinspection PyProtectedMember
    bot._prepare_first_step()
    # noinspection PyProtectedMember
    bot._prepare_step(state=game_state, proto_game_info=raw_game_info)
    # noinspection PyProtectedMember
    bot._find_expansion_locations()
    return bot
예제 #10
0
    def test_bot_ai(self, bot: BotAI):
        # Test initial bot attributes at game start

        # Properties from _prepare_start
        assert 1 <= bot.player_id <= 2
        assert isinstance(bot.race, Race)
        assert isinstance(bot.enemy_race, Race)

        # Properties from _prepare_step
        assert bot.units.amount == bot.townhalls.amount + bot.workers.amount
        assert bot.workers.amount == 12
        assert bot.townhalls.amount == 1
        assert bot.geysers.amount == 0
        assert bot.minerals == 50
        assert bot.vespene == 0
        assert bot.supply_army == 0
        assert bot.supply_workers == 12
        assert bot.supply_cap == 15
        assert bot.supply_used == 12
        assert bot.supply_left == 3
        assert bot.idle_worker_count == 0
        assert bot.army_count == 0

        # Test bot_ai functions
        assert bot.time == 0
        assert bot.time_formatted in {"0:00", "00:00"}
        assert bot.start_location is None  # Is populated by main.py
        bot._game_info.player_start_location = bot.townhalls.random.position
        assert bot.townhalls.random.position not in bot.enemy_start_locations
        assert bot.known_enemy_units == Units([])
        assert bot.known_enemy_structures == Units([])
        bot._game_info.map_ramps, bot._game_info.vision_blockers = bot._game_info._find_ramps_and_vision_blockers()
        assert bot.main_base_ramp  # Test if any ramp was found
        # TODO: Cache all expansion positions for a map and check if it is the same
        assert len(bot.expansion_locations) >= 10
        # On N player maps, it is expected that there are N*X bases because of symmetry, at least for 1vs1 maps
        assert (
            len(bot.expansion_locations) % (len(bot.enemy_start_locations) + 1) == 0
        ), f"{set(bot.expansion_locations.keys())}"
        # Test if bot start location is in expansion locations
        assert bot.townhalls.random.position in set(
            bot.expansion_locations.keys()
        ), f'This error might occur if you are running the tests locally using command "pytest test/", possibly because you are using an outdated cache.py version, but it should not occur when using docker and pipenv.\n{bot.townhalls.random.position}, {set(bot.expansion_locations.keys())}'
        # Test if enemy start locations are in expansion locations
        for location in bot.enemy_start_locations:
            assert location in set(bot.expansion_locations.keys()), f"{location}, {bot.expansion_locations.keys()}"

        # The following functions need to be tested by autotest_bot.py because they use API query which isn't available here as this file only uses the pickle files and is not able to interact with the API as SC2 is not running while this test runs
        # get_available_abilities
        # expand_now
        # get_next_expansion
        # distribute_workers
        assert bot.owned_expansions == {bot.townhalls.first.position: bot.townhalls.first}
        assert bot.can_feed(UnitTypeId.MARINE)
        assert bot.can_feed(UnitTypeId.SIEGETANK)
        assert not bot.can_feed(UnitTypeId.THOR)
        assert not bot.can_feed(UnitTypeId.BATTLECRUISER)
        assert not bot.can_feed(UnitTypeId.IMMORTAL)
        assert bot.can_afford(UnitTypeId.SCV)
        assert bot.can_afford(UnitTypeId.MARINE)
        assert not bot.can_afford(UnitTypeId.SIEGETANK)
        assert not bot.can_afford(UnitTypeId.BATTLECRUISER)
        assert not bot.can_afford(UnitTypeId.MARAUDER)
        # can_cast
        worker = bot.workers.random
        assert bot.select_build_worker(worker.position) == worker
        for w in bot.workers:
            if w == worker:
                continue
            assert bot.select_build_worker(w.position) != worker
        # can_place
        # find_placement
        assert bot.already_pending_upgrade(UpgradeId.STIMPACK) == 0
        assert bot.already_pending(UpgradeId.STIMPACK) == 0
        assert bot.already_pending(UnitTypeId.SCV) == 0
        # build
        # do
        # do_actions
        # chat_send
        assert 0 < bot.get_terrain_height(worker)
        assert bot.in_placement_grid(worker)
        assert bot.in_pathing_grid(worker)
        assert bot.is_visible(worker)
        # The pickle data was created by a terran bot, so there is no creep under any worker
        assert not bot.has_creep(worker)
예제 #11
0
class PathManager:
    """Manage unit pathing."""

    def __init__(
        self, raw_game_data: Any, raw_game_info: Any, raw_observation: Any
    ) -> None:
        """
        Set up variables for use within PathMangager.

        Args:
            raw_game_data (Any): self.game_data from main instance
            raw_game_info (Any): self.game_info from main instance
            raw_observation (Any): self.game_state from main instance

        Returns:
            None
        """
        self.bot = BotAI()
        game_data = GameData(raw_game_data.data)
        game_info = GameInfo(raw_game_info.game_info)
        game_state = GameState(raw_observation)
        self.bot._initialize_variables()
        self.bot._prepare_start(
            client=None, player_id=1, game_info=game_info, game_data=game_data
        )
        self.bot._prepare_step(state=game_state, proto_game_info=raw_game_info)
        map_name = game_info.map_name
        self.pathing_dict: Dict[int, PathDict] = {}
        map_width = game_info.map_size.width
        map_height = game_info.map_size.height
        raw_grid = np.zeros((map_width, map_height))
        for x in range(map_width):
            for y in range(map_height):
                pos = Point2((x, y))
                raw_grid[x][y] = game_info.pathing_grid[pos]
        self.map_grid = np.rot90(raw_grid.astype(int))
        np.save(f"map_grids/{map_name}_grid", self.map_grid)
        self.pf = PathFind(self.map_grid)

    def add_to_path_dict(self, unit: Unit, destination: Point2) -> None:
        """
        Add unit's path to the path storage dictionary.

        Args:
            unit (Unit): the unit for pathing
            destination (Point2): where the unit is going
        Returns:
            None
        """
        self.pathing_dict[unit.tag] = {"path": [], "step": 0}
        raw_unit_pos = unit.position
        floored_unit_pos = Point2((floor(raw_unit_pos.x), floor(raw_unit_pos.y)))
        floored_dest = Point2((floor(destination[0]), floor(destination[1])))
        self.pathing_dict[unit.tag]["path"] = self.pf.find_path(
            floored_unit_pos, floored_dest
        )[0]

    def follow_path(self, unit: Unit, default: Point2) -> Point2:
        """
        Follow the path set or set a new one if none exists.

        Args:
            unit (Unit): the unit moving

        Returns:
            Point2: the location to attack
        """
        if (
            len(
                self.bot.structures.filter(
                    lambda unit: unit.type_id
                    in {UnitTypeId.NYDUSNETWORK, UnitTypeId.NYDUSCANAL}
                )
            )
            < 2
        ):
            if unit.tag not in self.pathing_dict:
                self.add_to_path_dict(unit, tuple(default))
            advance_factor = int(unit.movement_speed) + 2
            self.pathing_dict[unit.tag]["step"] += advance_factor
            curr_step = self.pathing_dict[unit.tag]["step"]
            if curr_step >= len(self.pathing_dict[unit.tag]["path"]):
                curr_step = len(self.pathing_dict[unit.tag]["path"]) - 1
            if curr_step < 0:
                return default
            a_move_to = Point2(self.pathing_dict[unit.tag]["path"][curr_step])
            if curr_step == len(self.pathing_dict[unit.tag]["path"]) - 1:
                del self.pathing_dict[unit.tag]
            return a_move_to