Example #1
0
    def __init__(self, center_location, is_start_location, knowledge):
        self.center_location: Point2 = center_location
        self.is_start_location: bool = is_start_location

        self.knowledge = knowledge
        self.ai: sc2.BotAI = knowledge.ai
        self.cache: "UnitCacheManager" = knowledge.unit_cache
        self.unit_values: "UnitValue" = knowledge.unit_values
        self.needs_evacuation = False
        self._is_enemys = False

        self.zone_index: int = 0
        self.paths: Dict[int, Path] = dict()  # paths to other expansions as it is dictated in the .expansion_zones
        # Game time seconds when we have last had visibility on this zone.
        self.last_scouted_center: float = -1
        self.last_scouted_mineral_line: float = -1

        # Timing on when there could be enemy workers here
        self.could_have_enemy_workers_in = 0

        # All mineral fields on the zone
        self._original_mineral_fields: Units = self.ai.expansion_locations.get(self.center_location, Units([], self.ai))
        self.mineral_fields: Units = Units(self._original_mineral_fields.copy(), self.ai)

        self.last_minerals: int = 10000000  # Arbitrary value just to ensure a lower value will get updated.
        # Game time seconds when scout has last circled around the center location of this zone.

        # All vespene geysers on the zone
        self.gas_buildings: Units = None

        self.scout_last_circled: Optional[int] = None

        self.our_townhall: Optional[Unit] = None
        self.enemy_townhall: Optional[Unit] = None

        self.known_enemy_units: Units = Units([], self.ai)
        self.our_units: Units = Units([], self.ai)
        self.our_workers: Units = Units([], self.ai)
        self.enemy_workers: Units = Units([], self.ai)
        self.known_enemy_power: ExtendedPower = ExtendedPower(self.unit_values)
        self.our_power: ExtendedPower = ExtendedPower(self.unit_values)

        # Assaulting enemies can be further away, but zone defense should prepare for at least that amount of defense
        self.assaulting_enemies: Units = Units([], self.ai)
        self.assaulting_enemy_power: ExtendedPower = ExtendedPower(self.unit_values)

        # 3 positions behind minerals
        self.behind_mineral_positions: List[Point2] = self._init_behind_mineral_positions()
        self._count_minerals()
        self._minerals_counter = IntervalFunc(knowledge.ai, self._count_minerals, 0.5)
        self.gather_point = self.center_location.towards(self.ai.game_info.map_center, 5)

        self.height = self.ai.get_terrain_height(center_location)

        # This is ExtendedRamp!
        self.ramp = self._find_ramp(self.ai)
        self.radius = Zone.ZONE_RADIUS
        self.danger_radius = Zone.ZONE_DANGER_RADIUS

        if self.ramp is not None:
            self.gather_point = self.ramp.top_center.towards(self.center_location, 4)
 async def start(self, knowledge: "Knowledge"):
     await super().start(knowledge)
     self.all_own: Units = Units([], self.ai)
     self.empty_units: Units = Units([], self.ai)
Example #3
0
 async def start(self, knowledge: "Knowledge"):
     await super().start(knowledge)
     self.units = Units([], self.ai)
 def free_units(self) -> Units:
     units: Units = Units(self.roles[UnitTask.Idle.value].units, self.ai)
     units.extend(self.roles[UnitTask.Moving.value].units)
     return units
Example #5
0
File: bot.py Project: jj634/sc2bot
    async def on_step(self, iteration):
        self.units_by_tag = {unit.tag: unit for unit in self.all_own_units}

        print(
            list((str(point) + ": " +
                  str(list(len(group.medivac_tags) for group in groups))
                  for point, groups in self.harass_assignments.items())))

        await self.macro()

        # endgame condition
        if not self.townhalls.ready:
            if not self.units:
                await self.chat_send("(pineapple)")
            target: Point2 = self.enemy_structures.random_or(
                self.enemy_start_locations[0]).position
            for unit in self.units():
                unit.attack(target)
            return

        # handle waiting marines and medivacs
        self.waiting_marine_tags = self.waiting_marine_tags & self.units_by_tag.keys(
        )
        self.waiting_medivac_tags = self.waiting_medivac_tags & self.units_by_tag.keys(
        )

        waiting_marines: Units = Units(
            {self.units_by_tag[m_tag]
             for m_tag in self.waiting_marine_tags}, self)
        waiting_medivacs: Units = Units(
            {self.units_by_tag[m_tag]
             for m_tag in self.waiting_medivac_tags}, self)

        chill_spot = self.own_expansions[self.townhalls.amount > 0].towards(
            self.game_info.map_center, 7)
        for unit in (waiting_marines + waiting_medivacs
                     ).filter(lambda u: u.distance_to(chill_spot) > 5):
            unit.attack(chill_spot)

        # handle attack groups
        needs_regroup_groups: Set[DropTactics] = set()
        perished_groups: Set[DropTactics] = set()
        for group in self.harass_groups:
            needs_regroup = await group.handle(self.units_by_tag)
            perished = group.perished(self.units_by_tag)
            if needs_regroup:
                needs_regroup_groups.add(group)
            elif perished:
                perished_groups.add(group)

        # handle wounded attack groups
        for group in needs_regroup_groups | perished_groups:
            if group in needs_regroup_groups:
                self.waiting_marine_tags |= group.marine_tags
                self.waiting_medivac_tags |= group.medivac_tags
            elif group in perished_groups:
                self.stranded_marine_tags |= group.marine_tags

            self.harass_groups.remove(group)
            self.harass_assignments[group.targets[0]].remove(group)

            joiners: List[JoinTactics] = self.join_assignments[group]
            if joiners:
                new_drop_tactics = DropTactics(
                    marine_tags=joiners[0].marine_tags,
                    medivac_tags=joiners[0].medivac_tags,
                    targets=group.targets,
                    retreat_point=group.retreat_point,
                    bot_object=self,
                    walk=False)

                if len(joiners) > 1:
                    joiners[1].assignment = new_drop_tactics

                self.join_assignments[new_drop_tactics] = joiners[1:]
                self.harass_groups.add(new_drop_tactics)

                current_groups = self.harass_assignments.get(
                    new_drop_tactics.targets[0]) or []
                current_groups.append(new_drop_tactics)
                self.harass_assignments[
                    new_drop_tactics.targets[0]] = current_groups
            del self.join_assignments[group]

        # assign drop and join groups
        if self.already_pending_upgrade(UpgradeId.STIMPACK) >= 0.9:
            while (len(self.waiting_marine_tags) >= 8
                   and len(self.waiting_medivac_tags) >= 1):
                print("new group!")
                new_harass_marine_tags = set(
                    itertools.islice(self.waiting_marine_tags, 8))
                new_harass_medivac_tags = set(
                    itertools.islice(self.waiting_medivac_tags, 1))

                self.waiting_marine_tags.difference_update(
                    new_harass_marine_tags)
                self.waiting_medivac_tags.difference_update(
                    new_harass_medivac_tags)

                heuristic_scores: Dict[Tuple[Point2, int], int] = dict()
                for enemy_base_i in range(self.enemy_size):
                    enemy_base = self.enemy_expansions[enemy_base_i]
                    for harass_i in range(self.HARASS_SIZE):
                        index = enemy_base_i + self.enemy_size * harass_i

                        enemy_base_squads = self.harass_assignments.get(
                            enemy_base) or []
                        squad_size = 0
                        if harass_i < len(enemy_base_squads):
                            current_squad = enemy_base_squads[harass_i]
                            squad_size += len(current_squad.medivac_tags)
                            joiners = self.join_assignments[current_squad]
                            if joiners:
                                squad_size += sum(
                                    len(joiner.medivac_tags)
                                    for joiner in joiners)

                        heuristic_scores[(
                            enemy_base, harass_i)] = index + squad_size * 1000

                best_match: Tuple[Point2,
                                  int] = min(heuristic_scores.keys(),
                                             key=lambda t: heuristic_scores[t])
                print(f"heuristic: {best_match[0]}, {best_match[1]}")
                if best_match[0] in self.harass_assignments.keys(
                ) and best_match[1] < len(
                        self.harass_assignments[best_match[0]]):
                    # there is an existing DropTactics. create a JoinTactics group
                    print("adding new join tactics")
                    existing_drop_tactic = self.harass_assignments[
                        best_match[0]][best_match[1]]
                    existing_join_tactics = self.join_assignments[
                        existing_drop_tactic]
                    next_assignment = existing_join_tactics[
                        len(existing_join_tactics) -
                        1] if existing_join_tactics else existing_drop_tactic
                    new_join_tactics = JoinTactics(
                        marine_tags=new_harass_marine_tags,
                        medivac_tags=new_harass_medivac_tags,
                        bot_object=self,
                        assignment=next_assignment)

                    self.join_assignments[existing_drop_tactic].append(
                        new_join_tactics)
                else:
                    # there is not an existing DropTactics. create a new DropTactics group
                    print("adding new drop tactics")
                    new_drop_tactics = DropTactics(
                        marine_tags=new_harass_marine_tags,
                        medivac_tags=new_harass_medivac_tags,
                        targets=[best_match[0], self.enemy_start_locations[0]],
                        retreat_point=self.own_expansions[
                            self.townhalls.amount > 0],
                        bot_object=self,
                        walk=False)
                    current_groups = self.harass_assignments.get(
                        best_match[0]) or []
                    current_groups.append(new_drop_tactics)
                    self.harass_assignments[best_match[0]] = current_groups
                    self.harass_groups.add(new_drop_tactics)
                    self.join_assignments[new_drop_tactics] = []

        # handle join groups
        for main_drop, join_groups in self.join_assignments.items():
            for join_group_i in range(len(join_groups)):
                join_group: JoinTactics = join_groups[join_group_i]
                arrived = await join_group.handle(self.units_by_tag)
                if join_group.assignment.perished(self.units_by_tag):
                    assert type(
                        join_group.assignment
                    ) == JoinTactics, "somehow a drop tactics slipped by"
                    next_assignment = join_group.assignment
                    while next_assignment.perished(self.units_by_tag):
                        join_groups.remove(next_assignment)
                        next_assignment = next_assignment.assignment
                    join_group.assignment = next_assignment
                elif arrived:
                    join_group.assignment.marine_tags = join_group.assignment.marine_tags | join_group.marine_tags
                    join_group.assignment.medivac_tags = join_group.assignment.medivac_tags | join_group.medivac_tags

                    if join_group_i + 1 < len(join_groups):
                        arrived_joiner = join_groups[join_group_i + 1]
                        arrived_joiner.assignment = join_group.assignment

                    self.join_assignments[main_drop].remove(join_group)
Example #6
0
    async def execute(self) -> bool:
        self.defender_tags.clear()

        if self.ai.supply_army > 3:
            # Safe
            if self.was_active:
                self.free_others()
                self.was_active = False
            return True

        combined_enemies: Units = Units([], self.ai)

        for zone in self.knowledge.expansion_zones:  # type: Zone
            if not zone.is_ours:
                continue

            combined_enemies |= zone.known_enemy_units

        already_defending: Units = self.knowledge.roles.units(UnitTask.Defending)

        if not combined_enemies:
            if self.was_active:
                self.free_others()
                self.was_active = False
            # Safe
            return True

        # for unit in already_defending:
        #     # return workers back to base that have wandered too far away
        #     if unit.type_id in self.unit_values.worker_types and unit.distance_to(self.ai.start_location) > 30:
        #         self.knowledge.roles.clear_task(unit)
        #         unit.gather(self.gather_mf)

        worker_only = combined_enemies.amount == combined_enemies.of_type(self.unit_values.worker_types).amount

        if combined_enemies.amount == 1 and worker_only:
            # Single scout worker
            u: Unit

            if (
                self.ai.workers.filter(
                    lambda u: u.shield_health_percentage < 0.75 and u.distance_to(self.ai.start_location) < 30
                ).amount
                == 0
            ):
                # Safe, let the scout do whatever it wants
                # TODO: Check expansion / building blocking
                self.free_others()
                return False  # Blocks normal zone defense, TODO: Remove this?

            self.attack_target(combined_enemies[0], 2, already_defending)
            self.free_others()
            return False  # Block other defense methods

        if worker_only:
            distance, closest_enemy = self.closest_distance_between_our_theirs(combined_enemies)
            if closest_enemy is None:
                closest_enemy = combined_enemies.closest_to(self.ai.start_location)

            buildings_needs_defending = self.ai.structures.filter(lambda u: self.building_needs_defending(u, 0.6))
            if not buildings_needs_defending.exists and (
                distance > 4
                or (len(already_defending) < 5 and combined_enemies.closest_distance_to(self.ai.start_location) > 9)
            ):
                self.free_others()
                return False  # no real danger, go back to mining

            require_workers = combined_enemies.amount + 2
            worker_count = self.ai.supply_workers

            if require_workers > 5 and worker_count > 5:
                require_workers = min(worker_count - 2, require_workers)

            army = self.get_army(closest_enemy, require_workers, already_defending)

            if not army:
                return False  # No army to fight with, waiting for one.

            self.knowledge.roles.set_tasks(UnitTask.Defending, army)
            # my_closest = army.closest_to(closest_enemy.position)
            # center = army.center

            buildings_needs_defending = self.ai.structures.filter(lambda u: self.building_needs_defending(u, 0.5))
            # own_closest = army.closest_to(closest_enemy)

            if buildings_needs_defending.exists or distance < 3:
                for unit in army:
                    # if unit.type_id == UnitTypeId.PROBE and unit.shield <= 5:
                    #     await self.regroup_defend(actions, army, combined_enemies, unit)
                    # else:
                    self.knowledge.combat_manager.add_unit(unit)
            else:
                for unit in army:
                    await self.regroup_defend(army, combined_enemies, unit)

            self.knowledge.combat_manager.execute(closest_enemy.position, MoveType.Assault)
            self.free_others()
        else:
            return True  # Don't know how to defend against

        self.was_active = True
        return False  # In Combat
Example #7
0
 def test_tags_in(self):
     self.assertEqual(
         self.marines.tags_in({245346374, 245346375}),
         Units([self.marine1, self.marine2], self.mock_game_state))
     self.assertEqual(self.marines.tags_in({}), self.emptyUnitsGroup)
Example #8
0
    def _prepare_units(self):
        # Set of enemy units detected by own sensor tower, as blips have less unit information than normal visible units
        self.blips: Set[Blip] = set()
        self.units: Units = Units([], self)
        self.structures: Units = Units([], self)
        self.enemy_units: Units = Units([], self)
        self.enemy_structures: Units = Units([], self)
        self.mineral_field: Units = Units([], self)
        self.vespene_geyser: Units = Units([], self)
        self.resources: Units = Units([], self)
        self.destructables: Units = Units([], self)
        self.watchtowers: Units = Units([], self)
        self.all_units: Units = Units([], self)
        self.workers: Units = Units([], self)
        self.townhalls: Units = Units([], self)
        self.gas_buildings: Units = Units([], self)
        self.larva: Units = Units([], self)
        self.techlab_tags: Set[int] = set()
        self.reactor_tags: Set[int] = set()

        for unit in self.state.observation_raw.units:
            if unit.is_blip:
                self.blips.add(Blip(unit))
            else:
                unit_type: int = unit.unit_type
                # Convert these units to effects: reaper grenade, parasitic bomb dummy, forcefield
                unit_obj = Unit(unit, self)
                self.units.append(unit_obj)
Example #9
0
 def add_worker(self, new_worker: Unit):
     self.add_workers(Units([new_worker], self._bot_object))
Example #10
0
    async def on_step(self, iteration):
        self.units_by_tag = {unit.tag: unit for unit in self.all_own_units}

        for expansion_i in range(len(self.enemy_expansions)):
            self._client.debug_text_world(
                text=f"{expansion_i} : {self.enemy_expansions[expansion_i]}",
                pos=self.enemy_expansions[expansion_i],
                color=(0, 255, 0),
                size=12,
            )

        if iteration == 1:
            raxpos: Point2 = await self.find_placement(
                UnitTypeId.BARRACKS,
                near=self.start_location.towards(self.game_info.map_center, 5),
                addon_place=True)
            await self.client.debug_create_unit([
                [UnitTypeId.BARRACKS, 1, raxpos, 1],
            ])

        if iteration > 1:
            depot = self.structures(UnitTypeId.SUPPLYDEPOT)
            barracks = self.structures(UnitTypeId.BARRACKS)
            if not barracks and not depot and self.already_pending(
                    UnitTypeId.SUPPLYDEPOT) == 0:
                await self.build(UnitTypeId.SUPPLYDEPOT,
                                 near=self.start_location.towards(
                                     self.game_info.map_center, 5))
            if depot and self.already_pending(
                    UnitTypeId.BARRACKS) == 0 and not barracks:
                pos: Point2 = await self.find_placement(
                    UnitTypeId.BARRACKS,
                    near=self.start_location.towards(self.game_info.map_center,
                                                     5),
                    addon_place=True)
                await self.build(UnitTypeId.BARRACKS, near=pos)

            for barrack in barracks.ready.filter(lambda b: not b.has_add_on):
                barrack.build(UnitTypeId.BARRACKSTECHLAB)

            techlab_rax = barracks.ready.filter(
                lambda b: b.has_techlab).random_or(None)
            if techlab_rax and self.already_pending_upgrade(
                    UpgradeId.STIMPACK) == 0:
                techlab = self.structures.find_by_tag(
                    tag=techlab_rax.add_on_tag)
                techlab.research(UpgradeId.STIMPACK)

            if self.already_pending_upgrade(UpgradeId.STIMPACK) > 0:
                if (len(self.waiting_marine_tags) >= self.HARASS_SIZE * 8 and
                        len(self.waiting_medivac_tags) >= self.HARASS_SIZE):
                    new_harass_marine_tags = set(
                        itertools.islice(self.waiting_marine_tags,
                                         self.HARASS_SIZE * 8))
                    new_harass_medivac_tags = set(
                        itertools.islice(self.waiting_medivac_tags,
                                         self.HARASS_SIZE))

                    self.waiting_marine_tags.difference_update(
                        new_harass_marine_tags)
                    self.waiting_medivac_tags.difference_update(
                        new_harass_medivac_tags)

                    next_targets = list(
                        filter(lambda p: self.harass_assignments[p] is None,
                               self.enemy_expansions))
                    if len(next_targets) > 0:
                        new_drop_tactics = DropTactics(
                            marine_tags=new_harass_marine_tags,
                            medivac_tags=new_harass_medivac_tags,
                            targets=[next_targets[0]],
                            retreat_point=self.start_location,
                            bot_object=self,
                            walk=False)

                        self.harass_assignments[
                            next_targets[0]] = new_drop_tactics
                        self.harass_groups.add(new_drop_tactics)

                for group in self.harass_groups:
                    await group.handle(self.units_by_tag)

                alive_marine_tags = self.waiting_marine_tags & self.units_by_tag.keys(
                )
                alive_medivac_tags = self.waiting_medivac_tags & self.units_by_tag.keys(
                )

                waiting_marines: Units = Units(
                    {self.units_by_tag[m_tag]
                     for m_tag in alive_marine_tags}, self)
                waiting_medivacs: Units = Units(
                    {self.units_by_tag[m_tag]
                     for m_tag in alive_medivac_tags}, self)

                self.waiting_marine_tags = alive_marine_tags
                self.waiting_medivac_tags = alive_medivac_tags

                chill_spot = self.own_expansions[
                    self.townhalls.amount > 0].towards(
                        self.game_info.map_center, 10)
                for unit in (waiting_marines + waiting_medivacs
                             ).filter(lambda u: u.distance_to(chill_spot) > 5):
                    unit.attack(chill_spot)
Example #11
0
    def distribute_workers(self):
        # Only do anything once every 3 seconds
        if self.time - self.last_distribute < 3:
            return

        self.last_distribute = self.time

        # Kinda hard to gather anything without a base
        if not self.townhalls.ready.exists:
            return

        # mineral patches near one of our bases
        acceptable_minerals = self.mineral_field.filter(lambda node: any([
            nex.position.is_closer_than(15, node.position)
            for nex in self.townhalls.ready
        ]))

        workers_per_gas = 1 + min(
            2, int(self.workers.amount / acceptable_minerals.amount))

        if self.minerals < 50 and self.vespene > 300:
            workers_per_gas -= 1

        # gas buildings probably at bases that have been destroyed
        bad_geysers = self.structures(
            self.shared.gas_structure).filter(lambda a: all(
                ex.is_further_than(15, a) for ex in self.owned_expansions.keys(
                )) or a.vespene_contents == 0 or a.assigned_harvesters >
                                              workers_per_gas)

        # gas buildings that don't have enough harvesters
        needy_geysers = self.structures(
            self.shared.gas_structure).ready.tags_not_in([
                a.tag for a in bad_geysers
            ]).filter(lambda a: a.assigned_harvesters < workers_per_gas)

        # tag collections for easy selection and matching
        acceptable_mineral_tags = [f.tag for f in acceptable_minerals]
        needy_mineral_tags = [
            f.tag for f in acceptable_minerals
            if self.townhalls.closest_to(f.position).surplus_harvesters < 0
        ]

        # anywhere else is strictly forbidden
        unacceptable_mineral_tags = [
            f.tag
            for f in self.mineral_field.tags_not_in(acceptable_mineral_tags)
        ]

        bad_workers = self.unallocated(self.shared.worker_types).filter(
            lambda p:
            # Grab these suckers first
            p.is_idle or (p.is_gathering and p.orders[0].target in
                          unacceptable_mineral_tags) or
            (p.is_gathering and p.orders[0].target in bad_geysers) or p.orders[
                0].ability.id in [
                    AbilityId.ATTACK_ATTACKTOWARDS, AbilityId.ATTACK_ATTACK,
                    AbilityId.ATTACK
                ])

        # up to N workers, where N is the number of surplus harvesters, from each base where there are any
        # may not grab them all every time (it gets only the ones returning minerals), but it'll get enough
        excess_workers = Units(
            list_flatten([
                self.workers.filter(lambda w: w.is_carrying_minerals and w.
                                    orders and w.orders[0].target == base.tag)
                [0:base.surplus_harvesters] for base in self.townhalls.filter(
                    lambda base: base.surplus_harvesters > 0)
            ]), self)

        # to fill up your first gas building, you'll need these
        mining_workers = self.workers.filter(
            lambda p:
            # if more are needed, this is okay too
            p.is_gathering and (p.orders[0].target in acceptable_mineral_tags
                                or p.is_carrying_minerals)) - (bad_workers +
                                                               excess_workers)

        usable_workers = bad_workers + excess_workers + mining_workers

        taken_workers = 0

        def get_workers(num):
            nonlocal taken_workers
            if taken_workers + num > usable_workers.amount:
                return []
            taken_workers += num
            return usable_workers[taken_workers - num:taken_workers]

        for needy_geyser in needy_geysers:
            workers = get_workers(workers_per_gas -
                                  needy_geyser.assigned_harvesters)
            for worker in workers:
                self.do(worker.gather(needy_geyser))

        if taken_workers < bad_workers.amount and acceptable_mineral_tags:
            remaining_bad_workers = get_workers(bad_workers.amount -
                                                taken_workers)
            for worker in remaining_bad_workers:
                self.do(
                    worker.gather(
                        self.mineral_field.tags_in(
                            acceptable_mineral_tags).random))

        if taken_workers < bad_workers.amount + excess_workers.amount and needy_mineral_tags:
            remaining_excess_workers = get_workers(bad_workers.amount +
                                                   excess_workers.amount -
                                                   taken_workers)
            for worker in remaining_excess_workers:
                self.do(
                    worker.gather(
                        self.mineral_field.tags_in(needy_mineral_tags).random))
Example #12
0
class Scout(SubActs):
    units: Units

    def __init__(self, unit_types: Union[UnitTypeId, Set[UnitTypeId]], unit_count: int, *args: ScoutBaseAction):
        """
        Scout act for all races, loops the given scout actions
        @param unit_types: Types of units accepted as scouts
        @param unit_count: Units required to be used in scouting, scouting will only start after all are available
        @param args: Scout actions, cen be to scout a certain location, or to move around in certain way. Defaults to scouting enemy main
        """
        if isinstance(unit_types, UnitTypeId):
            self.unit_types = set()
            self.unit_types.add(unit_types)
        else:
            self.unit_types = unit_types
        self.unit_count = unit_count

        if len(args) > 0:
            super().__init__(*args)
        else:
            super().__init__(ScoutLocation.scout_main())

        self.scout_tags: List[int] = []
        self.started = False
        self.ended = False
        self.index = 0

    async def start(self, knowledge: "Knowledge"):
        await super().start(knowledge)
        self.units = Units([], self.ai)

    async def execute(self) -> bool:
        if self.ended:
            return True

        self.units.clear()

        if self.find_units():
            return True

        if self.units:
            self.roles.set_tasks(UnitTask.Scouting, self.units)
            await self.micro_units()  # Ignore if the scouting has finished
        return True

    async def micro_units(self) -> bool:
        """
        Micros units
        @return: True when finished
        """
        count = len(self.orders)
        self.index = self.index % count

        for looped in range(0, count):
            if looped == count:
                self.ended = True
                return True
            # noinspection PyTypeChecker
            action: ScoutBaseAction = self.orders[self.index]
            action.set_scouts(self.units)
            result = await action.execute()
            if not result:
                # Not finished
                return False

            self.index = (self.index + 1) % count
        return False

    def find_units(self) -> bool:
        if not self.started:
            if UnitTypeId.OVERLORD in self.unit_types:
                free_units = self.roles.get_types_from(
                    self.unit_types, UnitTask.Idle, UnitTask.Moving, UnitTask.Gathering, UnitTask.Reserved
                )
            else:
                free_units = self.roles.get_types_from(
                    self.unit_types, UnitTask.Idle, UnitTask.Moving, UnitTask.Gathering
                )
            if len(free_units) >= self.unit_count:
                # TODO: Better selection?
                new_scouts = free_units.random_group_of(self.unit_count)
                self.units.extend(new_scouts)
                self.scout_tags = new_scouts.tags

                self.started = True
        else:
            scouts = self.roles.get_types_from(self.unit_types, UnitTask.Scouting)
            self.units.extend(scouts.tags_in(self.scout_tags))
            if not self.units:
                # Scouts are dead, end the scout act
                self.ended = True
                return True
Example #13
0
class UnitManager():
    def __init__(self, bot: BotAI, scouting_manager: ScoutingManager):
        self.bot = bot
        self.scouting_manager = scouting_manager
        self.unselectable = Units([], self.bot._game_data)
        self.unselectable_enemy_units = Units([], self.bot._game_data)
        self.scouting_ttl = 300
        self.army_scouting_ttl = 100
        self.panic_scout_ttl = 0
        self.inject_targets: Dict[Unit, Unit] = {}
        self.inject_queens: Units = Units([], self.bot._game_data)
        self.dead_tumors: Units = Units([], self.bot._game_data)
        self.spread_overlords: Units = Units([], self.bot._game_data)
        self.chasing_workers: Units = Units([], self.bot._game_data)

    async def iterate(self, iteration):
        self.scouting_ttl -= 1

        actions: List[UnitCommand] = []

        all_army: Units = self.bot.units.exclude_type(
            {OVERLORD, DRONE, QUEEN, LARVA, EGG}).not_structure.ready
        observed_enemy_army = self.scouting_manager.observed_enemy_units.filter(
            lambda u: u.can_attack_ground or u.type_id == UnitTypeId.BUNKER)
        estimated_enemy_value = self.scouting_manager.estimated_enemy_army_value

        army_units = all_army

        for observed_enemy in observed_enemy_army:
            pos = observed_enemy.position
            self.bot._client.debug_text_world(f'observed',
                                              Point3((pos.x, pos.y, 10)), None,
                                              12)

        # ASSIGN INJECT QUEENS
        hatches = self.bot.find_closest_n_from_units(
            self.bot.start_location, 4,
            self.bot.units(HATCHERY)).ready.tags_not_in(
                set(map(lambda h: h.tag, self.inject_targets.keys())))
        for hatch in hatches:
            free_queens: Units = self.bot.units(QUEEN).tags_not_in(
                self.unselectable.tags).tags_not_in(self.inject_queens.tags)
            if free_queens.exists:
                queen = free_queens.random
                self.inject_targets[hatch] = queen
                self.inject_queens.append(queen)

        # INJECT
        for hatch in self.inject_targets:
            if self.bot.known_enemy_units.closer_than(15, hatch).exists:
                continue
            inject_queen = self.inject_targets[hatch]
            if inject_queen:
                try:
                    abilities = await self.bot.get_available_abilities(
                        inject_queen)
                    if abilities and len(
                            abilities
                    ) > 0 and AbilityId.EFFECT_INJECTLARVA in abilities:
                        actions.append(
                            inject_queen(AbilityId.EFFECT_INJECTLARVA, hatch))
                    else:
                        # move to hatch
                        pass
                except:
                    print('inject error')
            else:
                del self.inject_targets[hatch]

        # SCOUTING

        if army_units(
                LING
        ).exists and self.scouting_ttl < 0 and self.scouting_manager.enemy_raiders_value == 0:
            self.scouting_ttl = 300
            unit: Unit = army_units(LING).random
            actions.append(unit.stop())
            scouting_order: List[Point2] = []
            keys: List[Point2] = list(self.bot.expansion_locations.keys())
            for idx in range(len(self.bot.expansion_locations)):
                furthest = self.bot.enemy_start_locations[0].furthest(keys)
                scouting_order.append(furthest)
                keys.remove(furthest)
            for position in scouting_order:
                actions.append(unit.move(position, True))
            self.unselectable.append(unit)

        # army scout only if opponent army has not been close for a while
        if not observed_enemy_army.closer_than(
                70, self.bot.own_natural).amount > 2:
            self.army_scouting_ttl -= 1
        else:
            self.army_scouting_ttl = 60

        if self.army_scouting_ttl <= 0 and army_units(LING).exists:
            self.army_scouting_ttl = 60
            unit: Unit = army_units(LING).random
            actions.append(unit.move(self.bot.enemy_start_locations[0]))
            self.unselectable.append(unit)

        # panic scout main if drone difference gets high enough
        if self.bot.already_pending(DRONE) + self.bot.units(
                DRONE
        ).amount > 25 * self.scouting_manager.enemy_townhall_count:
            if self.panic_scout_ttl <= 0:
                if self.bot.units(OVERLORD).exists:
                    closest_overlord = self.bot.units(OVERLORD).tags_not_in(
                        self.unselectable.tags).closest_to(
                            self.bot.enemy_start_locations[0])
                    original_position = closest_overlord.position
                    actions.append(closest_overlord.stop())
                    actions.append(
                        closest_overlord.move(
                            self.bot.enemy_start_locations[0], True))
                    actions.append(
                        closest_overlord.move(original_position, True))
                    self.unselectable.append(closest_overlord)
                    self.panic_scout_ttl = 300
            else:
                self.panic_scout_ttl -= 1

        # KILL TERRAN BUILDINGS WITH MUTAS
        if self.scouting_manager.terran_floating_buildings:
            mutas: Units = self.bot.units(MUTALISK).tags_not_in(
                self.unselectable.tags)
            pos: Point2 = self.bot.enemy_start_locations[
                0] + 15 * self.bot._game_info.map_center.direction_vector(
                    self.bot.enemy_start_locations[0])
            corners = [
                Point2((0, 0)),
                Point2((self.bot._game_info.pathing_grid.width - 1, 0)),
                Point2((self.bot._game_info.pathing_grid.width - 1,
                        self.bot._game_info.pathing_grid.height - 1)),
                Point2((0, self.bot._game_info.pathing_grid.height - 1)),
                Point2((0, 0))
            ]
            for muta in mutas:
                for corner in corners:
                    actions.append(muta.attack(corner, True))
                self.unselectable.append(muta)

        # UPDATE UNSELECTABLE UNITS SNAPSHOTS

        self.unselectable = self.bot.units.tags_in(self.unselectable.tags)

        to_remove = []
        for unit in self.unselectable:
            self.bot._client.debug_text_world(
                f'unselectable', Point3(
                    (unit.position.x, unit.position.y, 10)), None, 12)
            if unit.is_idle or unit.is_gathering or not unit.is_visible:
                to_remove.append(unit.tag)
        self.unselectable = self.unselectable.tags_not_in(set(to_remove))

        self.spread_overlords = self.bot.units.tags_in(
            self.spread_overlords.tags)
        for overlord in self.spread_overlords:
            self.bot._client.debug_text_world(
                f'spread',
                Point3((overlord.position.x, overlord.position.y, 10)), None,
                12)

        groups_start_time = time.time()
        # ARMY GROUPS

        groups: List[Units] = self.group_army(
            army_units.tags_not_in(self.unselectable.tags))

        for group in groups:
            nearby_enemies = None
            if observed_enemy_army.exists:
                closest_enemy = observed_enemy_army.closest_to(group.center)
                if closest_enemy.distance_to(group.center) < 15:
                    nearby_enemies: Units = observed_enemy_army.closer_than(
                        15, closest_enemy)
                    enemy_value = self.bot.calculate_combat_value(
                        nearby_enemies.ready)
            group_value = self.bot.calculate_combat_value(group)

            if nearby_enemies and nearby_enemies.exists:
                bias = 1
                if nearby_enemies.closer_than(
                        15, self.bot.own_natural).exists and group_value > 750:
                    bias = 1.2
                if self.bot.supply_used > 180:
                    bias = 1.5
                should_engage: bool = self.evaluate_engagement(
                    self.bot.units.exclude_type({DRONE, OVERLORD}).closer_than(
                        20, nearby_enemies.center), nearby_enemies, bias) > 0
                if should_engage:
                    # attack enemy group

                    # ling micro
                    microing_back_tags: List[int] = []
                    if nearby_enemies(LING).exists:
                        for unit in group(LING):
                            local_enemies: Units = nearby_enemies.closer_than(
                                3, unit.position)
                            local_allies: Units = group.closer_than(
                                3, unit.position)
                            # TODO: use attack range instead of proximity... (if enemies cant attack they arent a threat)
                            if (self.bot.calculate_combat_value(local_enemies)
                                    > self.bot.calculate_combat_value(
                                        local_allies)):
                                target = unit.position + 5 * local_enemies.center.direction_vector(
                                    group.center)
                                actions.append(unit.move(target))
                                microing_back_tags.append(unit.tag)
                                self.bot._client.debug_text_world(
                                    f'micro point',
                                    Point3((target.x, target.y, 10)), None, 12)
                                self.bot._client.debug_text_world(
                                    f'microing back',
                                    Point3((unit.position.x, unit.position.y,
                                            10)), None, 12)

                    if nearby_enemies.exclude_type({
                            UnitTypeId.CHANGELINGZERGLING,
                            UnitTypeId.CHANGELING,
                            UnitTypeId.CHANGELINGZERGLINGWINGS
                    }).exists:
                        actions.extend(
                            self.command_group(
                                group.tags_not_in(set(microing_back_tags)),
                                AbilityId.ATTACK, nearby_enemies.center))
                    else:
                        actions.extend(
                            self.command_group(
                                group, AbilityId.ATTACK,
                                nearby_enemies.closest_to(group.center)))
                    self.bot._client.debug_text_world(
                        f'attacking group',
                        Point3((group.center.x, group.center.y, 10)), None, 12)
                else:
                    # retreat somewhwere
                    mins = self.bot.get_mineral_fields_for_expansion(
                        self.bot.closest_mining_expansion_location(
                            group.center).position)
                    if mins.exists:
                        move_position = mins.center
                    else:
                        move_position = self.bot.start_location
                    if group.center.distance_to(move_position) < 5:
                        # Last resort attack with everything
                        everything: Units = group
                        if enemy_value > 150:
                            everything = self.bot.units.closer_than(
                                15, group.center)
                            self.unselectable.extend(everything)
                        everything = everything + self.bot.units(QUEEN)
                        actions.extend(
                            self.command_group(everything, AbilityId.ATTACK,
                                               nearby_enemies.center))
                        self.bot._client.debug_text_world(
                            f'last resort',
                            Point3((group.center.x, group.center.y, 10)), None,
                            12)
                    else:
                        # TODO: dont retreat if too close to enemy
                        actions.extend(
                            self.command_group(group, AbilityId.MOVE,
                                               move_position))
                        self.bot._client.debug_text_world(
                            f'retreating',
                            Point3((group.center.x, group.center.y, 10)), None,
                            12)
            else:
                if group_value > 1.2 * estimated_enemy_value or self.bot.supply_used >= 180:
                    # attack toward closest enemy buildings
                    attack_position = self.bot.enemy_start_locations[0]
                    observed_structures = self.scouting_manager.observed_enemy_units.structure
                    if observed_structures.exists:
                        attack_position = observed_structures.closest_to(
                            group.center).position
                    if self.scouting_manager.observed_enemy_units.exists:
                        target_enemy_units: Units = self.scouting_manager.observed_enemy_units.filter(
                            lambda u: u.can_attack_ground)
                        if target_enemy_units.exists:
                            attack_position = target_enemy_units.closest_to(
                                group.center).position
                    actions.extend(
                        self.command_group(group, AbilityId.ATTACK,
                                           attack_position))
                    self.bot._client.debug_text_world(
                        f'attacking base',
                        Point3((group.center.x, group.center.y, 10)), None, 12)
                else:
                    # merge
                    other_units: Units = all_army.tags_not_in(
                        group.tags.union(self.unselectable.tags))
                    if other_units.exists:
                        closest_other_unit: Unit = other_units.closest_to(
                            group.center)
                        actions.extend(
                            self.command_group(group, AbilityId.MOVE,
                                               closest_other_unit.position))
                        self.bot._client.debug_text_world(
                            f'merging',
                            Point3((group.center.x, group.center.y, 10)), None,
                            12)
                    else:
                        self.bot._client.debug_text_world(
                            f'idle',
                            Point3((group.center.x, group.center.y, 10)), None,
                            12)
        execution_time = (time.time() - groups_start_time) * 1000
        print(f'//// Groups: {round(execution_time, 3)}ms')

        # DRONE DEFENSE
        for expansion in self.bot.owned_expansions:
            enemy_raid: Units = observed_enemy_army.closer_than(15, expansion)
            if enemy_raid.exists:
                raid_value = self.bot.calculate_combat_value(enemy_raid)
                defending_army: Units = self.bot.units.closer_than(
                    15, expansion)
                if raid_value > self.bot.calculate_combat_value(
                        defending_army.exclude_type({DRONE})):
                    for defender in self.bot.units(DRONE).closer_than(
                            10, expansion).tags_not_in(self.unselectable.tags):
                        pos = defender.position
                        if expansion != self.bot.start_location:
                            self.bot._client.debug_text_world(
                                f'mineral walking', Point3((pos.x, pos.y, 10)),
                                None, 12)
                            actions.append(
                                defender.gather(self.bot.main_minerals.random))
                        else:
                            # pull drones vs harass
                            if enemy_raid.closer_than(
                                    5, defender.position
                            ).exists and not enemy_raid.of_type({
                                    DRONE, UnitTypeId.PROBE, UnitTypeId.SCV
                            }).exists:
                                self.bot._client.debug_text_world(
                                    f'pull the bois', Point3(
                                        (pos.x, pos.y, 10)), None, 12)
                                actions.append(
                                    defender.attack(enemy_raid.center))
                            # counter worker rush
                            elif enemy_raid.of_type(
                                {DRONE, UnitTypeId.SCV,
                                 UnitTypeId.PROBE}).exists:
                                if raid_value > 90:
                                    self.bot._client.debug_text_world(
                                        f'defend worker rush',
                                        Point3((pos.x, pos.y, 10)), None, 12)
                                    actions.append(
                                        defender.attack(enemy_raid.center))

        # DEFEND CANNON RUSH AND OTHER STUFF WITH DRONES

        for expansion in self.bot.owned_expansions:
            enemy_scouting_workers = self.bot.known_enemy_units(
                {DRONE, UnitTypeId.PROBE, UnitTypeId.SCV}).closer_than(
                    20,
                    expansion).tags_not_in(self.unselectable_enemy_units.tags)
            enemy_proxies = self.bot.known_enemy_structures.closer_than(
                20, expansion).tags_not_in(self.unselectable_enemy_units.tags)
            if enemy_proxies.exists:
                for proxy in enemy_proxies:
                    if proxy.type_id == UnitTypeId.PHOTONCANNON:
                        for drone in self.bot.units(DRONE).tags_not_in(
                                self.unselectable.tags).take(4, False):
                            actions.append(drone.attack(proxy))
                            self.unselectable.append(drone)
                            self.unselectable_enemy_units.append(proxy)
            if enemy_scouting_workers.exists:
                for enemy_worker in enemy_scouting_workers:
                    own_workers: Units = self.bot.units(DRONE).tags_not_in(
                        self.unselectable.tags)
                    if own_workers.exists:
                        own_worker: Unit = own_workers.closest_to(enemy_worker)
                        actions.append(own_worker.attack(enemy_worker))
                        self.chasing_workers.append(own_worker)
                        self.unselectable.append(own_worker)
                        self.unselectable_enemy_units.append(enemy_worker)

        # send back drones that are chasing workers
        self.chasing_workers = self.bot.units.tags_in(
            self.chasing_workers.tags)
        for drone in self.chasing_workers(DRONE):
            if self.bot.units(HATCHERY).closest_to(
                    drone.position).position.distance_to(drone.position) > 25:
                if isinstance(drone.order_target, int):
                    self.unselectable_enemy_units = self.unselectable_enemy_units.tags_not_in(
                        {drone.order_target})
                self.chasing_workers = self.chasing_workers.tags_not_in(
                    {drone.tag})
                actions.append(drone.gather(self.bot.main_minerals.random))

        extra_queen_start_time = time.time()
        # EXTRA QUEEN CONTROL
        extra_queens = self.bot.units(QUEEN).tags_not_in(
            self.unselectable.tags)
        # if there's a fight contribute otherwise make creep tumors
        if extra_queens.exists:
            if self.bot.known_enemy_units.exists and self.bot.units.closer_than(
                    20, extra_queens.center).tags_not_in(
                        extra_queens.tags).filter(
                            lambda u: u.is_attacking
                        ).exists and self.bot.known_enemy_units.closer_than(
                            20, extra_queens.center).exists:
                actions.extend(
                    self.command_group(
                        extra_queens, AbilityId.ATTACK,
                        self.bot.known_enemy_units.closest_to(
                            extra_queens.center).position))
                self.bot._client.debug_text_world(
                    f'queen attack',
                    Point3((extra_queens.center.x, extra_queens.center.y, 10)),
                    None, 12)
            else:
                for queen in extra_queens.tags_not_in(self.inject_queens.tags):
                    if queen.is_idle:
                        abilities = await self.bot.get_available_abilities(
                            queen)
                        position = await self.bot.find_tumor_placement()
                        if AbilityId.BUILD_CREEPTUMOR_QUEEN in abilities and position and self.bot.has_creep(
                                position):
                            actions.append(
                                queen(AbilityId.BUILD_CREEPTUMOR, position))
                            self.unselectable.append(queen)
                        else:
                            if queen.position.distance_to(
                                    extra_queens.center) > 2:
                                # regroup extra queens
                                actions.append(queen.move(extra_queens.center))
        execution_time = (time.time() - extra_queen_start_time) * 1000
        print(f'//// Extra queens: {round(execution_time, 3)}ms')

        creep_start_time = time.time()
        # CREEP TUMORS
        for tumor in self.bot.units(UnitTypeId.CREEPTUMORBURROWED).tags_not_in(
                self.dead_tumors.tags):
            # TODO: direct creep spread to some direction...
            # Dont overmake creep xd
            abilities = await self.bot.get_available_abilities(tumor)
            if AbilityId.BUILD_CREEPTUMOR_TUMOR in abilities:
                angle = random.randint(0, 360)
                x = math.cos(angle)
                y = math.sin(angle)
                position: Point2 = tumor.position + (9 * Point2((x, y)))
                if self.bot.has_creep(position) and not self.bot.units(
                        UnitTypeId.CREEPTUMORBURROWED).closer_than(
                            9, position
                        ).exists and not self.bot.position_blocks_expansion(
                            position):
                    actions.append(tumor(AbilityId.BUILD_CREEPTUMOR, position))
                    self.dead_tumors.append(tumor)
        execution_time = (time.time() - creep_start_time) * 1000
        print(f'//// Creep: {round(execution_time, 3)}ms')

        # OVERLORD retreat from enemy structures and anti air stuff
        for overlord in self.bot.units(OVERLORD).tags_not_in(
                self.unselectable.tags):
            threats: Units = self.bot.known_enemy_units.filter(
                lambda u: u.is_structure or u.can_attack_air).closer_than(
                    15, overlord)
            if threats.exists:
                destination: Point2 = overlord.position + 2 * threats.center.direction_vector(
                    overlord.position)
                actions.append(overlord.move(destination))

        # OVERSEERS
        overseers: Units = self.bot.units(OVERSEER)
        if overseers.exists:
            for overseer in overseers:
                if self.spread_overlords.find_by_tag(overseer.tag):
                    self.spread_overlords = self.spread_overlords.tags_not_in(
                        {overseer.tag})
                abilities = await self.bot.get_available_abilities(overseer)
                if AbilityId.SPAWNCHANGELING_SPAWNCHANGELING in abilities:
                    actions.append(
                        overseer(AbilityId.SPAWNCHANGELING_SPAWNCHANGELING))

        # CHANGELINGS
        changelings: Units = self.bot.units(CHANGELING).tags_not_in(
            self.unselectable.tags)
        if changelings.exists:
            for changeling in changelings:
                actions.append(changeling.move(self.bot.enemy_natural))
                self.unselectable.append(changeling)

        return actions

    def one_of_targets_in_range(self, unit: Unit, targets: Units):
        for target in targets:
            if unit.target_in_range(target):
                return True
        return False

    def group_army(self, army: Units) -> List[Units]:
        groups: List[Units] = []
        already_grouped_tags = []

        for unit in army:
            if unit.tag in already_grouped_tags:
                continue
            # TODO: fix recursive grouping
            # neighbors: Units = self.find_neighbors(unit, army.tags_not_in(set(already_grouped_tags)))
            neighbors: Units = army.closer_than(15, unit.position)
            groups.append(neighbors)
            already_grouped_tags.extend(neighbors.tags)

        return groups

    def find_neighbors(self, THE_SOURCE: Unit, units: Units) -> Units:
        neighbors: Units = units.closer_than(3, THE_SOURCE.position)

        temp: Units = Units([], self.bot._game_data)
        for individual in neighbors:
            temp.__or__(
                self.find_neighbors(individual,
                                    units.tags_not_in(neighbors.tags)))
        output = neighbors.__or__(temp)
        if output is None:
            return Units([], self.bot._game_data)
        return neighbors.__or__(temp)

    def command_group(self, units: Units, command: UnitCommand,
                      target: Union[Unit, Point2]):
        commands = []
        for unit in units:
            commands.append(unit(command, target))
        return commands

    async def inject(self):
        ready_queens = []
        actions = []
        for queen in self.bot.units(QUEEN).idle:
            abilities = await self.bot.get_available_abilities(queen)
            if AbilityId.EFFECT_INJECTLARVA in abilities:
                ready_queens.append(queen)
        for queen in ready_queens:
            actions.append(
                queen(AbilityId.EFFECT_INJECTLARVA,
                      self.bot.units(HATCHERY).first))
        return actions

    def evaluate_engagement(self,
                            own_units: Units,
                            enemy_units: Units,
                            bias=1):
        own_ranged: Units = own_units.filter(lambda u: u.ground_range > 3)
        own_melee: Units = own_units.tags_not_in(own_ranged.tags)
        enemy_ranged: Units = enemy_units.filter(lambda u: u.ground_range > 3)

        try:
            own_ranged_value = bias * self.bot.calculate_combat_value(
                own_ranged)
        except:
            own_ranged_value = 0
        try:
            enemy_ranged_value = self.bot.calculate_combat_value(enemy_ranged)
        except:
            enemy_ranged_value = 0

        corrected_own_value = bias * self.bot.calculate_combat_value(own_units)

        if own_ranged_value < enemy_ranged_value and own_units.exists:
            perimeter = self.get_enemy_perimeter(
                enemy_units.not_structure, self.bot.known_enemy_structures,
                own_units.center)
            if own_melee.exists:
                own_melee_value = bias * self.bot.calculate_combat_value(
                    Units(own_melee.take(perimeter * 2, require_all=False),
                          self.bot._game_data))
            else:
                own_melee_value = 0
            corrected_own_value = own_melee_value + own_ranged_value
        evaluation = corrected_own_value - self.bot.calculate_combat_value(
            enemy_units)
        return evaluation

    def get_enemy_perimeter(self, enemy_units: Units, enemy_structures: Units,
                            reference_position: Point2):
        perimeter = 0
        pathing_grid: PixelMap = self.bot._game_info.pathing_grid
        for enemy_unit in enemy_units:
            enemies_excluding_self: Units = enemy_units.tags_not_in(
                {enemy_unit.tag})
            pos: Point2 = enemy_unit.position
            positions = [
                Point2((pos.x - 1, pos.y + 1)),
                Point2((pos.x, pos.y + 1)),
                Point2((pos.x + 1, pos.y + 1)),
                Point2((pos.x - 1, pos.y)),
                # [pos.x, pos.y], disregard center point
                Point2((pos.x + 1, pos.y)),
                Point2((pos.x - 1, pos.y - 1)),
                Point2((pos.x, pos.y - 1)),
                Point2((pos.x + 1, pos.y - 1)),
            ]
            if reference_position.distance_to(enemy_unit.position) > 5:
                positions = remove_n_furthest_points(positions,
                                                     reference_position, 3)
            for p in positions:
                if pathing_grid[
                        math.floor(p.x), math.floor(
                            p.y
                        )] <= 0 and not enemies_excluding_self.closer_than(
                            1, p).exists and not enemy_structures.closer_than(
                                1, p).exists:
                    perimeter += 1
        return perimeter
Example #14
0
 async def start(self, knowledge: 'Knowledge'):
     await super().start(knowledge)
     self.empty_units: Units = Units([], self.ai)
Example #15
0
 def test_tags_not_in(self):
     self.assertEqual(self.marines.tags_not_in({}), self.marines)
     self.assertEqual(
         self.marines.tags_not_in({245346374}),
         Units([self.marine2, self.marine3], self.mock_game_state))
Example #16
0
def test_bot_ai():
    bot: BotAI = random_bot_object
    # 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.workers.amount
    assert bot.structures.amount == bot.townhalls.amount
    assert bot.workers.amount == 12
    assert bot.townhalls.amount == 1
    assert bot.gas_buildings.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 properties updated by "_prepare_units" function
    assert not bot.blips
    assert bot.units
    assert bot.structures
    assert not bot.enemy_units
    assert not bot.enemy_structures
    assert bot.mineral_field
    assert bot.vespene_geyser
    assert bot.resources
    assert len(bot.destructables) >= 0
    assert isinstance(bot.destructables, (list, set, dict))
    assert len(bot.watchtowers) >= 0
    assert bot.all_units
    assert bot.workers
    assert bot.townhalls
    assert not bot.gas_buildings

    # 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.enemy_units == Units([], bot)
    assert bot.enemy_structures == Units([], bot)
    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.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
    assert len(
        bot.expansion_locations
    ) >= 10, f"Too few expansions found: {len(bot.expansion_locations)}"
    # Honorgrounds LE has 24
    assert len(
        bot.expansion_locations
    ) <= 24, f"Too many expansions found: {len(bot.expansion_locations)}"
    # 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
    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.ZERGLING)
    assert bot.can_afford(UnitTypeId.MARINE)
    assert bot.can_afford(UnitTypeId.SCV)
    assert bot.can_afford(UnitTypeId.DRONE)
    assert bot.can_afford(UnitTypeId.PROBE)
    assert bot.can_afford(AbilityId.COMMANDCENTERTRAIN_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)
    assert not bot.can_afford(UpgradeId.WARPGATERESEARCH)
    assert not bot.can_afford(AbilityId.RESEARCH_WARPGATE)

    # Store old values for minerals, vespene
    old_values = bot.minerals, bot.vespene, bot.supply_cap, bot.supply_left, bot.supply_used
    bot.vespene = 50
    assert bot.can_afford(UpgradeId.WARPGATERESEARCH)
    assert bot.can_afford(AbilityId.RESEARCH_WARPGATE)
    bot.minerals = 150
    bot.supply_cap = 15
    bot.supply_left = -1
    bot.supply_used = 16
    # Confirm that units that don't cost supply can be built while at negative supply using can_afford function
    assert bot.can_afford(UnitTypeId.GATEWAY)
    assert bot.can_afford(UnitTypeId.PYLON)
    assert bot.can_afford(UnitTypeId.OVERLORD)
    assert bot.can_afford(UnitTypeId.BANELING)
    assert not bot.can_afford(UnitTypeId.ZERGLING)
    assert not bot.can_afford(UnitTypeId.MARINE)
    bot.minerals, bot.vespene, bot.supply_cap, bot.supply_left, bot.supply_used = old_values

    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
    assert bot.already_pending_upgrade(UpgradeId.STIMPACK) == 0
    assert bot.already_pending(UpgradeId.STIMPACK) == 0
    assert bot.already_pending(UnitTypeId.SCV) == 0
    assert 0 < bot.get_terrain_height(worker)
    assert bot.in_placement_grid(worker)
    assert bot.in_pathing_grid(worker)
    # The pickle data was created by a terran bot, so there is no creep under any worker
    assert not bot.has_creep(worker)
    # Why did this stop working, not visible on first frame?
    assert bot.is_visible(
        worker
    ), f"Visibility value at worker is {bot.state.visibility[worker.position.rounded]}"

    # Check price for morphing units and upgrades
    cost_100 = [
        AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL1,
        UpgradeId.TERRANSHIPWEAPONSLEVEL1,
        AbilityId.ARMORYRESEARCH_TERRANVEHICLEPLATINGLEVEL1,
        UpgradeId.TERRANVEHICLEARMORSLEVEL1,
        AbilityId.ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL1,
        UpgradeId.TERRANVEHICLEWEAPONSLEVEL1,
        AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYARMORLEVEL1,
        UpgradeId.TERRANINFANTRYARMORSLEVEL1,
        AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL1,
        UpgradeId.TERRANINFANTRYWEAPONSLEVEL1,
        AbilityId.RESEARCH_ZERGMELEEWEAPONSLEVEL1,
        UpgradeId.ZERGMELEEWEAPONSLEVEL1,
        AbilityId.RESEARCH_ZERGMISSILEWEAPONSLEVEL1,
        UpgradeId.ZERGMISSILEWEAPONSLEVEL1,
        AbilityId.FORGERESEARCH_PROTOSSGROUNDARMORLEVEL1,
        UpgradeId.PROTOSSGROUNDARMORSLEVEL1,
        AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL1,
        UpgradeId.PROTOSSGROUNDWEAPONSLEVEL1,
        AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL1,
        UpgradeId.TERRANINFANTRYWEAPONSLEVEL1,
        AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL1,
        UpgradeId.TERRANINFANTRYWEAPONSLEVEL1,
        AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL1,
        UpgradeId.ZERGFLYERWEAPONSLEVEL1,
        AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL1,
        UpgradeId.PROTOSSAIRWEAPONSLEVEL1,
    ]
    cost_175 = [
        AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL2,
        UpgradeId.TERRANSHIPWEAPONSLEVEL2,
        AbilityId.ARMORYRESEARCH_TERRANVEHICLEPLATINGLEVEL2,
        UpgradeId.TERRANVEHICLEARMORSLEVEL2,
        AbilityId.ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL2,
        UpgradeId.TERRANVEHICLEWEAPONSLEVEL2,
        AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYARMORLEVEL2,
        UpgradeId.TERRANINFANTRYARMORSLEVEL2,
        AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL2,
        UpgradeId.TERRANINFANTRYWEAPONSLEVEL2,
        AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL2,
        UpgradeId.TERRANINFANTRYWEAPONSLEVEL2,
        AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL2,
        UpgradeId.TERRANINFANTRYWEAPONSLEVEL2,
        AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL2,
        UpgradeId.ZERGFLYERWEAPONSLEVEL2,
        AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL2,
        UpgradeId.PROTOSSAIRWEAPONSLEVEL2,
    ]
    cost_200 = [
        AbilityId.RESEARCH_ZERGMELEEWEAPONSLEVEL3,
        UpgradeId.ZERGMELEEWEAPONSLEVEL3,
        AbilityId.RESEARCH_ZERGMISSILEWEAPONSLEVEL3,
        UpgradeId.ZERGMISSILEWEAPONSLEVEL3,
        AbilityId.FORGERESEARCH_PROTOSSGROUNDARMORLEVEL3,
        UpgradeId.PROTOSSGROUNDARMORSLEVEL3,
        AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL3,
        UpgradeId.PROTOSSGROUNDWEAPONSLEVEL3,
    ]
    cost_250 = [
        AbilityId.ARMORYRESEARCH_TERRANSHIPWEAPONSLEVEL3,
        UpgradeId.TERRANSHIPWEAPONSLEVEL3,
        AbilityId.ARMORYRESEARCH_TERRANVEHICLEPLATINGLEVEL3,
        UpgradeId.TERRANVEHICLEARMORSLEVEL3,
        AbilityId.ARMORYRESEARCH_TERRANVEHICLEWEAPONSLEVEL3,
        UpgradeId.TERRANVEHICLEWEAPONSLEVEL3,
        AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYARMORLEVEL3,
        UpgradeId.TERRANINFANTRYARMORSLEVEL3,
        AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL3,
        UpgradeId.TERRANINFANTRYWEAPONSLEVEL3,
        AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL3,
        UpgradeId.TERRANINFANTRYWEAPONSLEVEL3,
        AbilityId.ENGINEERINGBAYRESEARCH_TERRANINFANTRYWEAPONSLEVEL3,
        UpgradeId.TERRANINFANTRYWEAPONSLEVEL3,
        AbilityId.RESEARCH_ZERGFLYERATTACKLEVEL3,
        UpgradeId.ZERGFLYERWEAPONSLEVEL3,
        AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL3,
        UpgradeId.PROTOSSAIRWEAPONSLEVEL3,
    ]

    cost_150 = [
        AbilityId.RESEARCH_ZERGGROUNDARMORLEVEL1,
        UpgradeId.ZERGGROUNDARMORSLEVEL1,
        AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL1,
        UpgradeId.PROTOSSSHIELDSLEVEL1,
        AbilityId.RESEARCH_ZERGFLYERARMORLEVEL1,
        UpgradeId.ZERGFLYERARMORSLEVEL1,
        AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL1,
        UpgradeId.PROTOSSSHIELDSLEVEL1,
        AbilityId.RESEARCH_ZERGMELEEWEAPONSLEVEL2,
        UpgradeId.ZERGMELEEWEAPONSLEVEL2,
        AbilityId.RESEARCH_ZERGMISSILEWEAPONSLEVEL2,
        UpgradeId.ZERGMISSILEWEAPONSLEVEL2,
        AbilityId.FORGERESEARCH_PROTOSSGROUNDARMORLEVEL2,
        UpgradeId.PROTOSSGROUNDARMORSLEVEL2,
        AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL2,
        UpgradeId.PROTOSSGROUNDWEAPONSLEVEL2,
    ]
    cost_225 = [
        AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL2,
        UpgradeId.PROTOSSSHIELDSLEVEL2,
        AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL2,
        UpgradeId.PROTOSSSHIELDSLEVEL2,
        AbilityId.RESEARCH_ZERGGROUNDARMORLEVEL2,
        UpgradeId.ZERGGROUNDARMORSLEVEL2,
        AbilityId.RESEARCH_ZERGFLYERARMORLEVEL2,
        UpgradeId.ZERGFLYERARMORSLEVEL2,
    ]
    cost_300 = [
        AbilityId.RESEARCH_ZERGGROUNDARMORLEVEL3,
        UpgradeId.ZERGGROUNDARMORSLEVEL3,
        AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL3,
        UpgradeId.PROTOSSSHIELDSLEVEL3,
        AbilityId.RESEARCH_ZERGFLYERARMORLEVEL3,
        UpgradeId.ZERGFLYERARMORSLEVEL3,
        AbilityId.FORGERESEARCH_PROTOSSSHIELDSLEVEL3,
        UpgradeId.PROTOSSSHIELDSLEVEL3,
    ]
    cost_list = [100, 175, 200, 250, 150, 225, 300]

    def calc_cost(item_id) -> Cost:
        if isinstance(item_id, AbilityId):
            return bot.game_data.calculate_ability_cost(item_id)
        elif isinstance(item_id, UpgradeId):
            return bot.game_data.upgrades[item_id.value].cost
        elif isinstance(item_id, UnitTypeId):
            creation_ability: AbilityId = bot.game_data.units[
                item_id.value].creation_ability
            return bot.game_data.calculate_ability_cost(creation_ability)

    def assert_cost(item_id, real_cost: Cost):
        assert calc_cost(
            item_id
        ) == real_cost, f"Cost of {item_id} should be {real_cost} but is {calc_cost(item_id)}"

    for items, cost in zip(
        [cost_100, cost_175, cost_200, cost_250, cost_150, cost_225, cost_300],
            cost_list):
        real_cost2: Cost = Cost(cost, cost)
        for item in items:
            assert_cost(item, real_cost2)
            assert (
                bot.calculate_cost(item) == real_cost2
            ), f"Cost of {item} should be {real_cost2} but is {calc_cost(item)}"

    # Do not use the generic research abilities in the bot when testing if you can afford it as these are wrong
    assert_cost(AbilityId.RESEARCH_ZERGFLYERARMOR, Cost(0, 0))
    assert_cost(AbilityId.RESEARCH_ZERGFLYERATTACK, Cost(0, 0))
    assert_cost(AbilityId.RESEARCH_ZERGGROUNDARMOR, Cost(0, 0))
    assert_cost(AbilityId.RESEARCH_ZERGMELEEWEAPONS, Cost(0, 0))
    assert_cost(AbilityId.RESEARCH_ZERGMISSILEWEAPONS, Cost(0, 0))
    assert_cost(AbilityId.RESEARCH_TERRANINFANTRYARMOR, Cost(0, 0))
    assert_cost(AbilityId.RESEARCH_TERRANINFANTRYWEAPONS, Cost(0, 0))
    assert_cost(AbilityId.RESEARCH_PROTOSSGROUNDARMOR, Cost(0, 0))
    assert_cost(AbilityId.RESEARCH_PROTOSSGROUNDWEAPONS, Cost(0, 0))
    assert_cost(AbilityId.RESEARCH_PROTOSSSHIELDS, Cost(0, 0))
    assert_cost(AbilityId.RESEARCH_TERRANSHIPWEAPONS, Cost(0, 0))
    assert_cost(AbilityId.RESEARCH_TERRANVEHICLEWEAPONS, Cost(0, 0))

    # Somehow this is 0, returned by the API
    assert_cost(AbilityId.BUILD_REACTOR, Cost(0, 0))
    # UnitTypeId.REACTOR has no creation ability (None)
    # assert_cost(UnitTypeId.REACTOR, Cost(50, 50))

    assert_cost(AbilityId.BUILD_REACTOR_BARRACKS, Cost(50, 50))
    assert_cost(UnitTypeId.BARRACKSREACTOR, Cost(50, 50))
    assert_cost(AbilityId.UPGRADETOORBITAL_ORBITALCOMMAND, Cost(150, 0))
    assert_cost(UnitTypeId.ORBITALCOMMAND, Cost(150, 0))
    assert_cost(AbilityId.UPGRADETOORBITAL_ORBITALCOMMAND, Cost(150, 0))

    assert bot.calculate_unit_value(UnitTypeId.ORBITALCOMMAND) == Cost(550, 0)
    assert bot.calculate_unit_value(UnitTypeId.RAVAGER) == Cost(100, 100)
    assert bot.calculate_unit_value(UnitTypeId.ARCHON) == Cost(175, 275)
    assert bot.calculate_unit_value(UnitTypeId.ADEPTPHASESHIFT) == Cost(0, 0)
    assert bot.calculate_unit_value(UnitTypeId.AUTOTURRET) == Cost(100, 0)
    assert bot.calculate_unit_value(UnitTypeId.INFESTORTERRAN) == Cost(0, 0)
    assert bot.calculate_unit_value(UnitTypeId.INFESTORTERRANBURROWED) == Cost(
        0, 0)
    assert bot.calculate_unit_value(UnitTypeId.LARVA) == Cost(0, 0)
    assert bot.calculate_unit_value(UnitTypeId.EGG) == Cost(0, 0)
    assert bot.calculate_unit_value(UnitTypeId.LOCUSTMP) == Cost(0, 0)
    assert bot.calculate_unit_value(UnitTypeId.LOCUSTMPFLYING) == Cost(0, 0)
    assert bot.calculate_unit_value(UnitTypeId.BROODLING) == Cost(0, 0)
    # Other and effects
    assert bot.calculate_unit_value(UnitTypeId.KD8CHARGE) == Cost(0, 0)
    assert bot.calculate_unit_value(
        UnitTypeId.RAVAGERCORROSIVEBILEMISSILE) == Cost(0, 0)
    assert bot.calculate_unit_value(UnitTypeId.VIPERACGLUESCREENDUMMY) == Cost(
        0, 0)

    assert bot.calculate_cost(UnitTypeId.BROODLORD) == Cost(150, 150)
    assert bot.calculate_cost(UnitTypeId.RAVAGER) == Cost(25, 75)
    assert bot.calculate_cost(UnitTypeId.BANELING) == Cost(25, 25)
    assert bot.calculate_cost(UnitTypeId.ORBITALCOMMAND) == Cost(150, 0)
    assert bot.calculate_cost(
        AbilityId.UPGRADETOORBITAL_ORBITALCOMMAND) == Cost(150, 0)
    assert bot.calculate_cost(UnitTypeId.REACTOR) == Cost(50, 50)
    assert bot.calculate_cost(UnitTypeId.TECHLAB) == Cost(50, 25)
    assert bot.calculate_cost(UnitTypeId.QUEEN) == Cost(150, 0)
    assert bot.calculate_cost(UnitTypeId.HATCHERY) == Cost(300, 0)
    assert bot.calculate_cost(UnitTypeId.LAIR) == Cost(150, 100)
    assert bot.calculate_cost(UnitTypeId.HIVE) == Cost(200, 150)
    assert bot.calculate_cost(UnitTypeId.DRONE) == Cost(50, 0)
    assert bot.calculate_cost(UnitTypeId.SCV) == Cost(50, 0)
    assert bot.calculate_cost(UnitTypeId.PROBE) == Cost(50, 0)
    assert bot.calculate_cost(UnitTypeId.SPIRE) == Cost(200, 200)
    assert bot.calculate_cost(UnitTypeId.ARCHON) == bot.calculate_unit_value(
        UnitTypeId.ARCHON)

    # The following are morph abilities that may need a fix
    assert_cost(AbilityId.MORPHTOBROODLORD_BROODLORD, Cost(300, 250))
    assert_cost(UnitTypeId.BROODLORD, Cost(300, 250))
    assert_cost(AbilityId.MORPHTORAVAGER_RAVAGER, Cost(100, 100))
    assert_cost(AbilityId.MORPHTOBROODLORD_BROODLORD, Cost(300, 250))
    assert_cost(AbilityId.MORPHZERGLINGTOBANELING_BANELING, Cost(50, 25))

    assert Cost(100, 50) == 2 * Cost(50, 25)
    assert Cost(100, 50) == Cost(50, 25) * 2

    assert bot.calculate_supply_cost(UnitTypeId.BARRACKS) == 0
    assert bot.calculate_supply_cost(UnitTypeId.HATCHERY) == 0
    assert bot.calculate_supply_cost(UnitTypeId.OVERLORD) == 0
    assert bot.calculate_supply_cost(UnitTypeId.ZERGLING) == 1
    assert bot.calculate_supply_cost(UnitTypeId.MARINE) == 1
    assert bot.calculate_supply_cost(UnitTypeId.BANELING) == 0
    assert bot.calculate_supply_cost(UnitTypeId.QUEEN) == 2
    assert bot.calculate_supply_cost(UnitTypeId.ROACH) == 2
    assert bot.calculate_supply_cost(UnitTypeId.RAVAGER) == 1
    assert bot.calculate_supply_cost(UnitTypeId.CORRUPTOR) == 2
    assert bot.calculate_supply_cost(UnitTypeId.BROODLORD) == 2
    assert bot.calculate_supply_cost(UnitTypeId.HYDRALISK) == 2
    assert bot.calculate_supply_cost(UnitTypeId.LURKERMP) == 1
Example #17
0
 def test_noqueue(self):
     self.assertEqual(self.marines.noqueue,
                      Units([self.marine3], self.mock_game_state))
Example #18
0
 def _initialize_variables(self):
     DistanceCalculation.__init__(self)
     # Specific opponent bot ID used in sc2ai ladder games http://sc2ai.net/
     # The bot ID will stay the same each game so your bot can "adapt" to the opponent
     self.opponent_id: int = None
     # This value will be set to True by main.py in self._prepare_start if game is played in realtime (if true, the bot will have limited time per step)
     self.realtime: bool = False
     self.all_units: Units = Units([], self)
     self.units: Units = Units([], self)
     self.workers: Units = Units([], self)
     self.townhalls: Units = Units([], self)
     self.structures: Units = Units([], self)
     self.gas_buildings: Units = Units([], self)
     self.enemy_units: Units = Units([], self)
     self.enemy_structures: Units = Units([], self)
     self.resources: Units = Units([], self)
     self.destructables: Units = Units([], self)
     self.watchtowers: Units = Units([], self)
     self.mineral_field: Units = Units([], self)
     self.vespene_geyser: Units = Units([], self)
     self.larva: Units = Units([], self)
     self.techlab_tags: Set[int] = set()
     self.reactor_tags: Set[int] = set()
     self.minerals: int = None
     self.vespene: int = None
     self.supply_army: Union[float, int] = None
     # Doesn't include workers in production
     self.supply_workers: Union[float, int] = None
     self.supply_cap: Union[float, int] = None
     self.supply_used: Union[float, int] = None
     self.supply_left: Union[float, int] = None
     self.idle_worker_count: int = None
     self.army_count: int = None
     self.warp_gate_count: int = None
     self.larva_count: int = None
     self.actions: List[UnitCommand] = []
     self.blips: Set[Blip] = set()
     self.race: Race = None
     self.enemy_race: Race = None
     self._units_created: Counter = Counter()
     self._unit_tags_seen_this_game: Set[int] = set()
     self._units_previous_map: Dict[int, Unit] = dict()
     self._structures_previous_map: Dict[int, Unit] = dict()
     self._enemy_units_previous_map: Dict[int, Unit] = dict()
     self._enemy_structures_previous_map: Dict[int, Unit] = dict()
     self._all_units_previous_map: Dict[int, Unit] = dict()
     self._previous_upgrades: Set[UpgradeId] = set()
     self._expansion_positions_list: List[Point2] = []
     self._resource_location_to_expansion_position_dict: Dict[Point2,
                                                              Point2] = {}
     # Internally used to keep track which units received an action in this frame, so that self.train() function does not give the same larva two orders - cleared every frame
     self.unit_tags_received_action: Set[int] = set()
Example #19
0
 def test_idle(self):
     self.assertEqual(self.marines.idle,
                      Units([self.marine3], self.mock_game_state))
Example #20
0
    async def update_influence(self):
        power = ExtendedPower(self.unit_values)
        self.path_finder_terrain.reset()  # Reset
        self.map.reset()  # Reset
        self.map.enable_colossus_map(
            self.knowledge.my_race == Race.Protoss
            and len(self.cache.own(UnitTypeId.COLOSSUS)) > 0)
        self.map.enable_reaper_map(
            self.knowledge.my_race == Race.Terran
            and len(self.cache.own(UnitTypeId.REAPER)) > 0)

        positions = []

        for mf in self.ai.mineral_field:  # type: Unit
            # In 4.8.5+ minerals are no longer visible in pathing grid unless player has vision
            positions.append(mf.position)

        self.path_finder_terrain.create_block(positions, (2, 1))
        self.map.create_block(positions, (2, 1))

        self.set_rocks(self.path_finder_terrain)
        self.set_rocks(self.map)

        for building in self.ai.structures + self.knowledge.known_enemy_structures:  # type: Unit
            if building.type_id in buildings_2x2:
                self.map.create_block(building.position, (2, 2))
            elif building.type_id in buildings_3x3:
                self.map.create_block(building.position, (3, 3))
            elif building.type_id in buildings_5x5:
                self.map.create_block(building.position, (5, 3))
                self.map.create_block(building.position, (3, 5))

        self.map.normalize_influence(20)

        for enemy_type in self.cache.enemy_unit_cache:  # type: UnitTypeId
            enemies: Units = self.cache.enemy_unit_cache.get(
                enemy_type, Units([], self.ai))
            if len(enemies) == 0:
                continue

            example_enemy: Unit = enemies[0]
            power.clear()
            power.add_unit(enemy_type, 100)

            if self.unit_values.can_shoot_air(example_enemy):
                positions: List[Point2] = [
                    unit.position for unit in enemies
                ]  # need to be specified in both places
                s_range = self.unit_values.air_range(example_enemy)

                if example_enemy.type_id == UnitTypeId.CYCLONE:
                    s_range = 7

                if example_enemy.is_structure:
                    self.map.add_pure_ground_influence(positions,
                                                       power.air_power,
                                                       s_range, s_range)
                else:
                    self.map.add_air_influence(positions, power.air_power,
                                               s_range, s_range + 3)

            if self.unit_values.can_shoot_ground(example_enemy):
                positions = [unit.position for unit in enemies
                             ]  # need to be specified in both places
                s_range = self.unit_values.ground_range(example_enemy)
                if example_enemy.type_id == UnitTypeId.CYCLONE:
                    s_range = 7
                if example_enemy.type_id == UnitTypeId.SIEGETANKSIEGED:
                    self.map.add_tank_influence(positions, power.ground_power)
                elif s_range < 2:
                    self.map.add_walk_influence(positions, power.ground_power,
                                                7)
                elif example_enemy.is_structure:
                    self.map.add_pure_ground_influence(positions,
                                                       power.ground_power,
                                                       s_range, s_range)
                elif s_range < 5:
                    self.map.add_pure_ground_influence(positions,
                                                       power.ground_power,
                                                       s_range, 7)
                else:
                    self.map.add_pure_ground_influence(positions,
                                                       power.ground_power,
                                                       s_range, s_range + 3)

        # influence, radius, points, can it hit air?
        effect_dict: Dict[EffectId, Tuple[float, float, List[Point2],
                                          bool]] = dict()
        for effect in self.ai.state.effects:
            values: Tuple[float, float, List[Point2], bool] = None

            if effect.id == EffectId.RAVAGERCORROSIVEBILECP:
                values = effect_dict.get(effect.id, (1000, 2.5, [], True))
                values[2].append(Point2.center(effect.positions))
            elif effect.id == EffectId.BLINDINGCLOUDCP:
                values = effect_dict.get(effect.id, (400, 3.5, [], False))
                values[2].append(Point2.center(effect.positions))
            elif effect.id == EffectId.NUKEPERSISTENT:
                values = effect_dict.get(effect.id, (900, 9, [], True))
                values[2].append(Point2.center(effect.positions))
            elif effect.id == EffectId.PSISTORMPERSISTENT:
                values = effect_dict.get(effect.id, (300, 3.5, [], True))
                values[2].append(Point2.center(effect.positions))
            elif effect.id == EffectId.LIBERATORTARGETMORPHDELAYPERSISTENT:
                values = effect_dict.get(effect.id, (200, 6, [], False))
                values[2].append(Point2.center(effect.positions))
            elif effect.id == EffectId.LIBERATORTARGETMORPHPERSISTENT:
                values = effect_dict.get(effect.id, (300, 6, [], False))
                values[2].append(Point2.center(effect.positions))
            elif effect.id == EffectId.LURKERMP:
                # Each lurker spine deals splash damage to a radius of 0.5
                values = effect_dict.get(effect.id, (1000, 1, [], False))
                values[2].extend(effect.positions)

            if values is not None and effect.id not in effect_dict:
                effect_dict[effect.id] = values

        for effects in effect_dict.values():
            if effects[3]:
                self.map.add_both_influence(effects[2], effects[0], effects[1],
                                            effects[1])
            else:
                self.map.add_ground_influence(effects[2], effects[0],
                                              effects[1], effects[1])
Example #21
0
class TestUnits(unittest.TestCase):

    # @classmethod
    # def setUpClass(cls):
    #     pass

    # @classmethod
    # def tearDownClass(cls):
    #     pass

    def setUp(self):
        mock_proto_data1 = MockProtoData(tag=245346374,
                                         pos=Mock(x=-5, y=6, z=50),
                                         health=35,
                                         health_max=45,
                                         orders=[
                                             Mock(ability_id=AbilityId.ATTACK,
                                                  target_unit_tag=1337,
                                                  progress=1.0)
                                         ])
        mock_proto_data2 = MockProtoData(
            tag=245346375,
            pos=Mock(x=-2, y=7, z=50),
            orders=[
                Mock(ability_id=AbilityId.MOVE,
                     target_world_space_pos=Point2((0, 0)),
                     progress=1.0)
            ])
        mock_proto_data3 = MockProtoData(
            tag=245346376,
            pos=Mock(x=7, y=7, z=50),
        )
        self.mock_game_state = MockGameState()
        self.marine1 = Unit(mock_proto_data1, self.mock_game_state)
        self.marine2 = Unit(mock_proto_data2, self.mock_game_state)
        self.marine3 = Unit(mock_proto_data3, self.mock_game_state)
        self.marines = Units([self.marine1, self.marine2, self.marine3],
                             self.mock_game_state)
        self.emptyUnitsGroup = Units([], self.mock_game_state)

    def tearDown(self):
        # unnecessary here
        del self.marine1
        del self.marine2
        del self.marine3
        del self.marines
        del self.mock_game_state

    def test_amount(self):
        self.assertEqual(self.marines.amount, 3)
        self.assertEqual(self.emptyUnitsGroup.amount, 0)

    def test_empty(self):
        self.assertFalse(self.marines.empty)
        self.assertTrue(self.emptyUnitsGroup.empty)

    def test_exists(self):
        self.assertTrue(self.marines.exists)
        self.assertFalse(self.emptyUnitsGroup.exists)

    def test_find_by_tag(self):
        self.assertEqual(self.marines.find_by_tag(245346374), self.marine1)
        self.assertIsNone(self.marines.find_by_tag(245346))

    def test_first(self):
        self.assertEqual(self.marines.first, self.marine1)

    def test_random(self):
        self.assertTrue(
            self.marines.random in [self.marine1, self.marine2, self.marine3])

    def test_closest_distance_to(self):
        self.assertEqual(self.marines.closest_distance_to(Point2((10, 10))),
                         (3**2 + 3**2)**0.5)

    def test_closest_to(self):
        self.assertEqual(self.marines.closest_to(Point2((10, 10))),
                         self.marine3)

    def test_furthest_to(self):
        self.assertEqual(self.marines.furthest_to(Point2((10, 10))),
                         self.marine1)

    def test_closer_than(self):
        self.assertEqual(self.marines.closer_than(20, Point2((10, 10))),
                         self.marines)
        self.assertEqual(self.marines.closer_than(6, Point2((10, 10))),
                         Units([self.marine3], self.mock_game_state))
        self.assertEqual(self.marines.closer_than(2, Point2((10, 10))),
                         self.emptyUnitsGroup)

    def test_tags_in(self):
        self.assertEqual(
            self.marines.tags_in({245346374, 245346375}),
            Units([self.marine1, self.marine2], self.mock_game_state))
        self.assertEqual(self.marines.tags_in({}), self.emptyUnitsGroup)

    def test_tags_not_in(self):
        self.assertEqual(self.marines.tags_not_in({}), self.marines)
        self.assertEqual(
            self.marines.tags_not_in({245346374}),
            Units([self.marine2, self.marine3], self.mock_game_state))

    def test_of_type(self):
        self.assertEqual(self.marines.of_type(UnitTypeId.MARINE), self.marines)
        self.assertEqual(self.marines.of_type([UnitTypeId.MARINE]),
                         self.marines)

    def test_exclude_type(self):
        self.assertEqual(self.marines.exclude_type([UnitTypeId.MARINE]),
                         self.emptyUnitsGroup)

    def test_tags(self):
        self.assertSetEqual(self.marines.tags, {u.tag for u in self.marines})

    def test_noqueue(self):
        self.assertEqual(self.marines.noqueue,
                         Units([self.marine3], self.mock_game_state))

    def test_idle(self):
        self.assertEqual(self.marines.idle,
                         Units([self.marine3], self.mock_game_state))

    def test_owned(self):
        self.assertEqual(self.marines.owned, self.marines)

    def test_enemy(self):
        self.assertEqual(self.marines.enemy, self.emptyUnitsGroup)
Example #22
0
    async def distribute_workers(self,
                                 performanceHeavy=True,
                                 onlySaturateGas=False):
        mineralTags = [x.tag for x in self.mineral_field]
        gas_buildingTags = [x.tag for x in self.gas_buildings]

        workerPool = Units([], self)
        workerPoolTags = set()

        # Find all gas_buildings that have surplus or deficit
        deficit_gas_buildings = {}
        surplusgas_buildings = {}
        for g in self.gas_buildings.filter(lambda x: x.vespene_contents > 0):
            # Only loop over gas_buildings that have still gas in them
            deficit = g.ideal_harvesters - g.assigned_harvesters
            if deficit > 0:
                deficit_gas_buildings[g.tag] = {"unit": g, "deficit": deficit}
            elif deficit < 0:
                surplusWorkers = self.workers.closer_than(10, g).filter(
                    lambda w: w not in workerPoolTags and len(w.orders) == 1
                    and w.orders[0].ability.id in [AbilityId.HARVEST_GATHER]
                    and w.orders[0].target in gas_buildingTags)
                for i in range(-deficit):
                    if surplusWorkers.amount > 0:
                        w = surplusWorkers.pop()
                        workerPool.append(w)
                        workerPoolTags.add(w.tag)
                surplusgas_buildings[g.tag] = {"unit": g, "deficit": deficit}

        # Find all townhalls that have surplus or deficit
        deficitTownhalls = {}
        surplusTownhalls = {}
        if not onlySaturateGas:
            for th in self.townhalls:
                deficit = th.ideal_harvesters - th.assigned_harvesters
                if deficit > 0:
                    deficitTownhalls[th.tag] = {"unit": th, "deficit": deficit}
                elif deficit < 0:
                    surplusWorkers = self.workers.closer_than(
                        10,
                        th).filter(lambda w: w.tag not in workerPoolTags and
                                   len(w.orders) == 1 and w.orders[0].ability.
                                   id in [AbilityId.HARVEST_GATHER] and w.
                                   orders[0].target in mineralTags)
                    # workerPool.extend(surplusWorkers)
                    for i in range(-deficit):
                        if surplusWorkers.amount > 0:
                            w = surplusWorkers.pop()
                            workerPool.append(w)
                            workerPoolTags.add(w.tag)
                    surplusTownhalls[th.tag] = {"unit": th, "deficit": deficit}

            if all([
                    len(deficit_gas_buildings) == 0,
                    len(surplusgas_buildings) == 0,
                    len(surplusTownhalls) == 0 or deficitTownhalls == 0,
            ]):
                # Cancel early if there is nothing to balance
                return

        # Check if deficit in gas less or equal than what we have in surplus, else grab some more workers from surplus bases
        deficitGasCount = sum(
            gasInfo["deficit"]
            for gasTag, gasInfo in deficit_gas_buildings.items()
            if gasInfo["deficit"] > 0)
        surplusCount = sum(-gasInfo["deficit"]
                           for gasTag, gasInfo in surplusgas_buildings.items()
                           if gasInfo["deficit"] < 0)
        surplusCount += sum(-thInfo["deficit"]
                            for thTag, thInfo in surplusTownhalls.items()
                            if thInfo["deficit"] < 0)

        if deficitGasCount - surplusCount > 0:
            # Grab workers near the gas who are mining minerals
            for gTag, gInfo in deficit_gas_buildings.items():
                if workerPool.amount >= deficitGasCount:
                    break
                workersNearGas = self.workers.closer_than(
                    10, gInfo["unit"]).filter(
                        lambda w: w.tag not in workerPoolTags and len(
                            w.orders) == 1 and w.orders[0].ability.id in [
                                AbilityId.HARVEST_GATHER
                            ] and w.orders[0].target in mineralTags)
                while workersNearGas.amount > 0 and workerPool.amount < deficitGasCount:
                    w = workersNearGas.pop()
                    workerPool.append(w)
                    workerPoolTags.add(w.tag)

        # Now we should have enough workers in the pool to saturate all gases, and if there are workers left over, make them mine at townhalls that have mineral workers deficit
        for gTag, gInfo in deficit_gas_buildings.items():
            if performanceHeavy:
                # Sort furthest away to closest (as the pop() function will take the last element)
                workerPool.sort(key=lambda x: x.distance_to(gInfo["unit"]),
                                reverse=True)
            for i in range(gInfo["deficit"]):
                if workerPool.amount > 0:
                    w = workerPool.pop()
                    if len(w.orders) == 1 and w.orders[0].ability.id in [
                            AbilityId.HARVEST_RETURN
                    ]:
                        w.gather(gInfo["unit"], queue=True)
                    else:
                        w.gather(gInfo["unit"])

        if not onlySaturateGas:
            # If we now have left over workers, make them mine at bases with deficit in mineral workers
            for thTag, thInfo in deficitTownhalls.items():
                if performanceHeavy:
                    # Sort furthest away to closest (as the pop() function will take the last element)
                    workerPool.sort(
                        key=lambda x: x.distance_to(thInfo["unit"]),
                        reverse=True)
                for i in range(thInfo["deficit"]):
                    if workerPool.amount > 0:
                        w = workerPool.pop()
                        mf = self.mineral_field.closer_than(
                            10, thInfo["unit"]).closest_to(w)
                        if len(w.orders) == 1 and w.orders[0].ability.id in [
                                AbilityId.HARVEST_RETURN
                        ]:
                            w.gather(mf, queue=True)
                        else:
                            w.gather(mf)
Example #23
0
 def all_from_task(self, task: Union[int, UnitTask]) -> Units:
     return Units(self.roles[task].units, self.ai)
Example #24
0
class WorkerManager:
    """ Responsible for managing the workers """

    GATHER_RANGE = 1.4
    MINERAL_POP_RANGE_MAX = 0.2
    MINERAL_POP_RANGE_MIN = 0.001

    def __init__(self, bot: CheatMoney, minerals):
        self.bot = bot
        self.minerals = Units(minerals, self.bot)
        self.workers = Units([], self.bot)

        for mineral in self.minerals:
            mineral.workers_assigned = 0

        print(f'PATH_UNTIL_RANGE: {self.GATHER_RANGE}')

    async def add(self, worker: Unit):
        self.workers.append(worker)

        # assign workers to each mineral patch
        # prioritize the closest minerals. maximum 2 workers per patch
        for mineral in self.minerals.sorted_by_distance_to(worker):
            if mineral.workers_assigned < 2:

                # if there is already a worker assigned to this patch, assign our worker partners
                if mineral.workers_assigned == 1:
                    for worker_partner in self.workers:
                        if hasattr(
                                worker_partner, 'assigned_mineral'
                        ) and worker_partner.assigned_mineral == mineral:
                            worker.worker_partner = worker_partner
                            worker_partner.worker_partner = worker

                worker.assigned_mineral = mineral
                mineral.workers_assigned += 1
                break

    async def on_step(self, iteration):
        for worker in self.workers:
            # for some reason the work in our list doesn't get its data updated, so we need to get this one
            updated_worker = self.bot.workers.find_by_tag(worker.tag)
            if updated_worker.is_carrying_minerals:  # if worker has minerals, return to base
                # check for mineral popping opportunity
                if hasattr(worker, 'worker_partner') \
                    and self.in_mineral_pop_range(worker) \
                    and self.on_correct_side_of_partner(worker)\
                    and updated_worker.distance_to(self.bot.hq_location) > 4:
                    self.bot.do(updated_worker.move(self.bot.hq_location))
                else:
                    self.bot.do(updated_worker.return_resource())
            # if the worker is over a certain distance away, path to mineral patch
            elif updated_worker.distance_to(
                    worker.assigned_mineral.position) > self.GATHER_RANGE:
                pos = updated_worker.position - self.bot.hq_location
                norm = preprocessing.normalize([pos], norm='l1')[0]
                self.bot.do(
                    updated_worker.move(worker.assigned_mineral.position -
                                        Point2((norm[0], norm[1]))))
            # if the worker is in range to gather, issue a gather command
            else:
                self.bot.do(updated_worker.gather(worker.assigned_mineral))

    def in_mineral_pop_range(self, worker):
        # for some reason the work in our list doesn't get its data updated, so we need to get this one
        updated_worker = self.bot.workers.find_by_tag(worker.tag)
        updated_worker_partner = self.bot.workers.find_by_tag(
            worker.worker_partner.tag)
        pos = updated_worker.position - updated_worker_partner.position
        range = math.hypot(pos[0], pos[1])
        return range < self.MINERAL_POP_RANGE_MAX and range > self.MINERAL_POP_RANGE_MIN

    def on_correct_side_of_partner(self, worker):
        # for some reason the work in our list doesn't get its data updated, so we need to get this one
        updated_worker = self.bot.workers.find_by_tag(worker.tag)
        updated_worker_partner = self.bot.workers.find_by_tag(
            worker.worker_partner.tag)
        return updated_worker_partner.distance_to(
            worker.assigned_mineral.position) < updated_worker.distance_to(
                worker.assigned_mineral.position)
Example #25
0
 def __init__(self, task: UnitTask, cache: UnitCacheManager, ai: BotAI):
     self.task = task
     self.units: Units = Units([], ai)
     self.tags: List[int] = []
     self.cache = cache
 def free_workers(self) -> Units:
     """ Free workers, ie. gathering minerals or gas, or idling, and not dedicated to defending or scouting."""
     units: Units = Units(self.roles[UnitTask.Idle.value].units, self.ai)
     units.extend(self.roles[UnitTask.Gathering.value].units)
     # Mules should not count for workers
     return units.of_type([UnitTypeId.DRONE, UnitTypeId.PROBE, UnitTypeId.SCV])
Example #27
0
    async def attack_units(self, iteration, units, limitToAttack):
        # airUnits = units.filter(lambda unit: unit.can_attack_air)
        targets = (
            self.enemy_units
            | self.enemy_structures).filter(lambda unit: unit.can_be_attacked)
        supectsTargets: Units = Units(variaveis.listEnemyTargettargted, self)
        targetsEnemyCanAttack = targets.filter(lambda unit: unit.can_attack)
        targetToMove = ""
        if supectsTargets:
            targetToMove = supectsTargets.closest_to(
                self.enemy_start_locations[0]).position

        for warrior in units:

            enemyCloserThan20 = targets.closer_than(25, warrior)
            unitsCloserThan10 = units.closer_than(
                int(variaveis.warriorsAttack * 0.7), warrior)
            #Abilitys
            if targetsEnemyCanAttack.closer_than(10, warrior):
                # print(warrior.name)
                if warrior.type_id == UnitTypeId.SENTRY:
                    warrior(AbilityId.GUARDIANSHIELD_GUARDIANSHIELD)
                if warrior.type_id == UnitTypeId.STALKER:
                    warrior(AbilityId.EFFECT_BLINK_STALKER,
                            targetsEnemyCanAttack.closest_to(warrior).position)
                if warrior.type_id == UnitTypeId.VOIDRAY:
                    warrior(AbilityId.EFFECT_VOIDRAYPRISMATICALIGNMENT)
        #Verificando hp e shield
            if warrior.health_percentage > 0.8 or warrior.shield_percentage == 1 or variaveis.gameState == variaveis.atacarState or warrior.shield_health_percentage < 0.5:
                #decisão de alvo
                # full push
                if variaveis.gameState == variaveis.atacarState:
                    targets = (self.enemy_units | self.enemy_structures
                               ).filter(lambda unit: unit.can_be_attacked)
                    targetsEnemyCanAttack = targets.filter(
                        lambda unit: unit.can_attack)
                    if targetsEnemyCanAttack:
                        warrior.attack(
                            targetsEnemyCanAttack.closest_to(warrior))
                    elif targets:
                        warrior.attack(targets.closest_to(warrior))
                    elif variaveis.hunting:
                        warrior.scan_move(targetToMove)
                    else:
                        warrior.attack(self.enemy_start_locations[0])
                        if warrior.distance_to(
                                self.enemy_start_locations[0]) < 5:
                            variaveis.hunting = True

                # ataque seguro
                elif targets:
                    targetsEnemyCanAttack = targets.filter(
                        lambda unit: unit.can_attack)
                    if targetsEnemyCanAttack:
                        warrior.attack(
                            targetsEnemyCanAttack.closest_to(warrior))
                    elif enemyCloserThan20.closer_than(8, warrior).amount > 0:
                        warrior.attack(targets.closest_to(warrior))
                    elif enemyCloserThan20.amount == 0 and targets.closer_than(
                            5, warrior):
                        warrior.attack(
                            self.enemy_structures.closest_to(warrior))
                    elif enemyCloserThan20.amount * 0.7 <= unitsCloserThan10.amount:
                        warrior.attack(targets.closest_to(warrior))
                        # print("tem gente atacando")
                    elif enemyCloserThan20.amount * 0.3 > unitsCloserThan10.amount:
                        furthesWarrior = units.furthest_to(
                            targets.closest_to(warrior))
                        warrior.move(furthesWarrior)
                    elif enemyCloserThan20.amount == 0:
                        warriorsClosestEnenmy = units.closest_to(
                            targets.closest_to(warrior))
                        warrior.move(warriorsClosestEnenmy)
                # elif self.enemy_units.closer_than(15,warrior).amount > 0:
                #     warrior.attack(self.enemy_units.closest_to(warrior))
                # elif targets.closer_than(15,warrior).amount > 0:
                #     warrior.attack(targets.closest_to(warrior))
                elif units.amount >= limitToAttack and unitsCloserThan10.amount >= limitToAttack * 0.5:
                    if targets:
                        if unitsCloserThan10.amount >= targets.closer_than(
                                10, warrior).amount * 0.6:
                            warrior.attack(targets.closest_to(warrior))
                        else:
                            furthesWarrior = units.furthest_to(
                                targetsEnemyCanAttack.closest_to(warrior))
                            warrior.move(furthesWarrior)
                    elif variaveis.hunting and targetToMove:
                        # print("Scann")
                        warrior.scan_move(targetToMove)
                    else:
                        warrior.attack(self.enemy_start_locations[0])
                        if warrior.distance_to(
                                self.enemy_start_locations[0]) < 5:
                            variaveis.hunting = True
                else:
                    targetsHome = (self.townhalls
                                   | self.structures(UnitTypeId.PYLON))
                    targettoMove = self.start_location
                    # if self.structures(UnitTypeId.PYLON).amount>0:
                    #     targettoMove = self.structures(UnitTypeId.PYLON).closest_to(self.enemy_start_locations)
                    if targetsHome:
                        targettoMove = targetsHome.closest_to(
                            self.enemy_start_locations[0])
                        if targets:
                            target = targets.closest_to(warrior)
                            targettoMove = targetsHome.closest_to(target)

                        warrior.move(targettoMove)
                    else:
                        warrior.attack(self.enemy_start_locations[0])
            else:
                # if units.furthest_to(targetsEnemyCanAttack.closest_to(warrior)):
                #     safePlace = units.furthest_to(targetsEnemyCanAttack.closest_to(warrior))
                if variaveis.forte2 != "":
                    safePlace = variaveis.forte2
                elif variaveis.forte1 != "":
                    safePlace = variaveis.forte1
                else:
                    safePlace = (self.townhalls
                                 | self.structures(UnitTypeId.PYLON))
                if warrior.distance_to(safePlace) < 10:
                    warrior.attack(safePlace)
                else:
                    warrior.move(safePlace)

        return
Example #28
0
 def all_from_task(self, task: UnitTask) -> Units:
     return Units(self.roles[task.value].units, self.ai)
Example #29
0
    def __init__(self, response_observation):
        self.response_observation = response_observation
        self.actions = response_observation.actions  # successful actions since last loop
        self.action_errors = response_observation.action_errors  # error actions since last loop

        # https://github.com/Blizzard/s2client-proto/blob/51662231c0965eba47d5183ed0a6336d5ae6b640/s2clientprotocol/sc2api.proto#L575
        # TODO: implement alerts https://github.com/Blizzard/s2client-proto/blob/51662231c0965eba47d5183ed0a6336d5ae6b640/s2clientprotocol/sc2api.proto#L640
        self.observation = response_observation.observation
        self.observation_raw = self.observation.raw_data
        self.dead_units: Set[
            int] = self.observation_raw.event.dead_units  # returns set of tags of units that died
        self.alerts = self.observation.alerts
        self.player_result = response_observation.player_result
        self.chat = response_observation.chat
        self.common: Common = Common(self.observation.player_common)

        # Area covered by Pylons and Warpprisms
        self.psionic_matrix: PsionicMatrix = PsionicMatrix.from_proto(
            self.observation_raw.player.power_sources)
        self.game_loop: int = self.observation.game_loop  # 22.4 per second on faster game speed

        # https://github.com/Blizzard/s2client-proto/blob/33f0ecf615aa06ca845ffe4739ef3133f37265a9/s2clientprotocol/score.proto#L31
        self.score: ScoreDetails = ScoreDetails(self.observation.score)
        self.abilities = self.observation.abilities  # abilities of selected units

        self._blipUnits = []
        self.own_units: Units = Units([])
        self.enemy_units: Units = Units([])
        self.mineral_field: Units = Units([])
        self.vespene_geyser: Units = Units([])
        self.resources: Units = Units([])
        self.destructables: Units = Units([])
        self.watchtowers: Units = Units([])
        self.units: Units = Units([])

        for unit in self.observation.raw_data.units:
            if unit.is_blip:
                self._blipUnits.append(unit)
            else:
                unit_obj = Unit(unit)
                self.units.append(unit_obj)
                alliance = unit.alliance
                # Alliance.Neutral.value = 3
                if alliance == 3:
                    unit_type = unit.unit_type
                    # XELNAGATOWER = 149
                    if unit_type == 149:
                        self.watchtowers.append(unit_obj)
                    # mineral field enums
                    elif unit_type in mineral_ids:
                        self.mineral_field.append(unit_obj)
                        self.resources.append(unit_obj)
                    # geyser enums
                    elif unit_type in geyser_ids:
                        self.vespene_geyser.append(unit_obj)
                        self.resources.append(unit_obj)
                    # all destructable rocks
                    else:
                        self.destructables.append(unit_obj)
                # Alliance.Self.value = 1
                elif alliance == 1:
                    self.own_units.append(unit_obj)
                # Alliance.Enemy.value = 4
                elif alliance == 4:
                    self.enemy_units.append(unit_obj)
        self.upgrades: Set[UpgradeId] = {
            UpgradeId(upgrade)
            for upgrade in self.observation_raw.player.upgrade_ids
        }

        # Set of unit tags that died this step
        self.dead_units: Set[int] = {
            dead_unit_tag
            for dead_unit_tag in self.observation_raw.event.dead_units
        }
        # Set of enemy units detected by own sensor tower, as blips have less unit information than normal visible units
        self.blips: Set[Blip] = {Blip(unit) for unit in self._blipUnits}
        # self.visibility[point]: 0=Hidden, 1=Fogged, 2=Visible
        self.visibility: PixelMap = PixelMap(
            self.observation_raw.map_state.visibility, mirrored=True)
        # HSPARK 수정 시작
        # self.creep[point]: 0=No creep, 1=creep
        # self.creep: PixelMap = PixelMap(self.observation_raw.map_state.creep, mirrored=True)
        self.creep: PixelMap = PixelMap(self.observation_raw.map_state.creep,
                                        mirrored=True,
                                        in_bits=True)
        # HSPARK 수정 끝

        # Effects like ravager bile shot, lurker attack, everything in effect_id.py
        self.effects: Set[EffectData] = {
            EffectData(effect)
            for effect in self.observation_raw.effects
        }
        """ Usage: