アットウィキロゴ

sample.py

このページはais/python/sample.pyのコメントを訳したものです。

sample.py

#!WPY
 
"""これは必要最低限のpythonAIの例です"""
 
import ai as wesnoth
import heapq
 
def pos(location):
    """デバッグメッセージでポジションを表示するためだけの関数"""
    return "(%d, %d)" % (1 + location.x, 1 + location.y)
 
def debug(string):
    pass
 
class AI:
    """AIのメインクラス"""
 
    def __init__(self):
        """このクラスはAIのターン毎に構成されます。
        定期的に値を取得し続けるため、またゲームをセーブした時のために、
        set_variableとget_variableを使用します。"""
 
        self.team = wesnoth.get_current_team()
        self.village_radius = 25
        self.scout_villages = 3
 
        self.recruit()
 
        self.fight()
 
        self.conquer()
 
    def conquer(self):
        """村を占領しようとします"""
        villages = self.find_villages()
        units =  wesnoth.get_destinations_by_unit().keys()
 
        #(distance, unit, village)というリストを作成
        queue = []
        for village in villages:
            for unit in units:
                d = self.get_distance(unit, village)
                if d != None: heapq.heappush(queue, (d, unit, village))
 
        #次にユニットを村に指定し、村まで移動させます
        while queue:
            d, unit, village = heapq.heappop(queue)
            if unit in units and village in villages:
                units.remove(unit)
                villages.remove(village)
                self.go_to(unit, village)
 
                if not units: break
                if not villages: break
 
    def cumulate_damage(self, cumulated, hitpoints, new_damage):
        cumulated2 = {}
        for already, ap in cumulated.iteritems():
            for hp, probability in new_damage.iteritems():
                damage = int(already + hitpoints - hp)
                cumulated2[damage] = cumulated2.get(damage, 0) + ap * probability
        return cumulated2
 
    def danger_estimate(self, unit, where, enemy):
        """敵がどこにいて攻撃されるかどうかの危険度の情報を得ます"""
 
        scores = []
        u = wesnoth.get_units()[unit]
        e = wesnoth.get_units()[enemy]
        u_defense = u.defense_modifier(wesnoth.get_map(), where)
        e_defense = e.defense_modifier(wesnoth.get_map(), enemy)
 
        u_bonus = 100 - (u.type().alignment - 1) * wesnoth.get_gamestatus().lawful_bonus
        e_bonus = 100 - (e.type().alignment - 1) * wesnoth.get_gamestatus().lawful_bonus
 
        for attack in e.attacks():
            score = attack.damage * attack.num_attacks * e_bonus / 100
            score *= u_defense
            score *= u.damage_against(attack) / 100
 
            back = []
            for retaliation in u.attacks():
                if attack.range == retaliation.range:
                    x = retaliation.damage * retaliation.num_attacks * u_bonus / 100
                    x *= e_defense
                    x *= e.damage_against(retaliation) / 100
                    back.append(x)
 
            if back:
                r = max(back)
                score -= r
            heapq.heappush(scores, score)
 
        return scores[0]
 
    def danger(self, unit, location):
        """ユニットがその場所を動いた時の危険度を見積もります。"""
        attackers = []
        for enemy, destinations in wesnoth.get_enemy_destinations_by_unit():
            for tile in wesnoth.get_adjacent_tiles(unit):
                if tile in destinations:
                    heuristic = danger_estimate(unitm, location, enemy)
                    if heuristic > 0:
                        heapq.heappush(attackers, (-heuristic, enemy, tile))
        result = 0
        already = {}
        while attackers:
            danger, enemy, tile = heapq.heappop(attackers)
            if not already[enemy] and not already[tile]:
                danger = -danger
                result += danger
                already[enemy] = 1
                already[tile] = 1
        return result
 
    def fight(self):
        """敵に攻撃します"""
        enemies =  wesnoth.get_enemy_destinations_by_unit().keys()
        units =  wesnoth.get_destinations_by_unit().keys()
 
        # 味方が倒す事が可能かつ倒せる可能性がある全てのユニットのリストを取得します。
        # これはヒューリスティックであり、ZoCとユニットの配置は無視します。
        kills = []
        for enemy in enemies:
            e = wesnoth.get_units()[enemy]
            k = {0: 1.0}
            for unit, destinations in wesnoth.get_destinations_by_unit().iteritems():
                u = wesnoth.get_units()[unit]
                for tile in wesnoth.get_adjacent_tiles(enemy):
                    if tile in destinations:
                        own_hp, enemy_hp = u.attack_statistics(tile, enemy)
                        k = self.cumulate_damage(k, e.hitpoints, enemy_hp)
            ctk = 0
            for damage, p in k.iteritems():
                if damage >= e.hitpoints:
                    ctk += p
            if ctk:
                heapq.heappush(kills, (-ctk, enemy))
 
        # 現在の自身のユニットが敵を倒せる場所を見つけます
        attacks = []
        while kills:
            ctk, enemy = heapq.heappop(kills)
            e = wesnoth.get_units()[enemy]
            ctk = -ctk
            for tile in wesnoth.get_adjacent_tiles(enemy):
                for unit in wesnoth.get_units_by_destination().get(tile, []):
                    u = wesnoth.get_units()[unit]
                    own_hp, enemy_hp = u.attack_statistics(tile, enemy)
                    score = e.hitpoints - sum([x[0] * x[1] for x in enemy_hp.iteritems()])
                    score -= u.hitpoints - sum([x[0] * x[1] for x in own_hp.iteritems()])
 
                    # 攻撃の確率が同じ場所が2つあった場合、地勢のいい方を選択します
                    score *= 50 / u.defense_modifier(tile)
 
                    heapq.heappush(attacks, (-score, unit, tile, enemy))
                    #own_hpとenemy_hpを表示
                    debug("Score for %s at %s: %s<->%s: %f [%s]" % (u.name,
                        pos(unit), pos(tile), pos(enemy), score, e.name))
 
        # 次にユニットを敵に指定して、移動、攻撃させます
        while attacks:
            score, unit, tile, enemy = heapq.heappop(attacks)
            score = -score
 
            if unit in units and enemy in enemies:
                #try:
                loc = wesnoth.move_unit(unit, tile)
                #except ValueError:
                #    loc = None
                if loc == tile:
                    e = wesnoth.get_units()[enemy]
                    wesnoth.attack_unit(tile, enemy)
                    if not e.is_valid:
                        enemies.remove(enemy)
                    units.remove(unit)
                    if not units: break
 
    def recruit(self):
        """ユニットを召還します"""
 
        # 最初に残っている金があるか確認します
        cheapest = min([x.cost for x in self.team.recruits()])
        if self.team.gold < cheapest: return
 
        # マップ上での主塔を確認します
        keeps = self.find_keeps()
 
        #リーダーを取得
        leader = None
        for location, unit in wesnoth.get_units().iteritems():
            if unit.side == self.team.side and unit.can_recruit:
                leader = location
                break
 
        # リーダーの近くにある占領した村の数を取得
        villages = len([x for x in self.find_villages()
            if leader.distance_to(x) < self.village_radius])
 
        units_recruited = int(wesnoth.get_variable("units_recruited") or 0)
 
        def attack_score(u1, u2):
            """u1が u2に攻撃した時の大体のスコア"""
            maxdeal = 0
            for attack in u1.attacks():
                deal = attack.damage * attack.num_attacks
                deal *= u2.damage_from(attack) / 100.0
                for defense in u2.attacks():
                    if attack.range == defense.range:
                        receive = defense.damage * defense.num_attacks
                        receive *= u1.damage_from(defense) / 100.0
                        deal -= receive
                if deal > maxdeal: maxdeal = deal
            return maxdeal
 
        def recruit_score(recruit, speed, defense, aggression, resistance):
            """あるユニットのタイプを召還するためのスコア"""
            need_for_speed = 3 * (villages / self.scout_villages -
                units_recruited)
            if need_for_speed < 0: need_for_speed = 0
            v = speed * need_for_speed + defense * 0.1 + aggression + resistance
            v += 1
            if v < 1: v = 1
            return v
 
        # このマップでユニットが効果的かどうか計算します
        map = wesnoth.get_map()
        recruits = self.team.recruits()
        recruits_list = []
        for recruit in recruits:
            speed = 0.0
            defense = 0.0
            n = map.x * map.y
            for y in range(map.y):
                for x in range(map.x):
                    location = wesnoth.get_location(x, y)
                    speed += recruit.movement_cost(location)
                    defense += 100 - recruit.defense_modifier(location)
            speed = recruit.movement * n / speed
            defense /= n
 
            aggression = 0.0
            resistance = 0.0
            enemies = wesnoth.get_enemy_destinations_by_unit().keys()
            n = len(enemies)
            for location in enemies:
                enemy = wesnoth.get_units()[location]
                aggression += attack_score(recruit, enemy)
                resistance -= attack_score(enemy, recruit)
            aggression /= n
            resistance /= n
 
            debug("%s: speed: %f, defense: %f, aggression: %f, resistance: %f" %
                (recruit.name, speed, defense, aggression, resistance))
 
            recruits_list.append((recruit, speed, defense, aggression, resistance))
 
        # 召還します
        for location, unit in wesnoth.get_units().iteritems():
            if unit.side == self.team.side and unit.can_recruit:
 
                keepsort = []
                for keep in keeps:
                    heapq.heappush(keepsort, (location.distance_to(keep), keep))
 
                keep = keepsort[0][1]
 
                self.go_to(location, keep)
                for i in range(6): # up to 6 units (TODO: can be more)
                    # Get a random, weighted unit type from the available.
                    heap = []
                    total_v = 0
                    for r in recruits_list:
                        v = recruit_score(*r)
                        v *= v * v
                        total_v += v
                        heapq.heappush(heap, (-v, r[0]))
                    r = wesnoth.get_random(0, total_v)
                    while 1:
                        v, recruit = heapq.heappop(heap)
                        debug("%d %d" % (r, v))
                        r += v
                        if r <= 0: break
 
                    # タイルが隣り合ったところに召還します
                    # TODO: 実際は, 可能な限り最も近い位置を使うべき
                    for position in wesnoth.get_adjacent_tiles(location):
                        if wesnoth.recruit_unit(recruit.name, position):
                            break
                    else:
                        # was not possible -> we're done
                        break
                    units_recruited += 1
                    wesnoth.set_variable("units_recruited", str(units_recruited))
 
    def find_villages(self):
        """自分のではないか、または敵の村を全て取得"""
        villages = []
        m = wesnoth.get_map()
        for x in range(m.x):
            for y in range(m.y):
                location = wesnoth.get_location(x, y)
                if wesnoth.get_map().is_village(location):
                    for team in wesnoth.get_teams():
                        # 既に自軍が持っている村か?
                        if team.owns_village(location) and not team.is_enemy:
                            break
                    else:
                        # そうでなければ誰もいないか、敵の村ということになる
                        villages.append(location)
 
        return villages
 
    def find_keeps(self):
        """主塔を見つけます"""
        keeps = []
        m = wesnoth.get_map()
        for x in range(m.x):
            for y in range(m.y):
                location = wesnoth.get_location(x, y)
                if wesnoth.get_map().is_keep(location):
                    keeps.append(location)
        return keeps
 
    def get_distance(self, location, target, must_reach = False):
        """ユニットが任意のターゲットや場所まで何ターンかかるかを取得します"""
        if location == target: return 0
        unit = wesnoth.get_units()[location]
        path = unit.find_path(location, target, 100)
        extra = 0
        if not path:
            extra = 1
            if must_reach: return None
            for adjacent in wesnoth.get_adjacent_tiles(target):
                #ユニットが5ターン移動に費やす事に価値があるかどうか
                path = unit.find_path(location, adjacent,
                    unit.type().movement * 5)
                if path: break
            else:
                return None
        l = 0
        for location in path:
            l += unit.movement_cost(location)
        l -= unit.movement_left
        l /= unit.type().movement
        l += 1 + extra
        return l
 
    def attack(self, location, enemy):
        """敵に攻撃します"""
        wesnoth.attack_unit(location, enemy)
 
    def go_to(self, location, target, must_reach = False):
        """任意の位置のユニットを任意の位置のターゲットまで移動させ、そのポジションまで行けるかどうかを返します。
        """
        if location == target: return location
 
        # もし道が塞がっていたら、近くを通る
        unit_locations = wesnoth.get_units().keys()
        if target in unit_locations:
            if must_reach: return location
            adjacent = wesnoth.get_adjacent_tiles(target)
            targets = [x for x in adjacent if not x in unit_locations]
            if targets:
                target = targets[0]
            else:
                return location
 
        # パスを見つける
        for l, unit in wesnoth.get_units().iteritems():
            if location ==  l:
                path = unit.find_path(location, target, unit.type().movement * 5)
                break
        else:
            return location
 
        if path:
            possible_destinations = wesnoth.get_destinations_by_unit().get(location, [])
            if must_reach:
                if not target in path: return location
                if not target in possible_destinations: return location
 
            # 逆にしたパスの最初の到達可能な位置を見つけます。
            path.reverse()
 
            for p in path:
                if p in possible_destinations and not p in unit_locations:
                    location = wesnoth.move_unit(location, p)
                    return location
        return location
 
import sys
print "Running sample ai."
print "Wesnoth", wesnoth.get_version()
print "Python", sys.version
 
AI()

コメント

  • ゲーム内の専門用語を適切に訳せていない気がします。 -- shin@管理者 (2009-02-18 13:59:49)
    名前:
    コメント:

total: - today: - yesterday: -

最終更新:2009年02月18日 13:59