LuaAI

From The Battle for Wesnoth Wiki
Revision as of 15:25, 23 February 2016 by Mattsc (talk | contribs) (Dynamic Lua Aspects: update this section)

This is a page containing information on configuring the AI using Lua. In addition to the technical information on this page, a tutorial-style guide is also available at Lua AI Howto.

NB: previous contents of the page moved to http://wiki.wesnoth.org/LuaAI(old)

Lua AI Functions

The code of custom Lua candidate actions (CAs) has access to the ai table. This is a Lua table containing functions for getting information about the gamestate and for executing AI moves. Together with the other Wesnoth Lua functions they can be used in the CA evaluation and execution functions to code the desired AI behavior.

ai Table Functions Containing Gamestate Information

  • ai.side: side number of the AI

Aspects

The contents of all the AiWMLAI aspects]] can also be read using ai table commands of the form

  • ai.get_<aspect_name>(), for example
    • ai.get_aggression()
    • ai.get_caution()

Note that this returns the result of the aspect evaluation, not its definition. For example, ai.get_avoid() returns the list of avoided hexes, rather than the StandardLocationFilter used to find them, and dynamic Lua aspects return the function evaluation result, not the function itself.

ai Table Functions for Executing AI Moves

  • ai.move(FROM_X,FROM_Y,TO_X,TO_Y): partial move from FROM_X,FROM_Y to TO_X,TO_Y. Each location can be given either by unit object or as a pair of comma-separated coordinates.
  • ai.move_full((FROM_X,FROM_Y,TO_X_TO_Y): full move from FROM_X,FROM_Y to TO_X,TO_Y. Each location can be given either by unit object or as a pair of comma-separated coordinates.
  • ai.attack(ATTACKER_X,ATTACKER_Y,DEFENDER_X,DEFENDER_Y): attack from ATTACKER_X,ATTACKER_Y to DEFENDER_X,DEFENDER_Y. Each location can be given either by unit object or as a pair of comma-separated coordinates. Best weapon is selected. Default aggression is used.
  • ai.attack(ATTACKER_X,ATTACKER_Y,DEFENDER_X,DEFENDER_Y,WEAPON): attack from ATTACKER_X,ATTACKER_Y to DEFENDER_X,DEFENDER_Y. Each location can be given either by unit object or as a pair of comma-separated coordinates. WEAPON weapon is used (0-based). WEAPON -1 means 'select best weapon'. Default aggression is used.
  • ai.attack(ATTACKER_X,ATTACKER_Y,DEFENDER_X,DEFENDER_Y,-1,AGGRESSION): attack from ATTACKER_X,ATTACKER_Y to DEFENDER_X,DEFENDER_Y. Each location can be given either by unit object or as a pair of comma-separated coordinates. weapon -1 means 'select best weapon'. Aggression AGGRESSION is used.
  • ai.recruit("UNIT NAME", RECRUIT_X,RECRUIT_Y): recruit a unit by name. Optional recruit location can be given either by unit object or as a pair of comma-separated coordinates.
  • ai.recruit("UNIT NAME"): recruit a unit by name on any suitable location.
  • ai.recall("UNIT ID", RECALL_X,RECALL_Y): recall a unit by id. Optional recall location can be given either by unit object or as a pair of comma-separated coordinates.
  • ai.recall("UNIT ID"): recall a unit by id on any suitable location.
  • ai.synced_command(COMMAND, X1, Y1): execute the specified Lua command in a replay and MP safe way. COMMAND is a string containing the Lua code and X1,Y1 is an optional map location that is the only allowed external variable used in the code string (specified as Lua variables x1 and y1).

Dynamic Lua Aspects

It is possible to define aspects using Lua. These can be either static or dynamic, that is, their value can change depending on the game state. We demonstrate this here with a few examples.

First, as simple example of defining the aggression aspect statically in Lua:

[aspect]
    id="aggression"
    engine="lua"
    value="0.3"
[/aspect]

There is, of course, no advantage of this over simply defining the aspect in WML, we simply show it for completeness and for the syntax. An example of defining aggression dynamically so that it increases slowly from turn to turn is

[aspect]
    id=aggression
    engine=lua
	     
    code=<<
        local value = wesnoth.current.turn / 10.
        return value
    >>      
[/aspect]

Note the difference between the value and code attributes.

The advancements Aspect

A slightly more elaborate example, using the advancements aspect, is given below. This aspect is different from others in that it can be used with a function advance(x, y), where (x, y) is the current location of the unit about to advance. You can then use wesnoth.get_unit(x, y) to retrieve the unit object itself. For example, the following code advances the unit with id=Bob to a Swordsman (assuming here that it is a spearman), while all other spearmen are advanced to javelineers.

[ai]
    [aspect]
        id=advancements
        engine=lua

        code=<<
            function advance(x, y)
                local unit = wesnoth.get_unit(x, y)
                if (unit.id == 'Bob') then
                    return 'Swordsman'
                else
                    return 'Javelineer'
                end
            end
            return advance
        >>
    [/aspect]
[/ai]

Note that this aspect only affects attacking units. When a defending unit advances it is still decided randomly. That is because Wesnoth's design does not allow the AI to make out of turn decisions.

Preload event

It is a good idea to have such a preload event in your scenario if you are intending to use Lua for AI programming or customizing. The code of this event will most probably be moved out to a macro(except for the line that requires patrol.lua, since it is not necessary).

[event]
    name=preload
    first_time_only=no
    [lua]
        code = <<
            H = wesnoth.require "lua/helper.lua"
            W = H.set_wml_action_metatable {}
            _ = wesnoth.textdomain "my-campaign" 
	    ai = {}
	    ca_counter = 0

	    wesnoth.require("ai/lua/patrol.lua")

	    >>
	[/lua]
[/event]

The code attribute can be further expanded by any Lua code needed. For example, we can set up some global variables there or include additional code libraries("wesnoth.require("ai/lua/patrol.lua")" - includes code that handles patrolling of units).

Engine code

In the engine tag you should define functions that will handle evaluation and execution of your custom candidate actions and stages. You can also run some code that is intended to be run once in the beginning.
The definition of the [engine] tag should be place inside the [ai] tag.

[engine]
    name="lua"
    code= <<
        -- your engine code here
    >>
[/engine]

Stages

Once the engine has been set up, we can start adding stages to the configuration of our AI.
To add a stage we just use the [stage] tag.

[stage]
    engine=lua
    code=<<
        -- Code for stage execution here
        -- It is better to call predefined functions for the [engine] code, 
        -- than to define the functions here, to keep the structure of the stages clear
    >>
[/stage]

Candidate actions

In addition to the method described below, it is also possible to use external candidate actions.

This is an example from the current version of the lua_ai arena. The stage with an id "ca_loop" is the RCA AI loop stage, as we can see from its name. Currently it has two candidate actions, both Lua backed.

            [stage]
                name=testing_ai_default::candidate_action_evaluation_loop
		id=ca_loop
                [candidate_action]
                    engine=lua
                    name=first
		    id=firstca
                    evaluation="return (...):candidate_action_evaluation_hello()"
                    execution="local ai, cfg = ...; ai:candidate_action_execution_hello(cfg)"
                [/candidate_action]
                [candidate_action]
                    engine=lua
                    name=second
                    evaluation="return (...):candidate_action_evaluation_hello2()"
                    execution="(...):candidate_action_execution_hello2()"
                [/candidate_action]
            [/stage]

Basically, we need to have an engine attribute, an evaluation and execution functions. The syntax (...):foo() means "call eng.foo(eng) where eng is whatever the engine code returns". We can also add, remove, modify candidate actions on the fly, using wesnoth.wml_actions.modify_ai() functionality. Behavior(sticky) candidate actions use this approach.

Behavior(sticky) candidate actions

Sometimes we need a specific unit to do a specific action in our scenario, e.g. we want a scout unit to patrol between 3 places on the map, to provide us with visibility needed. In this case we can create a sticky candidate action that will tie itself to the unit, and remove itself when the unit has died. The syntax for defining a sticky candidate action is the following:

[event]
	name=side 2 turn 1
	first_time_only=yes	
        [add_ai_behavior]
           side=2
           [filter]
                name="Rark"
           [/filter]
	   sticky=yes
	   loop_id=ca_loop
           evaluation="return patrol_eval_rark()"
           execution="patrol_rark()"
        [/add_ai_behavior]
[/event]

Here the behavior CA is added to the configuration of the AI when the event triggers. Obviously this will happen when side 2 gets its first turn, but the event can be whatever needed. The definition is very similar to a simple candidate action, but here we also filter out a unit and state that we want the behavior to be sticky. If we don't, the action will not try to remove itself, when the unit gets killed, and this could cause errors.
Such CAs can also be added from inside other CA or stage code using wesnoth.wml_actions.add_ai_behavior(cfg) function.

Sticky candidate actions never execute more than once per turn. Therefore, if the action consists of a series of goals, the CA must evaluate and execute each goal as it is achieved within itself and not rely on being called multiple times.

Behavior function library

Behaviors are defined using generators. A behavior generator receives arguments(unit info, configuration, etc) and returns a pair of functions: evaluator and executor. These functions are to be passed to the add_ai_behavior tag, or directly to the function.

Patrolling

The only behavior defined at the moment is the patrol behavior. After calling patrol_gen(name, locations), we receive a pair of functions to use for creation of candidate actions
Inclusion of the patrolling code:

wesnoth.require("ai/lua/patrol.lua")

Usage:

local patrol_rark = patrol_gen("Rark", {{x=14, y=7}, {x=15, y=7}, {x=15, y=8}, {x=14, y=8}})
-- patrol_rark.exec -- execution function
-- patrol_rark.eval -- evaluation function

As you can see, the first argument is the name of the unit, the second is a table of coordinates of arbitrary length.

Goals and targets

When deciding what move to do next, the AI uses targets(markers on the map, pointing to villages, units, locations, etc). Targets are produced by goal objects. There are multiple predefined goal objects of different types in the C++ implementation, but now it is possible to define goal objects in Lua.

[goal]
	name=lua_goal
	value=6
	engine=lua
	code = <<
		local t = {
	 	    t[1] = {value=2.3, type=3, loc={x=5, y=6} }
	 	    t[2] = {value=2.4, type=4, loc={x=4, y=16} }
 		}
 		return t
 	>>
[/goal]

As you can see, the return value must be a table containing 0 or more targets. Each target must contain the three fields: "loc", "type", "value". This code will then be parsed by the Lua engine and the targets will be available to use to any engine desired. To get the targets inside Lua, a simple

local tg = ai.get_targets() 

must be called. tg will contain a table of the same format, as one described in the goal definition code upper.


Persistence

If you need to store some data between save/load routines, you probably need a place to store the data. For this purpose, a field named "data" is automatically added to the return value of your engine code. The save routine will find this field, convert it to a config and store its content in readable format. When the scenario gets loaded back, the engine code will inject the data back in the AI context. All other data will be lost. Note that all content of "data" needs to be in WML table format, otherwise it will not be saved.


Debug access to the AI tree

wesnoth.debug_ai(side) 

-- has been added. The function only works in debug mode and returns a table containing the component tree of the active AI for a side, the engine functions and the accessor to the ai.* table of the LuaAI Also allows the user to execute stages and evaluate/execute candidate actions(eval and exec can be run independently).

Structure of the table(example):

{
    stage_hello = function: 0x6dc24c0,
    ai = #the ai table,
    data = {
               },
    do_moves = function: 0x6dc2590,
    candidate_action_evaluation_hello = function: 0x6dc2560,
    candidate_action_evaluation_hello2 = function: 0x6dc2640,
    components = {
                         id = "",
                         stage = {
                                         another_stage = {
                                                                 name = "another_stage",
                                                                 id = "",
                                                                 exec = function: 0x17cd3bb,
                                                                 engine = "lua",
                                                                 stg_ptr = "userdata: 0x7c5a8d0"
                                                             },
                                         testing_ai_default::candidate_action_evaluation_loop = {
                                                                                                         name = "testing_ai_default::candidate_action_evaluation_loop",
                                                                                                       candidate_action = {
                                                                                                                                  external = {
                                                                                                                                                     name = "external",
                                                                                                                                                     id = "",
                                                                                                                                                     exec = function: 0x17cd2f3,
                                                                                                                                                     engine = "lua",
                                                                                                                                                     ca_ptr = "userdata: 0x6fb3600"
                                                                                                                                                 },
                                                                                                                                  second = {
                                                                                                                                                   name = "second",
                                                                                                                                                   id = "",
                                                                                                                                                   exec = function: 0x17cd2f3,
                                                                                                                                                   engine = "lua",
                                                                                                                                                   ca_ptr = "userdata: 0x6fb2f50"
                                                                                                                                               }
                                                                                                                              },
                                                                                                       id = "ca_loop",
                                                                                                       exec = function: 0x17cd3bb,
                                                                                                       engine = "",
                                                                                                       stg_ptr = "userdata: 0x6fb2728"
                                                                                                   }
                                    },
                        engine = "",
                        name = ""
                    },
   candidate_action_execution_hello2 = function: 0x6dc2670,
   candidate_action_execution_hello = function: 0x6dc2530

}

The fields described as " name = function: 0xXXXXXXXX" are functions and can be called to using the usual function call syntax of Lua.

wesnoth.debug_ai(2).ai -- will return the AI table of the Lua AI context.
wesnoth.debug_ai(2).stage[another_stage].exec() -- will execute the stage.
wesnoth.debug_ai(2).ai.get_aggression() -- will the return the current value of the aggression aspect of the AI

Therefore, we have up-to-date access to the whole AI, including the data segment.

NB!: the back-end of this function will be refactored quite drastically and that may involve usage syntax changes. This shouldn't be a big problem, since this function is only used for debugging and won't work in non-debug mode. If something suddenly stops working for you, come back to this page and watch the updates.