Example #1
0
 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 #2
0
    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)
Example #3
0
    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
Example #4
0
class StrategicObjective():
    def __init__(self, module, urgency, rendezvous=None):
        self.module = module
        self.bot = module.bot
        self.status = ObjectiveStatus.ALLOCATING
        self.status_since = self.bot.time
        self.allocated = set()
        self.urgency = urgency
        self.rendezvous = rendezvous
        self.units = Units([], self.bot)
        self.last_seen = self.bot.time
        self.enemies = self.find_enemies()
        self.log = module.log.withFields({"objective": type(self).__name__})

    def __getattr__(self, name):
        return getattr(self.bot, name)

    @property
    def target(self):
        raise NotImplementedError(
            "You must extend this class and provide a target property")

    def is_complete(self):
        if self.time - self.last_seen > 5:
            self.log.info(
                "completed: enemies have not been seen for 5 seconds")
            if self.status == ObjectiveStatus.ACTIVE:
                # any enemies still in self.enemies should be removed from the enemy army
                # there probably aren't many but they are clearly interfering with us at this point
                self.log.info(
                    f"objective was active; clearing {self.enemies.amount} tags from known enemies!"
                )
                for enemy_tag in (e.tag for e in self.enemies):
                    self.shared.known_enemy_units.pop(enemy_tag, None)

            return True
        return False

    def find_enemies(self):
        return (Units(self.shared.known_enemy_units.values(), self.bot) + self.enemy_structures) \
          .filter(lambda u: u.is_visible or not self.is_visible(u.position))

    def abort(self):
        self.status = ObjectiveStatus.RETREATING
        self.status_since = self.time

    async def tick(self):
        self.log.info({
            "status": self.status,
            "num_units": self.units.amount,
            "num_enemies": self.enemies.amount,
            "game_time": self.time,
        })
        self.enemies = self.find_enemies()
        if self.enemy_units.tags_in(e.tag for e in self.enemies).exists:
            self.last_seen = self.time
        if self.status >= ObjectiveStatus.ALLOCATING:
            self.allocate()
        if self.status == ObjectiveStatus.STAGING:
            self.stage()
        if self.status == ObjectiveStatus.ACTIVE:
            await self.micro()
        if self.status == ObjectiveStatus.RETREATING:
            await self.retreat()

    # retreat requires implementation in subclasses
    # [ Really tricky on defense. For now, victory or death! ]
    async def retreat(self):
        await self.micro()

    def stage(self):
        # override this if you want to stage units
        self.status = ObjectiveStatus.ACTIVE
        self.status_since = self.time

    def retreat_unit(self, unit, target):
        if unit.ground_range >= 5 and unit.weapon_cooldown == 0:
            self.do(unit.attack(target))
        else:
            self.do(unit.move(target))

    def minimum_supply(self, enemy_units):
        return 0

    def optimum_supply(self, enemy_units):
        return sum(
            supply_cost(unit) for unit in self.bot.units.filter(
                lambda u: u.ground_dps + u.air_dps > 5 and not is_worker(u)))

    def do_attack(self, unit):
        self.do(
            unit.attack(
                self.enemies.closest_to(self.target.position).position if self.
                enemies.exists else self.target.position))

    async def micro(self):
        self.rendezvous = None
        if self.units.empty:
            return

        if self.time - self.status_since > 2:
            self.status_since = self.time
            for unit in self.units:
                self.do_attack(unit)

        near_target_units = self.units.closer_than(15, self.target)
        cooling_down_units = near_target_units.filter(
            lambda u: u.weapon_cooldown > 0)
        if cooling_down_units.amount < near_target_units.amount / 3:
            for unit in cooling_down_units:
                self.do(unit.move(unit.position.towards(self.target, 2)))
                self.do(unit.attack(self.target.position, queue=True))

        nearby_enemies = Units(
            list({
                enemy_unit
                for (friendly_unit,
                     enemy_unit) in itertools.product(self.units, self.enemies)
                if enemy_unit.position.is_closer_than(10, friendly_unit)
            }), self.bot)
        if nearby_enemies.exists:
            allies_center = median_position([u.position for u in self.units])
            clustered_allies = self.units.closer_than(15, allies_center)
            if optimism(clustered_allies,
                        self.enemies) < 0.75 and self.supply_used < 180:
                self.status = ObjectiveStatus.RETREATING
                self.status_since = self.time
        return

    def allocate(self):
        may_proceed = False
        enemy_units = self.enemies
        minimum_supply = int(self.minimum_supply(enemy_units))
        optimum_supply = int(self.optimum_supply(enemy_units))
        allocated_supply = int(
            sum(supply_cost(u) for u in self.units.tags_in(self.allocated)))

        if minimum_supply <= allocated_supply:
            may_proceed = True

        still_needed = minimum_supply - allocated_supply
        still_wanted = max(optimum_supply - allocated_supply, still_needed)
        usable_units = self.unallocated(
            urgency=self.urgency).filter(lambda u: not is_worker(u))
        if enemy_units.filter(lambda e: not e.is_flying).empty:
            usable_units = usable_units.filter(lambda u: u.can_attack_air)
        preferred_units = usable_units.filter(
            lambda u: u.can_attack_both
        ) if usable_units.exists else usable_units
        adding_units = set()

        if sum(supply_cost(u) for u in preferred_units) >= still_needed:
            # still_wanted is actually supply, not units, so this will over-select for protoss and under-select for zerg
            adding_units = set(unit.tag
                               for unit in preferred_units.closest_n_units(
                                   self.target.position, still_wanted))
        elif sum(supply_cost(u) for u in usable_units) >= still_needed:
            adding_units = set(preferred_units)
            adding_units.update((unit.tag
                                 for unit in usable_units.closest_n_units(
                                     self.target.position, still_wanted)))

        self.deallocate(adding_units)
        self.allocated = self.allocated.union(adding_units)

        if sum(supply_cost(u)
               for u in self.units.tags_in(self.allocated)) >= minimum_supply:
            may_proceed = True

        if may_proceed:
            if self.status == ObjectiveStatus.ALLOCATING:
                self.status = ObjectiveStatus.STAGING
                self.status_since = self.time

            elif self.status == ObjectiveStatus.RETREATING and \
              self.shared.optimism > 1.5 and \
              self.units.exists and \
              self.units.closer_than(15, median_position([u.position for u in self.units])).amount > self.units.amount / 2:
                self.status = ObjectiveStatus.STAGING
                self.status_since = self.time

        self.units = self.bot.units.tags_in(self.allocated)

        if len(adding_units) > 0:
            self.log.debug({
                "message": "Allocating units",
                "quantity": len(adding_units),
                "now_allocated": len(self.allocated),
            })
        # noisy, but possibly informative
        # self.log.info(f"{self.units.amount} units allocated for {self.enemies.amount} known enemies")
        return
Example #5
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)