ReferencePythonAPI

From Wesnoth

Note: Starting from version 1.5.11 PythonAI support was removed from wesnoth. This means that this page is useless unless you are using an older version or are patching a new one (see patching).

The Python AI works by executing a python script at each turn, allowing to move and attack units. This document explains how to enable that AI and describes the objects a Python script can access as well as their properties and methods.

All properties are read-only, unless specified otherwise.

All objects except wesnoth.gamemap support the == operator. They actually support comparisons as well, but the result is only meaningful for equality test.

Contents

Building and specifying options for the Python AI

Python support will be built if HAVE_PYTHON is defined during the build process. Under Unix, this is disabled with the --disable-python configure switch.

There are three ways to use a Python AI once Python support is enabled. In all three cases, the .py file must be in the data/ais path relative to the Wesnoth data dir or userdata dir. E.g. under Unix, these scripts would be found: ~/.wesnoth/data/ais/my_ai.py, /usr/share/wesnoth/data/ais/my_ai.py. Or in Windows, wesnoth/userdata/data/ais/my_ai.py and wesnoth/data/ais/my_ai.py.

  • Method 1: Start a multiplayer game, and select the python AI to use. It must be present for all players. For this to work, the main .py file of the AI needs to have this comment as first line:
    #!WPY
    

    or if "Only Run Safe Python AIs" is disabled:

    #!UNSAFE_WPY
    
  • Method 2: Start a game from the commandline, using the --multiplayer option, for example (in one line):
    wesnoth
     --multiplayer
     --scenario=multiplayer_Fallenstar_Lake
     --controller2=ai
     --controller3=ai
     --algorithm2=python_ai
     --parm2=python_script:test.py
     --side2="Knalgan Alliance"
     --side3="Rebels"
    

    The above will start the scenario with the given id, with player 2 playing as python AI and using the script "test.py".

  • Method 3: Specify a python AI directly inside a map. In the [side] tag, put the following lines:
    controller=ai
    [ai]
    ai_algorithm=python_ai
    python_script=path to script
    [/ai]
    

    In this case, the script should reside in a directory "data/ais" below the campaign path.

Python availability

Wesnoth can be compiled without Python support, in which case Python AIs are not loaded. You can use the PYTHON preprocessor symbol in WML to check for that case (and then for example display a message about it to the user). See PreprocessorRef. Also see the next section.

Implementation details of the Python AI

There are a few details of the implementation that should be kept in mind when writing an AI. There is one Python interpreter, which is used by all AI's and initialized once, in the game. This Python interpreter will run the AI script every turn.

This has the following consequences:

  • The AI script is reloaded every turn, so it's not possible to store a global variable in the main script to cache data. See get_variable() and set_variable() for how to store data between turns (and also over saving/reloading a game).

wesnoth module reference

This is an automatically generated reference, but feel free to edit it - changes will not be overwritten but instead reviewed and included in the next version. This is the wesnoth AI module. The python script will be executed once for each turn of the side with the python AI using the script.

Functions

attack_unit() attack_unit(location attacker, location defender, int weapon = -1)

Unit at position 'attacker' attacks unit at position 'defender' with weapon 'weapon'. The weapon parameter is optional, and the same weapon which would be highlighted for a human player is used if it is omitted.

get_adjacent_tiles() positions = get_adjacent_tiles(location)

Returns a list of wesnoth.location representing tiles adjacent to the specified location.

get_current_team() team = get_current_team()

Returns a wesnoth.team object representing the current team.

get_destinations_by_unit() moves = get_destinations_by_unit()

Returns a dictionary. Keys are wesnoth.location objects pointing to player's units, values are lists of possible destinations for this unit.

get_enemy_destinations_by_unit() moves = get_enemy_destinations_by_unit()

Returns a dictionary. Keys are wesnoth.location objects pointing to player's units, values are lists of possible destinations for this unit.

get_enemy_units_by_destination() moves = get_enemy_units_by_destination()

Returns a dictionary. Keys are wesnoth.location objects pointing to positions the enemie's units can reach, values are lists of locations where units that can reach this position are.

get_gamestatus() status = get_gamestatus()

Returns a wesnoth.gamestatus object representing the current game status.

get_location() location = get_location(x, y)

Returns a wesnoth.location object pointing to position (x, y) on the map.

get_map() map = get_map()

Returns a wesnoth.gamemap object representing the current map.

get_teams() teams = get_teams()

Returns a list containing wesnoth.team objects, representing the current teams in the game.

get_units() units = get_units()

Returns a dictionary containing (location, unit) pairs.

get_units_by_destination() moves = get_units_by_destination()

Returns a dictionary. Keys are wesnoth.location objects pointing to positions the player's units can reach, values are lists of locations where units that can reach this position are.

get_variable() value = get_variable(variable, default)

Retrieves a persistent variable 'variable' from the AI, which has previously been set with set_variable - or None if it can't be found. Optionally, 'default' may be provided. If the variable can not be found and 'default' is provided, default will be returned.

get_version() get_version()

Returns a string containing current Wesnoth version

log_message() log_message(string)

Logs a message, displayed as a chat message, if debug is enabled.

move_unit() new_position = move_unit(location from, location to)

Moves the unit on 'from' to the location specified by 'to', and returns a wesnoth.location object representing the position the unit was moved to.

raise_user_interact() raise_user_interact()

Function which should be called frequently to allow the user to interact with the interface.

This function will make sure that interaction doesn't occur too often, so there is no problem with calling it very regularly.

recruit_unit() result = recruit_unit(string name, location where)

Recruits the unit of type 'name', at the location 'where'. Returns 1 on success, 0 on failure.

set_variable() set_variable(variable, value)

Sets a persistent variable 'variable' to 'value'. This can be used to make the AI save strings (and other python values which can be marshalled) over multiple turns.

test_move() (enemy_destinations_by_unit, enemy_units_by_destination) or None = test_move(from location, to location (optional))

Returns two dictionaries, representing the possible enemy moves if the unit were to move from 'from location' to 'test location'. If 'to location' is not given, it returns the possible enemy moves ignoring this unit. The results will be the same as if wesnoth.get_enemy_destinations_by_unit() and wesnoth.get_enemy_units_by_destination() were called after actually performing the move.

Returns None if there is no unit at 'from location'.

attacktype

Describes an attack type.

attack_weight AI setting, floating point number.
backstab This attack has backstab.
berserk This attack uses berserk.
charge This attack has the 'charge' special.
damage Attack damage.
defense_weight AI setting, floating point number.
drains This attack has the 'drains' special.
magical This attack is magical.
marksman This attack has 'marksman' special.
name Name of the attack.
num_attacks Number of hits.
plague This attack has 'plague' special.
poison This attack has the 'poison' special.
range String with the name of the attack range.
slow This attack causes slow.
stones This attack has 'stones' special.

gamemap

Represents the current map.

is_castle() result = is_castle(location)

Returns True if the given location is a castle tile (where units are recruited to), False otherwise.

is_keep() result = is_keep(location)

Returns True if a keep (where a leader must stand to recruit) is at the given location, False otherwise.

is_village() result = is_village(location)

Returns True if a village is at the given location, False otherwise.

x Width of the map in hexes.
y Height of the map in hexes.

gamestatus

This class has information about the game status.

lawful_bonus The bonus for lawful units in the current turn. This is the percentage to add to the attack damage of lawful units (alignment = 0), and to subtract from chaotic units (alignment = 2). Neutral units (alignment = 1) are not affected.
number_of_turns The maximum number of turns of the whole game.
previous_lawful_bonus The value of lawful_bonus in the previous turn.
turn The current turn.

location

Represents a single location on the map.

adjacent_to() result = adjacent_to(location)

Returns True if the location is adjacent to this one, False otherwise.

distance_to() int distance = distance_to(location)

Returns the distance in hexes to the other location.

x X position, starting with 0 for leftmost column.
y Y position, starting with 0 for topmost row.

team

Represents one team/player/side.

gold The current amount of gold this team has.
income The current per-turn income if this team.
is_enemy Whether this team is an enemy.
name The name of this team.
owns_village() result = owns_village(location)

True if the team owns a village at the given location.

recruits() recruits = recruits()

Returns a list of wesnoth.unittype objects of all possible recruits for this team.

side Side number of this team, starting with 1.
targets() targets = targets()

Returns a dictionary containing all WML targets for the team, mapping their locations to the scores in WML.

unit

Represents a single unit. Trying to use a method or access a property, with the exception of is_valid, will result in an exception if the unit is invalid (was destroyed last move, and so on).

attack_statistics() own_hp, enemy_hp = attack_statistics(location from, location to, int attack = -1)

Returns two dictionaries with the expected battle results when the unit attacks from 'from' to the unit at 'to', optionally using the attack with index 'attack', or if no attack is given the attack which would be presented to the player in the attack dialog. The dictionaries contain the expected hitpoints after the fight, as a mapping from hitpoints to percent, where percent are specified as floating point value from 0 to 1. For example, a return of: {0:1}, {50:0.5, 40:0.5} would mean, the attacking unit is certain to die (probability for 0 hitpoints is 1), and the enemy unit will either remain at 50 or 40 HP after the fight, with equal probability of 0.5.

attacks() attacktype[] = attacks()

Returns list of possible attack types.


can_attack If the unit can still attack.
can_recruit If the unit can recruit.
damage_from() percent = damage_from(attacktype)

Returns the damage in percent the unit receives when attacked with the given attack type. (0 means no damage at all, 100 means full damage, 200 means double damage.)

defense_modifier() percent = defense_modifier(location)

Returns the defense modifier in % (probability the unit will be hit) on the given location.

experience Current experience of the unit.
find_path() location[] path = find_path(location from, location to, float max_cost = unit.movement_left)

Finds a path from 'from' to 'to' costing less than 'max_cost' movement points to reach and returns it as a list of locations. path[0] will be 'from', path[-1] will be 'to'. If no path can be found (for example, if the target is not reachable, or it would cost more than max_cost), an empty list is returned.

hitpoints Current hitpoints of the unit.
is_enemy True if this is an enemy unit, False if it is allied.
is_valid Indicates if the unit is still valid in the game. This is the only accessible field of an invalid unit, all others trigger an exception.
max_experience Maximum experience of the unit.
max_hitpoints Maximum hitpoints of the unit.
max_movement Maximum movement points of the unit.
movement_cost() cost = movement_cost(location)

Returns the cost of moving over the given location.

movement_left How many movement points the unit has left.
name Name of the unit (description from WML).
poisoned If the unit is poisoned.
side The side of the unit, starting with 1.
slowed If the unit is slowed.
stoned If the unit is stoned.
type() unittype = type()

Returns the type of the unit.

unittype

Describes a unit type.

advances_to() unittype[] = advances_to()

Returns a list of wesnoth.unittype of possible advancements.

alignment Alignment of the type: 0=lawful, 1=neutral, 2=chaotic.
attacks() attacktype[] = attacks()

Returns list of possible attack types.


can_advance If type can advance.
cost Cost of the type.
curing If type has curing ability (remove poison from others).
damage_from() percent = damage_from(attacktype)

Returns the damage in percent a unit of this type receives when attacked with the given attack type. (0 means no damage at all, 100 means full damage, 200 means double damage.)

defense_modifier() percent = defense_modifier(location)

Returns the defense modifier in % (probability the unit will be hit) on the given location.

has_zoc If type has a ZOC.
heals If type can heal others (either healing or curing ability).
hitpoints Hitpoints of the type.
illuminates If type has illuminates ability.
leadership If type has leadership ability.
level Level of the type.
movement Movement points of the type.
movement_cost() cost = movement_cost(location)

Returns the cost of moving over the given location.

name Name of the unit type.
not_living If type has not-living ability.
regenerate If type has regenerate ability.
skirmisher If type has skirmisher ability.
steadfast If type has steadfast ability.
teleport If type has teleport ability.
usage AI's usage hint of the type, one of: 'archer', 'fighter', 'healer', 'mixed fighter', 'scout'.

Patching

If you'd like to patch the current version of Wesnoth to re-enable the PythonAI, you have to manually modify the files that were committed in revision 33040 and 33062.

I did this on version 1.6.4 and it worked fine except for the definition of "units:add" that has changed. I still couldn't find a solution to this problem, so test_move and unit.attack_statistics won't work.


If you want to take advantage of UNSAFE_WPY it is also necessary to modify wesnoth/data/ai/python/parse.py . At the bottom of the file you will see a line saying:

# If you want to disable safe python, use this instead:
# def parse(name): return open(name).read(), {}
def parse(name):
   global already, modules
   already = {}
   modules = {}
   return parse_file(name), modules

See Also

This page was last modified on 29 July 2009, at 15:29.