def __best_building_site_barracks(self, ai_stat: AI_GameStatus) -> Tuple[Priority, Tuple[int, int]]: """building sites should be a claimed tile and not next to a resource""" if ai_stat.me.resources >= ai_stat.cost_building_construction[BuildingType.BARRACKS]: candidates = [] for c in self.claimed_tiles: if not c.is_buildable: # if tile is not buildable, forget it continue res_next_to_can = basic.num_resources_on_adjacent(c) if res_next_to_can == 0: # very hard constraint (go by value would be better) #if self.compass.get_threat_level(c).value <= ThreatLevel.LOW_RISK.value: # build barracks only in safe zone if c not in self.danger_zone: candidates.append(c) if DETAILED_DEBUG: debug(f"possible candidates for a barracks: {len(candidates)}") if len(candidates) > 0: idx = random.randint(0, len(candidates) - 1) c = 0 for e in candidates: if idx == c: return Priority.P_MEDIUM, e.offset_coordinates c = c + 1 return Priority.P_NO, (-1, -1)
def __init__(self): from src.ai.ai_blueprint import AI self.dict_of_ais: Dict[int, AI] = {} debug("AI Game interface has been initialized") self.__has_finished = False # protected variable to avoid modifying it, which would lead to RC self.ref_to_move: Optional[AI_Move] = None self.time_begin = 0 self.time_end = 0
def __init__(self, name, other_players_ids: [int]): """the name of the ai""" self.name = name """each ai can do (not required) diplomacy""" self.diplomacy: AI_Diplo = AI_Diplo(other_players_ids, self.name) """this is used for development. instead of printing all AI info to the console, one can use the dump to display stats in-game""" self.__dump: str = "" debug("AI (" + str(name) + ") is running")
def stop_animation(self, drawable: Union[Army]): for s in self.move_animations: s.valid = False tbr = None for s in self.move_animations: if s.drawable.sprite.position == drawable.sprite.position: tbr = s if tbr: tbr.valid = False debug("removing drawable from animation") self.move_animations.remove(tbr)
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 evaluate_move_recruitment(self, ai_stat: AI_GameStatus) -> List[Union[RecruitmentOption, RaiseArmyOption]]: options = [] if ai_stat.me.population >= ai_stat.me.population_limit: return options if len(ai_stat.map.army_list) == 0: options.append(AI_Mazedonian.RaiseArmyOption(Priority.P_HIGH)) return options # cannot recruit if no army available # calculate offset to desired population by build order prio_merc = Priority.P_LOW prio_knight = Priority.P_LOW offset = self.build_order.population - ai_stat.me.population if offset > 0: prio_merc = Priority.increase(prio_merc) prio_knight = Priority.increase(prio_knight) else: prio_merc = Priority.decrease(prio_merc) prio_knight = Priority.decrease(prio_knight) # compare to desired army composition: army = ai_stat.map.army_list[0] if army.population > 0: percentage_merc_wanted = self.army_comp.ac[0] / (self.army_comp.ac[0] + self.army_comp.ac[0]) percentage_merc_actual = army.mercenaries / (army.mercenaries + army.knights) percentage_knig_wanted = self.army_comp.ac[1] / (self.army_comp.ac[0] + self.army_comp.ac[0]) percentage_knig_actual = army.knights / (army.mercenaries + army.knights) if DETAILED_DEBUG: debug(f"merc: {percentage_merc_wanted} - {percentage_merc_actual} | knight: {percentage_knig_wanted} - {percentage_knig_actual}") if (percentage_knig_wanted - percentage_knig_actual) < (percentage_merc_wanted - percentage_merc_actual): prio_merc = Priority.increase(prio_merc) else: prio_knight = Priority.increase(prio_knight) else: # in case the population is 0, we cannot compute the above. Just increase the priority prio_knight = Priority.increase(prio_knight) prio_merc = Priority.increase(prio_merc) # mercenary cost_unit_me = ai_stat.cost_unit_recruitment[UnitType.MERCENARY] if ai_stat.me.population + cost_unit_me.population <= ai_stat.me.population_limit: if ai_stat.me.resources >= cost_unit_me.resources: if ai_stat.me.culture >= cost_unit_me.culture: options.append(RecruitmentOption(UnitType.MERCENARY, prio_merc)) else: if DETAILED_DEBUG: hint("not enough culture to recruit a mercenary. actual: {} required: {}".format( ai_stat.me.culture, cost_unit_me.culture)) else: if DETAILED_DEBUG: hint("not enough resources to recruit a mercenary. actual: {} required: {}".format( ai_stat.me.resources, cost_unit_me.resources)) else: if DETAILED_DEBUG: hint("not enough free population to recruit a mercenary") # knight cost_unit_kn = ai_stat.cost_unit_recruitment[UnitType.KNIGHT] if ai_stat.me.population + cost_unit_kn.population <= ai_stat.me.population_limit: if ai_stat.me.resources >= cost_unit_kn.resources: if ai_stat.me.culture >= cost_unit_kn.culture: options.append(RecruitmentOption(UnitType.KNIGHT, prio_knight)) else: if DETAILED_DEBUG: hint("not enough culture to recruit a knight. actual: {} required: {}".format( ai_stat.me.culture, cost_unit_kn.culture)) else: if DETAILED_DEBUG: hint("not enough resources to recruit a knight. actual: {} required: {}".format( ai_stat.me.resources, cost_unit_kn.resources)) else: if DETAILED_DEBUG: hint("not enough free population to recruit a knight") return options
def evaluate_move_building(self, ai_stat: AI_GameStatus) -> List[BuildOption]: def normalize(value: int) -> Priority: if value <= 0: return Priority.P_NO elif value <= 1: return Priority.P_LOW elif value <= 2: return Priority.P_MEDIUM elif value <= 4: return Priority.P_HIGH return Priority.P_CRITICAL # get current bo (somewhat a code duplicate, but offset is required) current_bo = self.build_order offset_to_bo, _ = self.__compare_to_bo(current_bo, ai_stat) if current_bo != None: # hint("Currently active build order: {}".format(current_bo.name)) # value_farm = -1 prio_racks = Priority.P_NO value_hut = -1 # site_farm = (-1, -1) farm_opt = BuildOption(BuildingType.FARM, (-1, -1), [], Priority.P_NO) racks_opt = BuildOption(BuildingType.BARRACKS, (-1, -1), [], Priority.P_NO) hut_opt = BuildOption(BuildingType.HUT, (-1, -1), [], Priority.P_NO) site_racks = (-1, -1) site_hut = (-1, -1) for t, v in offset_to_bo: # hint(f"for type: {t}, v: {v}") if v > 0: # only check this building type if it is part of the bo if t == BuildingType.FARM: farm_opt: BuildOption = self.__best_building_site_farm(ai_stat) if v >= 2: if farm_opt.score.value > 0: # not Priority.P_NO farm_opt.score = Priority.increase(farm_opt.score) elif t == BuildingType.HUT: value_hut, site_hut = self.__best_building_site_hut(ai_stat) hut_opt = BuildOption(BuildingType.HUT, site_hut, [], normalize(value_hut)) v = v + self.inactive_huts if v >= 2: if hut_opt.score.value > 0: # not Priority.P_NO hut_opt.score = Priority.increase(hut_opt.score) elif t == BuildingType.BARRACKS: prio_racks, site_racks = self.__best_building_site_barracks(ai_stat) racks_opt = BuildOption(BuildingType.BARRACKS, site_racks, [], prio_racks) if v >= 2: if prio_racks.value > 0: # not Priority.P_NO racks_opt.score = Priority.increase(racks_opt.score) else: debug("Build order contains unknown building type -> " + str(t)) if ai_stat.me.food < self.food_lower_limit: if farm_opt is None: # we force to look for a site even if the BO does not allow for it farm_opt = value_farm, site_farm = self.__best_building_site_farm(ai_stat) # if farm_opt: # hint(f"amount of fields {len(farm_opt.associated_tiles)}") return [farm_opt, hut_opt, racks_opt] else: debug("No building order found. This is not supported so far. Need guidance!") return []
def evaluate_state(self, ai_stat: AI_GameStatus): old_state = self.state if self.state == AI_Mazedonian.AI_State.PASSIVE: if len(self.hostile_player) > 0: def_count = 0 agg_count = 0 for h_p in self.hostile_player: opp_s = self.opponent_strength[h_p] if opp_s == AI_Mazedonian.Strength.EQUAL: if self.equal_strength_defencive: def_count = def_count + 1 else: agg_count = agg_count + 1 elif opp_s == AI_Mazedonian.Strength.WEAKER: agg_count = agg_count + 1 elif opp_s == AI_Mazedonian.Strength.STRONGER: def_count = def_count + 1 elif opp_s == AI_Mazedonian.Strength.UNKNOWN: if self.unknown_strength_defencive: def_count = def_count + 1 else: agg_count = agg_count + 1 if def_count > agg_count: self.state = AI_Mazedonian.AI_State.DEFENSIVE elif def_count > agg_count: self.state = AI_Mazedonian.AI_State.AGGRESSIVE else: if self.tend_to_be_defencive: self.state = AI_Mazedonian.AI_State.DEFENSIVE else: self.state = AI_Mazedonian.AI_State.AGGRESSIVE elif self.state == AI_Mazedonian.AI_State.AGGRESSIVE: if len(self.hostile_player) == 0: self.state = AI_Mazedonian.AI_State.PASSIVE if len(self.priolist_targets) == 0: self.state = AI_Mazedonian.AI_State.DEFENSIVE elif self.state == AI_Mazedonian.AI_State.DEFENSIVE: if len(ai_stat.map.building_list) < self.previous_amount_of_buildings: # we got attacked if len(self.hostile_player) == 0: debug("Problem with AI, we got attacked but no hostile players?!") else: self.state = AI_Mazedonian.AI_State.AGGRESSIVE if len(self.hostile_player) == 0: self.state = AI_Mazedonian.AI_State.PASSIVE # crusade ? if self.protocol is AI_Mazedonian.Protocol.LATE_GAME and self.state != AI_Mazedonian.AI_State.CRUSADE: if ai_stat.me.food > 100 and ai_stat.me.population > 20: target_player_id = self.diplomacy.get_player_with_lowest_dv() if target_player_id != -1: if len(self.priolist_targets) > 0: self.state = AI_Mazedonian.AI_State.CRUSADE self.crusade_target_id = target_player_id self.crusade_time = 15 else: self._dump("unable to find ID for crusade") if self.state is AI_Mazedonian.AI_State.CRUSADE: self.crusade_time -= 1 if self.crusade_time < 0: self.state = AI_Mazedonian.AI_State.PASSIVE self.crusade_target_id = -1 self._dump(f"State: {old_state} -> {self.state}")
def print_active_trades(self): debug("Current Trades:") for tid, trade in self.trades.items(): debug(f"ID: {tid}, T: {trade.owner}, {trade.type.name}, {trade.offer}, {trade.demand} LT: {trade.life_time}.")