このページはais/python/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()total: - today: - yesterday: -