示例#1
0
 def set_state_construction(self):
     """set the state of the building to 'construction', also changes its texture"""
     self.building_state = BuildingState.UNDER_CONSTRUCTION
     if self.has_texture_construction():
         super().set_active_texture(self.__idx_texture_construction)
     else:
         error("Building does not have a construction texture!")
示例#2
0
 def set_state_destruction(self):
     """set the state of the building to 'destruction', also changes its texture"""
     self.building_state = BuildingState.DESTROYED
     if self.has_texture_destruction():
         super().set_active_texture(self.__idx_texture_destruction)
     else:
         error("Building does not have a destruction texture!")
示例#3
0
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]
示例#4
0
 def query_ai(self, query, arg, player_id) -> str:
     if query == "diplo":
         return str(self.dict_of_ais[player_id].diplomacy.get_diplomatic_value_of_player(arg))
     elif query == "state":
         return self.dict_of_ais[player_id].get_state_as_str()
     else:
         error("WRONG QUERY")
示例#5
0
 def __compare_to_ac(self, army: AI_Army, ac: ArmyConstellation) -> UnitType:
     off_merc = ac.ac[0] - (army.mercenaries / army.population)
     off_knight = ac.ac[1] - (army.knights / army.population)
     if off_merc < 0:  # we have too many mercs
         return UnitType.KNIGHT
     if off_knight < 0:  # too many knights
         return UnitType.MERCENARY
     error("This should not happen")
示例#6
0
 def bilinear_interpolation(a: (int, int), b: (int, int), t_start: float,
                            t_end: float, t: float) -> Tuple[int, int]:
     if t > t_end:
         error("Animator: t > t_end")
     w = (t - t_start) / (t_end - t_start)
     x_pos = a[0] + w * (b[0] - a[0])
     y_pos = a[1] + w * (b[1] - a[1])
     if x_pos < 0 or y_pos < 0:
         error(f"Animator: {x_pos}|{y_pos}")
     return int(x_pos), int(y_pos)
示例#7
0
def get_tile_from_ai_obj(obj: AI_OBJ) -> Optional[Tile]:
    """

    :param obj:
    :return:
    """
    tile: Optional[Tile] = None
    if type(obj) is Tile:
        tile = obj
    else:
        tile = obj.base_tile
    if tile is None:  # is None
        error("Unable to get Tile from AI_Obj")
    return tile
示例#8
0
 def update(self, time):
     tpl = Animator.bilinear_interpolation(
         self.source, self.destination, self.start_time_ms,
         self.start_time_ms + self.time_ms, time)
     if len(tpl) != 2:
         error(
             "Animator: serious error, we left the 2d space! len(tpl): "
             + str(len(tpl)))
         hint(str(self.source))
         hint(str(self.destination))
     # if not (type(tpl) == Tuple):
     #     error("Error in Animator -> bilinear interpolation output: " + str(type(tpl)))
     if self.valid:
         self.drawable.set_sprite_pos(tpl, self.camera_pos)
 def remove_units_of_type(self, amount: int, ut: UnitType):
     tbr = []
     count = 0
     for u in self.__units:
         if count >= amount:
             break
         if u.unit_type == ut:
             tbr.append(u)
             count = count + 1
     for r in tbr:
         self.__units.remove(r)
     if count != amount:
         error(
             "Army: unexpected amount of removed units. requested to remove: {}, removed: {}"
             .format(amount, count))
 def __get_tile(self, offset_coordinates) -> Optional[Tile]:
     try:
         tile = self.map[offset_coordinates]
         if not tile:
             error("Tile not part of the map -> dict key error")
             return None
         return tile
     except KeyError:
         error(
             f"Caught Key error for key {offset_coordinates}, available keys are:"
         )
         for k, _ in self.map.items():
             print(str(k), end="")
         print("")
         traceback.print_exc()
         raise KeyError
示例#11
0
 def get_neighbours_dist(self, h: Hexagon,
                         dist: int) -> Optional[List[Hexagon]]:
     if dist < 0:
         error("HexMap: negative distance?")
         return None
     elif dist == 0:
         return [h]  # return a list to be consistent
     elif dist == 1:
         return self.get_neighbours(h)
     elif dist == 2:
         return self.get_neighbours_dist2(h)
     else:
         nei = []
         for hex in self.map:
             if HexMap.hex_distance(h, hex) <= dist:
                 nei.append(hex)
         return list(filter(None, nei))
示例#12
0
def next_step_to_target(current_tile: Tile, target_tile: Tile,
                        domain: List[Tile]) -> Tuple[Optional[Tile], int]:
    """
    basic movement, returns next step, not the complete path

    :param current_tile: current tile of the walkable entity
    :param target_tile: target
    :param domain: the search domain, typically a subset of the walkable tiles
    :return: None, if no path is found, otherwise the next tile (step) where the army can move and the distance of the path
    """
    if not current_tile:
        error("current tile is None")
        return None, -1
    if not target_tile:
        error("target tile is None")
        return None, -1
    path = essentials.a_star(current_tile, target_tile, domain)
    if len(path) <= 1:  # no path found
        return None, -1
    return path[1], len(path) - 1  # return next step and distance of the path
示例#13
0
    def army_vs_army(attacker: Army, defender: Army) -> BattleAfterMath:
        attack_value = attacker.get_attack_strength()
        defencive_value = defender.get_defence_strength()
        if attack_value == 0:
            error("Attack value is 0 -> will adjust it to 0.5")
            attack_value = 0.5
        if defencive_value == 0:
            error("Defence value is 0 -> will adjust it to 0.5")
            defencive_value = 0.5

        attacker_won = attack_value >= defencive_value
        defender_won = defencive_value >= attack_value
        attacker_losses = defencive_value if attacker_won else attack_value
        defender_losses = attack_value if defender_won else defencive_value
        attacker_alive_pop_ratio = (attack_value - attacker_losses) / attack_value
        defender_alive_pop_ratio = (defencive_value - defender_losses) / defencive_value
        if attacker_won:
            attacker_alive_pop_ratio = attacker_alive_pop_ratio + (attacker_losses/3.0) / attack_value
        if defender_won:
            defender_alive_pop_ratio = defender_alive_pop_ratio + (defender_losses/3.0) / defencive_value
        attacker_pop = attacker.get_population()
        defender_pop = defender.get_population()
        attacker_surviving_pop_ratio = attacker_pop * attacker_alive_pop_ratio
        defender_surviving_pop_ratio = defender_pop * defender_alive_pop_ratio
        for unit in UnitType:
            attacker_unit_x = attacker.get_population_by_unit(unit)
            attacker_kill_count_unit_x = attacker_unit_x - ((attacker_unit_x / attacker_pop) *
                                                            attacker_surviving_pop_ratio)
            attacker.remove_units_of_type(ceil(attacker_kill_count_unit_x), unit)
            defender_unit_x = defender.get_population_by_unit(unit)
            defender_kill_count_unit_x = defender_unit_x - ((defender_unit_x / defender_pop) *
                                                            defender_surviving_pop_ratio)
            defender.remove_units_of_type(ceil(defender_kill_count_unit_x), unit)
        if attacker_won and defender_won:
            return BattleAfterMath.DRAW
        elif attacker_won:
            return BattleAfterMath.ATTACKER_WON
        else:
            return BattleAfterMath.DEFENDER_WON
示例#14
0
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)
示例#15
0
 def get_cardinal_direction(
         tile: Tuple[int, int, int],
         base: Tuple[int, int, int]) -> List[CardinalDirection]:
     """get the cardinal direction of the cube coordinates of a tile with respect to a base
     function may return two adjacent CDs. IN This case, the tile is placed on the border"""
     x_normal = tile[0] - base[0]
     y_normal = tile[1] - base[1]
     z_normal = tile[2] - base[2]
     if x_normal + y_normal + z_normal != 0:
         error("error in CD")
     ret = []
     if x_normal <= 0 and y_normal > 0 and z_normal <= 0:
         ret.append(CardinalDirection.SouthWest)
     if x_normal >= 0 and y_normal >= 0 and z_normal < 0:
         ret.append(CardinalDirection.South)
     if x_normal > 0 and y_normal <= 0 and z_normal <= 0:
         ret.append(CardinalDirection.SouthEast)
     if x_normal >= 0 and y_normal < 0 and z_normal >= 0:
         ret.append(CardinalDirection.NorthEast)
     if x_normal <= 0 and y_normal <= 0 and z_normal > 0:
         ret.append(CardinalDirection.North)
     if x_normal < 0 and y_normal >= 0 and z_normal >= 0:
         ret.append(CardinalDirection.NorthWest)
     return ret
示例#16
0
    def smooth_map(hex_map: HexMap):
        x_max = hex_map.map_dim[0]
        y_max = hex_map.map_dim[1]

        adjusted_tiles: List[(Hexagon, str)] = []

        for y in range(y_max):
            for x in range(x_max):
                current_hex = hex_map.get_hex_by_offset((x, y))
                if current_hex.ground.ground_type in SmoothMap.ignore_list:
                    continue

                inner = ""  # tex_code of the "inner" texture how it is written on the map!
                inv = False
                outer_tex = ''  # tex_code of the texture of the inner!!! tex       # TODO this is bad!
                inner_tex = ''  # tex_code of the texture of the outer!!! tex
                if current_hex.ground.tex_code == "xx":
                    inner = "gc"
                    outer_tex = 'lg'
                    inner_tex = 'dg'
                elif current_hex.ground.tex_code == "yy":
                    inner = "gr"
                    # inv = False
                    outer_tex = 'dg'
                    inner_tex = 'lg'
                elif current_hex.ground.tex_code == "zz":
                    inner = "st"
                    outer_tex = 'st'
                    inner_tex = 'lg'
                elif current_hex.ground.tex_code == "ww":
                    # inv = False
                    inner = "gr"
                    outer_tex = 'lg'
                    inner_tex = 'st'
                elif current_hex.ground.tex_code == "vv":
                    inner = "gc"
                    outer_tex = "dg"
                    inner_tex = "st"
                    inv = True
                elif current_hex.ground.tex_code == "uu":
                    inner = "gr"
                    outer_tex = "lg"
                    inner_tex = "st"
                    inv = True
                else:
                    continue

                # gather neighbours
                edge = [False, False, False, False, False, False
                        ]  # true if this side of the hexagon gets smoothed out
                nei = [
                    hex_map.get_hex_northeast(current_hex).ground.
                    tex_code,  # the order is important, better do it explicitly
                    hex_map.get_hex_east(current_hex).ground.tex_code,
                    hex_map.get_hex_southeast(current_hex).ground.tex_code,
                    hex_map.get_hex_southwest(current_hex).ground.tex_code,
                    hex_map.get_hex_west(current_hex).ground.tex_code,
                    hex_map.get_hex_northwest(current_hex).ground.tex_code
                ]

                for i in range(6):
                    edge[i] = nei[i] == current_hex.ground.tex_code

                mode = -1
                orientation = False
                if sum(edge) == 2 or sum(edge) == 1 or sum(
                        edge
                ) == 0:  # in-place replacement would not work -> store reference
                    if sum(edge) == 1 or sum(edge) == 0:
                        for i in range(6):
                            edge[i] = nei[i] == "wd" or nei[i] == "xx" or nei[
                                i] == "ww" or nei[i] == "vv" or nei[i] == "uu"

                    if edge[0] and edge[2]:  # 1_3
                        mode = SmoothMap.__1_TO_3
                        orientation = (edge[1] == inner)
                        if inv:
                            orientation = not orientation
                    elif edge[0] and edge[3]:  # 1_4
                        mode = SmoothMap.__1_TO_4
                        if nei[1] == inner:
                            orientation = True
                        else:
                            orientation = False
                    elif edge[0] and edge[4]:  # 1_5
                        mode = SmoothMap.__1_TO_5
                        orientation = nei[1] == inner
                    elif edge[1] and edge[3]:  # 2_4
                        mode = SmoothMap.__2_TO_4
                        if nei[2] == inner:
                            orientation = False
                        else:
                            orientation = True
                    elif edge[1] and edge[4]:  # 2_5
                        mode = SmoothMap.__2_TO_5
                        if nei[2] == inner:
                            orientation = False
                        else:
                            orientation = True
                    elif edge[1] and edge[5]:  # 2_6
                        mode = SmoothMap.__2_TO_6
                        orientation = nei[2] == inner
                    elif edge[2] and edge[4]:  # 3_5
                        mode = SmoothMap.__3_TO_5
                        if nei[3] == inner:
                            orientation = False
                        else:
                            orientation = True
                    elif edge[2] and edge[5]:  # 3_6
                        mode = SmoothMap.__3_TO_6
                        if nei[3] == inner:
                            orientation = True
                        else:
                            orientation = False
                    elif edge[3] and edge[5]:  # 4_6
                        mode = SmoothMap.__4_TO_6
                        if nei[4] == inner:
                            orientation = False
                        else:
                            orientation = True

                    if orientation:
                        adjusted_tiles.append(
                            (current_hex,
                             "{}_{}_{}var_0".format(outer_tex, inner_tex,
                                                    mode)))
                    else:
                        adjusted_tiles.append(
                            (current_hex,
                             "{}_{}_{}var_0".format(inner_tex, outer_tex,
                                                    mode)))
                # elif sum(edge) == 1:
                #     # this is okay if one side is water
                #     for i in range(6)
                #         edge[i] = nei[i] == "wd"
                #     mode = SmoothMap.__2_TO_5
                #     orientation = False
                #     if orientation:
                #         adjusted_tiles.append((current_hex, "{}_{}_{}var_0".format(outer_tex, inner_tex, mode)))
                #     else:
                #         adjusted_tiles.append((current_hex, "{}_{}_{}var_0".format(inner_tex, outer_tex, mode)))
                else:
                    error("Smooth Map. Lines may not split.")

        # set the tex_code
        for h, s in adjusted_tiles:
            h.ground.tex_code = s
            h.ground.ground_type = GroundType.MIXED
示例#17
0
    def weight_options(self, options: List[Option], ai_stat: AI_GameStatus, move: AI_Move):
        used_weights: List[str] = []
        for opt in options:                 # --------------------- Action options ----------
            opt.weighted_score = opt.score.value
            if opt.score == Priority.P_NO:  # no option (should not depend on weights) -> contain invalid info
                continue
            for w in self.weights:
                if w.condition(opt, ai_stat):
                    used_weights.append(w.condition.__name__)
                    if DETAILED_DEBUG:
                        self._dump(f"Weight w: {w.weight} applied on score: {opt.weighted_score} of {type(opt)} ")
                    opt.weighted_score = opt.weighted_score + w.weight

        used_weights.append(" | ")

        for m in self.priolist_targets:         # --------------------- Movement options ----------
            if m.score == Priority.P_NO:
                continue
            for w in self.m_weights:
                if w.condition(m, ai_stat):
                    used_weights.append(w.condition.__name__)
                    m.weighted_score = m.weighted_score + w.weight

        options.sort(key=lambda x: x.weighted_score, reverse=True)
        self.priolist_targets.sort(key=lambda x: x.weighted_score, reverse=True)

        self._dump("---")
        for opt in options:
            s = f"\tOption of type: {type(opt)}, score: {opt.weighted_score}"
            if type(opt) == RecruitmentOption or type(opt) == BuildOption:
                s = s + f", type {opt.type}"
            if type(opt) == ScoutingOption:
                s = s + f", site: {opt.site}"
            s = s + f", former priority: {opt.score}"
            self._dump(s)
        for m in self.priolist_targets:
            s = f"\tAttack Target : {'army' if type(m.target) is AI_Army else 'building'}, score: {m.weighted_score}"
            self._dump(s)
        s_tmp = ""
        for w in used_weights:
            s_tmp += w + ", "
        self._dump(s_tmp)
        # translate this into move
        best_option: Option = options[0]
        if type(best_option) == WaitOption:
            move.move_type = MoveType.DO_NOTHING
            move.str_rep_of_action = "waiting"
        elif type(best_option) == BuildOption:
            move.move_type = MoveType.DO_BUILD
            move.loc = best_option.site
            move.type = best_option.type
            for at in best_option.associated_tiles:
                move.info.append(at)
            s_tmp = f"building a {best_option.type} at {str(move.loc)} ({str(best_option.cardinal_direction)})"
            if move.type == BuildingType.FARM:
                s_tmp += f" TL: {str(best_option.threat_level)}"
            move.str_rep_of_action = s_tmp
        elif type(best_option) == RecruitmentOption:
            move.move_type = MoveType.DO_RECRUIT_UNIT
            move.type = best_option.type
            move.str_rep_of_action = f"recruiting a {best_option.type}"
        elif type(best_option) == ScoutingOption:
            move.move_type = MoveType.DO_SCOUT
            move.loc = best_option.site
            move.str_rep_of_action = "scouting at" + str(move.loc)
        elif type(best_option) == AI_Mazedonian.RaiseArmyOption:
            move.move_type = MoveType.DO_RAISE_ARMY
            move.loc = self.get_army_spawn_loc(ai_stat)
            move.str_rep_of_action = "raising new army at"
        elif type(best_option) is UpgradeOption:
            move.move_type = MoveType.DO_UPGRADE_BUILDING
            move.loc = best_option.site
            move.type = best_option.type
            move.str_rep_of_action = "upgrading hut to villa"
        else:
            error("unexpected type")

        self._dump(f"DECISION: {move.str_rep_of_action}")
示例#18
0
 def set_active_texture(self, idx: int):
     if self.tex_counter >= idx:
         self.sprite.set_texture(idx)
         self.__active_tex = idx
     else:
         error("Drawable: No texture at index: " + str(idx))
示例#19
0
    def weight_options(self, ai_stat: AI_GameStatus, move: AI_Move,
                       all_options: List[Union[BuildOption, RecruitmentOption,
                                               RaiseArmyOption, WaitOption]],
                       movement_options: List[ArmyMovementOption]):
        used_weights: List[str] = []
        for opt in all_options:  # --------------------- Action options ----------
            if opt.score == Priority.P_NO:
                continue
            opt.weighted_score = opt.score.value
            for w in self.weights:
                if w.condition(opt, ai_stat):
                    used_weights.append(w.condition.__name__)
                    opt.weighted_score = opt.weighted_score + w.weight

        used_weights.append(" | ")

        for opt in movement_options:  # --------------------- Movement options ----------
            if opt.score == Priority.P_NO:
                continue
            opt.weighted_score = opt.score.value
            for w in self.m_weights:
                if w.condition(opt, ai_stat):
                    used_weights.append(w.condition.__name__)
                    opt.weighted_score = opt.weighted_score + w.weight

        all_options.sort(key=lambda x: x.weighted_score, reverse=True)
        movement_options.sort(key=lambda x: x.weighted_score, reverse=True)

        if len(all_options) > 0:
            best_option = all_options[0]
            if type(best_option) == WaitOption:
                move.move_type = MoveType.DO_NOTHING
                move.str_rep_of_action = "waiting"
            elif type(best_option) == BuildOption:
                move.move_type = MoveType.DO_BUILD
                move.loc = best_option.site
                move.type = best_option.type
                move.str_rep_of_action = f"building a {best_option.type} at " + str(
                    move.loc)
            elif type(best_option) == UpgradeOption:
                move.move_type = MoveType.DO_UPGRADE_BUILDING
                move.loc = best_option.site
                move.type = best_option.type
                move.str_rep_of_action = f"upgrading building to {move.type}"
            elif type(best_option) == RecruitmentOption:
                move.move_type = MoveType.DO_RECRUIT_UNIT
                move.type = best_option.type
                move.str_rep_of_action = f"recruiting a {best_option.type}"
            elif type(best_option) == RaiseArmyOption:
                move.move_type = MoveType.DO_RAISE_ARMY
                move.loc = best_option.site
                move.str_rep_of_action = "raising new army at"
            else:
                error("unexpected type")
        if len(movement_options) > 0:
            best_m_option = movement_options[0]
            if best_m_option.weighted_score >= self.properties[
                    'threshold_army_movement']:
                move.move_army_to = best_m_option.next_step
                move.doMoveArmy = True

        for opt in all_options:
            s = f"Option of type {type(opt)}, score: {opt.weighted_score} ({opt.score})"
            if not (type(opt) == WaitOption or type(opt) == RaiseArmyOption):
                s = s + f" -> Type: {opt.type}"
            self._dump(s)
        for m_opt in movement_options:
            stmp = 'army' if type(m_opt.target) is AI_Army else ''
            stmp = 'building' if type(m_opt.target) is AI_Building else ''
            s = f"M-Option target: {type(m_opt)} target({stmp}), score: {m_opt.weighted_score} ({m_opt.score})"
            self._dump(s)

        s = f"DECISION: {move.str_rep_of_action}"
        if move.doMoveArmy:
            s += f" moving army to {move.move_army_to}"
        self._dump(s)