Added a sample python AI script.
It's not quite finished, so not sure it should go into 1.2.
This commit is contained in:
parent
d0b2ec6e65
commit
fc7ac64cbc
1 changed files with 283 additions and 0 deletions
283
data/ais/sample.py
Normal file
283
data/ais/sample.py
Normal file
|
@ -0,0 +1,283 @@
|
|||
#!WPY
|
||||
import wesnoth, heapq, random
|
||||
|
||||
"""This is a rather simple minded example of a python AI."""
|
||||
|
||||
def pos(location):
|
||||
"""Just a helper function for printing positions in debug messages."""
|
||||
return "(%d, %d)" % (1 + location.x, 1 + location.y)
|
||||
|
||||
class AI:
|
||||
"""A class representing our AI."""
|
||||
|
||||
def __init__(self):
|
||||
"""This class is constructed once for each turn of the AI. To get
|
||||
persistent variables across terms, which also are saved when the game is
|
||||
saved, use set_variable and get_variable."""
|
||||
|
||||
self.team = wesnoth.get_current_team()
|
||||
|
||||
self.recruit()
|
||||
|
||||
self.fight()
|
||||
|
||||
self.conquer()
|
||||
|
||||
def conquer(self):
|
||||
"""Try to capture villages."""
|
||||
villages = self.find_villages()
|
||||
units = wesnoth.get_destinations_by_unit().keys()
|
||||
|
||||
# Construct a sorted list of (distance, unit, village) triples.
|
||||
queue = []
|
||||
for village in villages:
|
||||
for unit in units:
|
||||
d = self.get_distance(unit, village)
|
||||
if d != None: heapq.heappush(queue, (d, unit, village))
|
||||
|
||||
# Now assign units to villages, and move them.
|
||||
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 fight(self):
|
||||
"""Attack enemies."""
|
||||
enemies = wesnoth.get_enemy_destinations_by_unit().keys()
|
||||
units = wesnoth.get_destinations_by_unit().keys()
|
||||
|
||||
# Get a list of all units we can possibly kill and their chance to kill.
|
||||
# This is just a heuristic, ignoring ZoC and unit placement.
|
||||
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)
|
||||
kn = {}
|
||||
for already, ap in k.iteritems():
|
||||
for hp, probability in enemy_hp.iteritems():
|
||||
damage = int(already + e.hitpoints - hp)
|
||||
kn[damage] = kn.get(damage, 0) + ap * probability
|
||||
k = kn
|
||||
ctk = 0
|
||||
for damage, p in k.iteritems():
|
||||
if damage >= e.hitpoints:
|
||||
ctk += p
|
||||
if ctk:
|
||||
heapq.heappush(kills, (-ctk, enemy))
|
||||
|
||||
# Now find positions from where own units can attack the to be killed
|
||||
# enemies.
|
||||
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()])
|
||||
|
||||
# This is so if there are two equally good attack
|
||||
# possibilities, we chose the one on better terrain.
|
||||
score *= 50 / u.defense_modifier(tile)
|
||||
|
||||
heapq.heappush(attacks, (-score, unit, tile, enemy))
|
||||
#print own_hp, enemy_hp
|
||||
print "Score for %s at %s: %s<->%s: %f [%s]" % (u.name, pos(unit), pos(tile),
|
||||
pos(enemy), score, e.name)
|
||||
|
||||
# Now assign units to enemies, and move and attack.
|
||||
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):
|
||||
"""Recruit units."""
|
||||
keeps = self.find_keeps()
|
||||
|
||||
def score(u1, u2):
|
||||
"""Some crude score of u1 attacking u2."""
|
||||
maxdeal = 0
|
||||
for attack in u1.attacks():
|
||||
deal = attack.damage * attack.num_attacks
|
||||
deal *= u2.damage_from(attack)
|
||||
for defense in u2.attacks():
|
||||
if attack.range == defense.range:
|
||||
receive = defense.damage * defense.num_attacks
|
||||
receive *= u1.damage_from(defense)
|
||||
deal -= receive
|
||||
if deal > maxdeal: maxdeal = deal
|
||||
return maxdeal
|
||||
|
||||
# Try to figure out which units are good in this map.
|
||||
map = wesnoth.get_map()
|
||||
properties = {}
|
||||
for recruit in self.team.recruits():
|
||||
slowness = 0
|
||||
defense = 0
|
||||
for y in range(map.y):
|
||||
for x in range(map.x):
|
||||
location = wesnoth.get_location(x, y)
|
||||
slowness += recruit.movement_cost(location)
|
||||
defense += recruit.defense_modifier(location)
|
||||
slowness /= recruit.movement
|
||||
|
||||
aggression = 0
|
||||
resistance = 0
|
||||
for location in wesnoth.get_enemy_destinations_by_unit().keys():
|
||||
enemy = wesnoth.get_units()[location]
|
||||
aggression += score(recruit, enemy)
|
||||
resistance -= score(enemy, recruit)
|
||||
sys.stdout.write("%s: " % recruit.name)
|
||||
sys.stdout.write("slowness: %d, " % slowness)
|
||||
sys.stdout.write("defense: %d, " % defense)
|
||||
sys.stdout.write("aggression: %d, " % aggression)
|
||||
sys.stdout.write("resistance: %d" % resistance)
|
||||
sys.stdout.write("\n")
|
||||
|
||||
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)
|
||||
recruit = random.choice(self.team.recruits())
|
||||
|
||||
# Try to recruit it on the adjacent tiles
|
||||
# TODO: actually, it should just use the nearest possible
|
||||
# location
|
||||
for position in wesnoth.get_adjacent_tiles(location):
|
||||
if wesnoth.recruit_unit(recruit.name, position):
|
||||
break
|
||||
else:
|
||||
# was not possible -> we're done
|
||||
break
|
||||
|
||||
def find_villages(self):
|
||||
"""Find all villages which are unowned or owned by enemies."""
|
||||
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():
|
||||
# does it alreadey belong to use or an ally?
|
||||
if team.owns_village(location) and not team.is_enemy:
|
||||
break
|
||||
else:
|
||||
# no, either it belongs to an enemy or to nobody
|
||||
villages.append(location)
|
||||
|
||||
return villages
|
||||
|
||||
def find_keeps(self):
|
||||
"""Find keep locations."""
|
||||
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):
|
||||
"""Find out how many turns it takes the unit at location to reach target."""
|
||||
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):
|
||||
# Consider 5 turns worth of movement of this unit.
|
||||
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):
|
||||
"""Attack an enemy unit."""
|
||||
wesnoth.attack_unit(location, enemy)
|
||||
|
||||
def go_to(self, location, target, must_reach = False):
|
||||
"""Make a unit at the given location go to the given target.
|
||||
Returns the reached position.
|
||||
"""
|
||||
if location == target: return location
|
||||
|
||||
# If target is occupied, try to go near it
|
||||
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
|
||||
|
||||
# find a path
|
||||
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
|
||||
|
||||
# find first reachable position in reversed path
|
||||
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
|
||||
|
||||
AI()
|
Loading…
Add table
Reference in a new issue