def __best_building_site_farm(self, ai_stat: AI_GameStatus) -> BuildOption: """building sites get scored by how many buildable fields there are next to it""" best_score = -1 best_site = (-1, -1) best_cd: Optional[List[CardinalDirection]] = None best_tl = None fields = [] p = Priority.P_NO is_next_to_res = False # just for printing if ai_stat.me.resources >= ai_stat.cost_building_construction[BuildingType.FARM]: for ai_t in ai_stat.map.buildable_tiles: tmp = False # just for printing possible_fields = [] score = 0 for n in essentials.get_neighbours_on_set(ai_t, ai_stat.map.buildable_tiles): if basic.num_resources_on_adjacent(n) == 0: possible_fields.append(n) if essentials.is_obj_in_list(n, ai_stat.map.scoutable_tiles): score += 1 score += len(possible_fields) amount_of_fields = min(3, len(possible_fields)) sampled = random.sample(possible_fields, amount_of_fields) score += len(essentials.get_neighbours_on_set(ai_t, ai_stat.map.scoutable_tiles)) / 2 # if build site is next to a resource --> reduce value by 1 for each resource field score = score - basic.num_resources_on_adjacent(ai_t) # make the score dependent on safety level of region #score = score - self.compass.get_threat_level(ai_t).value * 2 if ai_t in self.danger_zone: score += - 10 if best_score < score: best_score = score is_next_to_res = tmp best_site = ai_t.offset_coordinates best_cd = self.compass.get_cardinal_direction_obj(ai_t, self.compass.center_tile) best_tl = self.compass.get_threat_level(ai_t) fields.clear() for s in sampled: fields.append(s.offset_coordinates) if is_next_to_res: debug("The farm will be next to a resource (apparently there is no better spot)") # translate score to priority (normalization step) if len(fields) >= 3: p = Priority.P_HIGH elif len(fields) >= 2: p = Priority.P_MEDIUM elif len(fields) >= 1: p = Priority.P_LOW # hint(f" type eval: {type(p)}") return BuildOption(BuildingType.FARM, best_site, fields, p, cardinal_direction=best_cd, threat_level=best_tl)
def evasive_movement(current_tile: Tile, target_tile: Tile, domain: List[Tile]) -> Tuple[Optional[Tile], int]: """ This movement calculates the next step which maximizes the distance to the target_tile Use with method to avoid collision between the entity placed on the current_tile, with a potentially hostile entity located on the target_tile :param current_tile: tile of the evading entity :param target_tile: tile of the hostile entity :param domain: the search domain, typically a subset of the walkable tiles :return: None, if no path is found, otherwise the next step and the distance of the resulting distance to target """ if not current_tile: error("current tile is None") return None, -1 if not target_tile: error("target tile is None") return None, -1 longest_path: Tuple[int, Optional[Tile]] = (-1, None) for nei in essentials.get_neighbours_on_set(current_tile, domain): step, dist = next_step_to_target(nei, target_tile, domain) if step: if dist > longest_path[0]: longest_path = (dist, nei) return longest_path[1], longest_path[0]
def __count_inactive_huts(self, ai_stat) -> int: count = 0 for b in ai_stat.map.building_list: if b.type == BuildingType.HUT: has_res = False for n in essentials.get_neighbours_on_set(b.base_tile, ai_stat.map.discovered_tiles): if n.has_resource(): has_res = True if not has_res: count += 1 return count
def evaluate_move_recruit_unit( self, ai_stat: AI_GameStatus ) -> Union[None, RaiseArmyOption, RecruitmentOption]: if len(ai_stat.map.army_list) == 0: for b in ai_stat.map.building_list: nei = essentials.get_neighbours_on_set( b, ai_stat.map.walkable_tiles ) # buildable -> to avoid opp armies if len(nei) == 0: continue x = random.sample(nei, 1)[0] return RaiseArmyOption(x.offset_coordinates, Priority.P_MEDIUM) for t_u in self.properties['units']: if ai_stat.me.population + ai_stat.cost_unit_recruitment[ t_u].population <= ai_stat.me.population_limit: if ai_stat.me.resources >= ai_stat.cost_unit_recruitment[ t_u].resources: if ai_stat.me.culture >= ai_stat.cost_unit_recruitment[ t_u].culture: return RecruitmentOption(t_u, Priority.P_MEDIUM) return None
def protective_movement(current_tile: Tile, target_tile: Tile, protected_tile: Tile, domain: List[Tile]) -> Tuple[Optional[Tile], int]: """ If an army/obj chooses to use protective movement, it will stay close to the entity it is protecting It will position itself such that it intercepts the incoming hostile entity on the target tile if possible :param current_tile: tile of the entity which is protecting (friendly army for instance) :param target_tile: tile of the hostile entity :param protected_tile: tile of the entity which is to be protected :param domain: the search domain, typically a subset of the walkable tiles :return: None if there is a problem or no path is found, otherwise the next step and the size of the path """ if not (current_tile and target_tile and protected_tile): error("a tile is None") return None, -1 shortest_path: Tuple[int, Optional[Tile]] = (1000, None) for nei in essentials.get_neighbours_on_set(protected_tile, domain): step, dist = next_step_to_target(nei, target_tile, domain) if step: if dist < shortest_path[0]: shortest_path = (dist, nei) return next_step_to_target(current_tile, shortest_path[1], domain)
def get_army_spawn_loc(self, ai_stat: AI_GameStatus) -> Tuple[int, int]: nei: List[Tile] = essentials.get_neighbours_on_set(ai_stat.map.building_list[0].base_tile, ai_stat.map.walkable_tiles) idx = random.randint(0, len(nei)-1) return nei[idx].offset_coordinates
def calculate_army_movement( self, ai_stat: AI_GameStatus) -> List[ArmyMovementOption]: """ Villager movement In passive state: patrol In aggressive state: attack buildings In defencive state: protect own village, move to intercept incoming army """ movements: List[ArmyMovementOption] = [] if len(ai_stat.map.army_list) == 0: return movements if ai_stat.map.army_list[0].population == 0: return movements army_tile = ai_stat.map.army_list[0].base_tile village_tile = ai_stat.map.building_list[0].base_tile # --------------------- Passive movement -------------------- if self.state is AI_NPC.AI_State.PASSIVE: domain = [ x for x in ai_stat.map.walkable_tiles if not x.has_building() and not x.has_army() ] domain.append(army_tile) if not self.patrol_target: self.patrol_target = random.sample(domain, 1)[0].offset_coordinates pt = get_tile_by_xy(self.patrol_target, ai_stat.map.discovered_tiles) if pt not in domain: self._dump( "relocating patrol target, it appears to be blocked") pt = random.sample(domain, 1)[0] if get_distance(pt, army_tile) > 0: self._dump(f"patrol tile: {pt.offset_coordinates}") next_step, dist = next_step_to_target(army_tile, pt, domain) if next_step: movements.append( ArmyMovementOption(pt, Priority.P_MEDIUM, next_step.offset_coordinates)) else: self.patrol_target = None # --------------------- Defencive movement -------------------- elif self.state is AI_NPC.AI_State.DEFENSIVE: hostile_armies = [ x for x in ai_stat.map.opp_army_list if x.owner in self.hostile_player ] for h_a in hostile_armies: domain_no_buildings = [ x for x in ai_stat.map.walkable_tiles if not x.has_building() ] next_step, dist = protective_movement(army_tile, h_a.base_tile, village_tile, domain_no_buildings) if next_step: movements.append( ArmyMovementOption(self.patrol_target, Priority.P_MEDIUM, next_step.offset_coordinates)) if len(hostile_armies) == 0: target = random.sample( get_neighbours_on_set(village_tile, ai_stat.map.walkable_tiles), 1)[0] next_step, dist = next_step_to_target( army_tile, target, ai_stat.map.walkable_tiles) if next_step: movements.append( ArmyMovementOption(self.patrol_target, Priority.P_MEDIUM, next_step.offset_coordinates)) # --------------------- Aggressive movement -------------------- elif self.state is AI_NPC.AI_State.AGGRESSIVE: hostile_armies = [ x for x in ai_stat.map.opp_army_list if x.owner in self.hostile_player ] hostile_buildings = [ x for x in ai_stat.map.opp_building_list if x.owner in self.hostile_player ] for h_target in list(set().union(hostile_armies, hostile_buildings)): next_step, dist = next_step_to_target( army_tile, h_target.base_tile, ai_stat.map.walkable_tiles) if next_step: movements.append( ArmyMovementOption(h_target, Priority.P_MEDIUM, next_step.offset_coordinates)) return movements