def _defenders_from(self, task: UnitTask, current_power: ExtendedPower, position: Point2, power: ExtendedPower, units: Units): """ Get defenders from a task. """ if current_power.is_enough_for(power): return exclude_types = [] exclude_types.append(UnitTypeId.OVERSEER) exclude_types.extend(self.knowledge.unit_values.worker_types) exclude_types.extend(self.peace_unit_types) role_units = self.roles[task.value].units\ .exclude_type(exclude_types) unit: Unit for unit in role_units.sorted_by_distance_to(position): enough_air_power = current_power.air_power >= power.air_presence * 1.1 enough_ground_power = current_power.ground_power >= power.ground_presence * 1.1 if not self.unit_values.can_shoot_air(unit) and not enough_air_power and enough_ground_power: # Don't pull any more units that can't actually shoot the targets continue if not self.unit_values.can_shoot_ground(unit, self.knowledge) and enough_air_power and not enough_ground_power: # Don't pull any more units that can't actually shoot the targets continue current_power.add_unit(unit) units.append(unit) if current_power.is_enough_for(power): return return
def _should_attack(self, power: ExtendedPower) -> bool: if self.attack_on_advantage: if ((self.game_analyzer.our_army_predict in at_least_clear_advantage and self.game_analyzer.our_income_advantage in at_least_small_disadvantage) or (self.game_analyzer.our_army_predict in at_least_small_advantage and self.game_analyzer.our_income_advantage in at_least_clear_disadvantage)): # Our army is bigger but economy is weaker, attack! return True if ((self.game_analyzer.our_army_predict in at_least_small_disadvantage and self.game_analyzer.our_income_advantage in at_least_clear_advantage) or (self.game_analyzer.our_army_predict in at_least_clear_disadvantage and self.game_analyzer.our_income_advantage in at_least_small_advantage)): # Our army is smaller but economy is better, focus on defence! return False enemy_total_power: ExtendedPower = self.knowledge.enemy_units_manager.enemy_total_power enemy_total_power.multiply(ENEMY_TOTAL_POWER_MULTIPLIER) multiplier = ENEMY_TOTAL_POWER_MULTIPLIER zone_count = 0 for zone in self.knowledge.expansion_zones: # type: Zone if zone.is_enemys: zone_count += 1 enemy_main: Zone = self.knowledge.expansion_zones[-1] enemy_natural: Zone = self.knowledge.expansion_zones[-2] if zone_count == 1 and enemy_main.is_enemys: # We should seriously consider whether we want to crash and burn against a one base defense enemy_total_power.add_units(enemy_main.enemy_static_defenses) #multiplier *= 2 elif zone_count == 2 and enemy_natural.is_enemys: enemy_total_power.add_units(enemy_natural.enemy_static_defenses) # if (self.knowledge.enemy_race == Race.Terran # and self.knowledge.enemy_units_manager.unit_count(UnitTypeId.SIEGETANK) > 1): # multiplier = 1.6 enemy_total_power.power = max(self.start_attack_power, enemy_total_power.power) if power.is_enough_for(enemy_total_power, 1 / multiplier): self.print(f"Power {power.power:.2f} is larger than required attack power {enemy_total_power.power:.2f} -> attack!") return True if self.ai.supply_used > 190: self.print(f"Supply is {self.ai.supply_used} -> attack!") return True return False
async def execute(self) -> bool: unit: Unit all_defenders = self.knowledge.roles.all_from_task(UnitTask.Defending) for i in range(0, len(self.knowledge.expansion_zones)): zone: 'Zone' = self.knowledge.expansion_zones[i] zone_tags = self.defender_tags[i] zone_defenders_all = all_defenders.tags_in(zone_tags) zone_worker_defenders = zone_defenders_all(self.worker_type) zone_defenders = zone_defenders_all.exclude_type(self.worker_type) enemies = zone.known_enemy_units # Let's loop zone starting from our main, which is the one we want to defend the most # Check that zone is either in our control or is our start location that has no Nexus if zone_defenders.exists or zone.is_ours or zone == self.knowledge.own_main_zone: if not self.defense_required(enemies): # Delay before removing defenses in case we just lost visibility of the enemies if zone.last_scouted_center == self.knowledge.ai.time \ or self.zone_seen_enemy[i] + PlanZoneDefense.ZONE_CLEAR_TIMEOUT < self.ai.time: self.knowledge.roles.clear_tasks(zone_defenders_all) zone_defenders.clear() zone_tags.clear() continue # Zone is well under control. else: self.zone_seen_enemy[i] = self.ai.time if enemies.exists: # enemy_center = zone.assaulting_enemies.center enemy_center = enemies.closest_to( zone.center_location).position elif zone.assaulting_enemies: enemy_center = zone.assaulting_enemies.closest_to( zone.center_location).position else: enemy_center = zone.gather_point defense_required = ExtendedPower(self.unit_values) defense_required.add_power(zone.assaulting_enemy_power) defense_required.multiply(1.5) defenders = ExtendedPower(self.unit_values) for unit in zone_defenders: self.combat.add_unit(unit) defenders.add_unit(unit) # Add units to defenders that are being warped in. for unit in self.knowledge.roles.units( UnitTask.Idle).not_ready: if unit.distance_to(zone.center_location) < zone.radius: # unit is idle in the zone, add to defenders self.combat.add_unit(unit) self.knowledge.roles.set_task(UnitTask.Defending, unit) zone_tags.append(unit.tag) if not defenders.is_enough_for(defense_required): defense_required.substract_power(defenders) for unit in self.knowledge.roles.get_defenders( defense_required, zone.center_location): if unit.distance_to( zone.center_location) < zone.radius: # Only count units that are close as defenders defenders.add_unit(unit) self.knowledge.roles.set_task(UnitTask.Defending, unit) self.combat.add_unit(unit) zone_tags.append(unit.tag) if len(enemies) > 1 or (len(enemies) == 1 and enemies[0].type_id not in self.unit_values.worker_types): # Pull workers to defend only and only if the enemy isn't one worker scout if defenders.is_enough_for(defense_required): # Workers should return to mining. for unit in zone_worker_defenders: zone.go_mine(unit) if unit.tag in zone_tags: # Just in case, should be in zone tags always. zone_tags.remove(unit.tag) # Zone is well under control without worker defense. else: await self.worker_defence(defenders.power, defense_required, enemy_center, zone, zone_tags, zone_worker_defenders) self.combat.execute(enemy_center, MoveType.SearchAndDestroy) return True # never block
class GameAnalyzer(ManagerBase): def __init__(self): super().__init__() self._enemy_air_percentage = 0 self._our_income_advantage = 0 self._our_predicted_army_advantage = 0 self._our_predicted_tech_advantage = 0 self.enemy_gas_income = 0 self.enemy_mineral_income = 0 self.our_zones = 0 self.enemy_zones = 0 self.our_power: ExtendedPower = None self.enemy_power: ExtendedPower = None self.enemy_predict_power: ExtendedPower = None self.predicted_defeat_time = 0.0 self.minerals_left: List[int] = [] self.vespene_left: List[int] = [] self.resource_updater: IntervalFunc = None self._last_income: Advantage = Advantage.Even self._last_army: Advantage = Advantage.Even self._last_predict: Advantage = Advantage.Even async def start(self, knowledge: 'Knowledge'): await super().start(knowledge) self.enemy_predicter: EnemyArmyPredicter = knowledge.enemy_army_predicter self.our_power = ExtendedPower(self.unit_values) self.enemy_power: ExtendedPower = ExtendedPower(self.unit_values) self.enemy_predict_power: ExtendedPower = ExtendedPower( self.unit_values) self.resource_updater = IntervalFunc(self.ai, self.save_resources_status, 1) self.resource_updater.execute() def save_resources_status(self): self.minerals_left.append(self.ai.minerals) self.vespene_left.append(self.ai.vespene) async def update(self): self.resource_updater.execute() self.our_power.clear() self.our_zones = 0 self.enemy_zones = 0 our_income = self.knowledge.income_calculator.mineral_income + self.knowledge.income_calculator.gas_income if self.knowledge.my_worker_type is None: # random and we haven't seen enemy race yat enemy_workers = 12 else: enemy_workers = self.knowledge.enemy_units_manager.enemy_worker_count if not self.knowledge.enemy_main_zone.is_scouted_at_least_once: enemy_workers += 12 mineral_fields = 0 for zone in self.knowledge.zone_manager.expansion_zones: # type: Zone if zone.is_enemys: self.enemy_zones += 1 mineral_fields += len(zone.mineral_fields) if zone.is_ours: self.our_zones += 1 built_vespene = len(self.cache.enemy(self.unit_values.gas_miners)) self._enemy_gas_income = min(enemy_workers, built_vespene * 3) * GAS_MINE_RATE workers_on_minerals = min(mineral_fields * 2, enemy_workers - built_vespene * 3) workers_on_minerals = max(0, workers_on_minerals) self.enemy_mineral_income = workers_on_minerals enemy_income = self.enemy_mineral_income + self._enemy_gas_income self._our_income_advantage = our_income - enemy_income self.our_power.add_units( self.ai.units.filter(lambda u: u.is_ready and u.type_id != self. knowledge.my_worker_type)) self.enemy_predict_power = self.enemy_predicter.predicted_enemy_power self.enemy_power = self.enemy_predicter.enemy_power self._enemy_air_percentage = 0 if self.enemy_predict_power.air_presence > 0: self._enemy_air_percentage = self.enemy_predict_power.air_power / self.enemy_predict_power.power being_defeated = self.predicting_defeat if being_defeated and self.predicted_defeat_time == 0.0: self.predicted_defeat_time = self.ai.time elif not being_defeated and self.predicted_defeat_time != 0.0: self.predicted_defeat_time = 0 income = self._calc_our_income_advantage() army = self._calc_our_army_advantage() predict = self._calc_our_army_predict() if self._last_income != income: self.print(f'Income advantage is now {income.name}') if self._last_army != army: self.print(f'Known army advantage is now {army.name}') if self._last_predict != predict: self.print(f'Predicted army advantage is now {predict.name}') self._last_income = income self._last_army = army self._last_predict = predict async def post_update(self): if self.debug: msg = f"Our income: {self.knowledge.income_calculator.mineral_income} / {round(self.knowledge.income_calculator.gas_income)}" msg += f"\nEnemy income: {self.enemy_mineral_income} / {round(self.enemy_gas_income)}" msg += f"\nResources: {round(self._our_income_advantage)}+{self.our_zones - self.enemy_zones}" \ f" ({self.our_income_advantage.name})" msg += f"\nArmy: {round(self.our_power.power)} vs" \ f" {round(self.enemy_power.power)} ({self.our_army_advantage.name})" msg += f"\nArmy predict: {round(self.our_power.power)} vs" \ f" {round(self.enemy_predict_power.power)} ({self.our_army_predict.name})" msg += f"\nEnemy air: {self.enemy_air.name}" self.client.debug_text_2d(msg, Point2((0.4, 0.15)), None, 14) @property def our_income_advantage(self) -> Advantage: return self._last_income def _calc_our_income_advantage(self) -> Advantage: number = self._our_income_advantage + (self.our_zones - self.enemy_zones) * 10 if number > 40: return Advantage.OverwhelmingAdvantage if number < -40: return Advantage.OverwhelmingDisadvantage if number > 20: return Advantage.ClearAdvantage if number < -20: return Advantage.ClearDisadvantage if number > 10: return Advantage.SmallAdvantage if number < -10: return Advantage.SmallDisadvantage if number > 5: return Advantage.SlightAdvantage if number < -5: return Advantage.SlightDisadvantage return Advantage.Even @property def army_at_least_clear_disadvantage(self) -> bool: return self.our_army_predict in at_least_clear_disadvantage @property def army_at_least_small_disadvantage(self) -> bool: return self.our_army_predict in at_least_small_disadvantage @property def army_at_least_clear_advantage(self) -> bool: return self.our_army_predict in at_least_clear_advantage @property def army_at_least_small_advantage(self) -> bool: return self.our_army_predict in at_least_small_advantage @property def army_at_least_advantage(self) -> bool: return self.our_army_predict in at_least_advantage @property def army_can_survive(self) -> bool: return self.our_army_predict not in at_least_small_disadvantage @property def predicting_victory(self) -> bool: return (self.our_army_predict == Advantage.OverwhelmingAdvantage and self.our_income_advantage == Advantage.OverwhelmingAdvantage) @property def bean_predicting_defeat_for(self) -> float: if self.predicted_defeat_time == 0: return 0 return self.ai.time - self.predicted_defeat_time @property def predicting_defeat(self) -> bool: return (self.our_army_predict == Advantage.OverwhelmingDisadvantage and (self.ai.supply_workers < 5 or self.our_income_advantage == Advantage.OverwhelmingDisadvantage)) @property def our_army_predict(self) -> Advantage: return self._last_predict def _calc_our_army_predict(self) -> Advantage: if self.our_power.is_enough_for(self.enemy_predict_power, our_percentage=1 / 1.1): if self.our_power.power > 20 and self.our_power.is_enough_for( self.enemy_predict_power, our_percentage=1 / 3): return Advantage.OverwhelmingAdvantage if self.our_power.power > 10 and self.our_power.is_enough_for( self.enemy_predict_power, our_percentage=1 / 2): return Advantage.ClearAdvantage if self.our_power.power > 5 and self.our_power.is_enough_for( self.enemy_predict_power, our_percentage=1 / 1.4): return Advantage.SmallAdvantage return Advantage.SlightAdvantage if self.enemy_predict_power.is_enough_for(self.our_power, our_percentage=1 / 1.1): if self.enemy_predict_power.power > 20 and self.enemy_predict_power.is_enough_for( self.our_power, our_percentage=1 / 3): return Advantage.OverwhelmingDisadvantage if self.enemy_predict_power.power > 10 and self.enemy_predict_power.is_enough_for( self.our_power, our_percentage=1 / 2): return Advantage.ClearDisadvantage if self.enemy_predict_power.power > 5 and self.enemy_predict_power.is_enough_for( self.our_power, our_percentage=1 / 1.4): return Advantage.SmallDisadvantage return Advantage.SlightDisadvantage return Advantage.Even @property def our_army_advantage(self) -> Advantage: return self._last_army def _calc_our_army_advantage(self) -> Advantage: if self.our_power.is_enough_for(self.enemy_power, our_percentage=1 / 1.1): if self.our_power.power > 20 and self.our_power.is_enough_for( self.enemy_power, our_percentage=1 / 3): return Advantage.OverwhelmingAdvantage if self.our_power.power > 10 and self.our_power.is_enough_for( self.enemy_power, our_percentage=1 / 2): return Advantage.ClearAdvantage if self.our_power.power > 5 and self.our_power.is_enough_for( self.enemy_power, our_percentage=1 / 1.4): return Advantage.SmallAdvantage return Advantage.SlightAdvantage if self.enemy_power.is_enough_for(self.our_power, our_percentage=1 / 1.1): if self.enemy_power.power > 20 and self.enemy_power.is_enough_for( self.our_power, our_percentage=1 / 3): return Advantage.OverwhelmingDisadvantage if self.enemy_power.power > 10 and self.enemy_power.is_enough_for( self.our_power, our_percentage=1 / 2): return Advantage.ClearDisadvantage if self.enemy_power.power > 5 and self.enemy_power.is_enough_for( self.our_power, our_percentage=1 / 1.4): return Advantage.SmallDisadvantage return Advantage.SlightDisadvantage return Advantage.Even @property def enemy_air(self) -> AirArmy: if self._enemy_air_percentage > 0.90: return AirArmy.AllAir if self._enemy_air_percentage > 0.65: return AirArmy.AlmostAllAir if self._enemy_air_percentage > 0.35: return AirArmy.Mixed if self._enemy_air_percentage > 0: return AirArmy.SomeAir return AirArmy.NoAir async def on_end(self, game_result: Result): own_types: List[UnitTypeId] = [] own_types_left: Dict[UnitTypeId, int] = {} enemy_types: List[UnitTypeId] = [] enemy_types_left: Dict[UnitTypeId, int] = {} lost_data = self.knowledge.lost_units_manager.get_own_enemy_lost_units( ) own_lost: Dict[UnitTypeId, List[Unit]] = lost_data[0] enemy_lost: Dict[UnitTypeId, List[Unit]] = lost_data[1] for unit_type, units in self.cache.own_unit_cache.items( ): # type: (UnitTypeId, Units) type_id = self.unit_values.real_type(unit_type) if type_id not in own_types: own_types.append(type_id) val = own_types_left.get(type_id, 0) own_types_left[type_id] = val + units.amount for unit_count in self.knowledge.enemy_units_manager.enemy_composition: # type: UnitCount unit_type = unit_count.enemy_type if unit_type not in enemy_types: enemy_types.append(unit_type) val = enemy_types_left.get(unit_type, 0) enemy_types_left[unit_type] = val + unit_count.count for unit_type, units in own_lost.items( ): # type: (UnitTypeId, List[Unit]) if unit_type not in own_types: own_types.append(unit_type) for unit_type, units in enemy_lost.items( ): # type: (UnitTypeId, List[Unit]) if unit_type not in enemy_types: enemy_types.append(unit_type) self.print_end("Own units:") self._print_by_type(own_types, own_lost, own_types_left) self.print_end("Enemy units:") self._print_by_type(enemy_types, enemy_lost, enemy_types_left) maxed_minerals = max(self.minerals_left) avg_minerals = sum(self.minerals_left) / len(self.minerals_left) maxed_gas = max(self.vespene_left) avg_gas = sum(self.vespene_left) / len(self.vespene_left) self.print_end( f'Minerals max {maxed_minerals} Average {round(avg_minerals)}') self.print_end(f'Vespene max {maxed_gas} Average {round(avg_gas)}') def _print_by_type(self, types: List[UnitTypeId], lost_units: Dict[UnitTypeId, List[Unit]], left_units: Dict[UnitTypeId, int]): def get_counts(unit_type: UnitTypeId) -> tuple: dead = len(lost_units.get(unit_type, [])) alive = left_units.get(unit_type, 0) total = dead + alive return total, alive, dead # Sort types by total count types = sorted(types, key=lambda t: get_counts(t)[0], reverse=True) for unit_type in types: counts = get_counts(unit_type) self.print_end(f"{str(unit_type.name).ljust(17)} " f"total: {str(counts[0]).rjust(3)} " f"alive: {str(counts[1]).rjust(3)} " f"dead: {str(counts[2]).rjust(3)} ") def print_end(self, msg: str): self.knowledge.print(msg, "GameAnalyzerEnd", stats=False)