Difference between revisions of "LuaAI"

From The Battle for Wesnoth Wiki
(Debug access to the AI tree: copy in content from another page (section needs cleaning up))
m (Functions Returning Aspect Values)
Line 40: Line 40:
 
   
 
   
 
  Functions returning tables:
 
  Functions returning tables:
  ai.get_attacks
+
  ai.get_attacks()
  ai.get_avoid
+
  ai.get_avoid()
  ai.get_leader_goal
+
  ai.get_leader_goal()
  ai.get_recruitment_pattern
+
  ai.get_recruitment_pattern()
  
 
=== Functions Returning Other Game State Information ===
 
=== Functions Returning Other Game State Information ===

Revision as of 21:32, 23 February 2016

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 Legacy Methods 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 Returning Game State Information

The ai table contains a large number of functions returning game state information. Some of those are simple scalar values which are self-explanatory, while others can be tables with many elements and complex structure. We will not show the structure of all these tables here. If you need to know the exact structure of such a table, use the method described below for accessing the AI tree.

Functions Returning Aspect Values

The contents of many of the AI 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.

The full list of function returning aspect values is

Functions returning scalar values (numbers, strings or booleans):
ai.get_aggression()
ai.get_attack_depth()
ai.get_caution()
ai.get_grouping()
ai.get_leader_aggression()
ai.get_leader_ignores_keep()
ai.get_leader_value()
ai.get_number_of_possible_recruits_to_force_recruit()
ai.get_passive_leader()
ai.get_passive_leader_shares_keep()
ai.get_recruitment_ignore_bad_combat()
ai.get_recruitment_ignore_bad_movement()
ai.get_scout_village_targeting()
ai.get_simple_targeting()
ai.get_support_villages()
ai.get_village_value()
ai.get_villages_per_scout()

Functions returning tables:
ai.get_attacks()
ai.get_avoid()
ai.get_leader_goal()
ai.get_recruitment_pattern()

Functions Returning Other Game State Information

  • ai.side: side number of the AI

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.


Goals and targets

It is also possible to define AI goals and targets in Lua. This is, however, somewhat inconvenient and we generally recommend using the [goal] tag with WML StandardUnitFilters (SUFs) or StandardLocationFilters (SLFs) instead. That is much more versatile and it makes no difference to the AI behavior whether the goals are defined using WML or Lua.

The only time that defining goals in Lua might be advantageous is if one wants to use goals with dynamic values, such as in this example, where one goal has a constant value, while the other's value increases from turn to turn.

[goal]
    name=lua_goal
    engine=lua
    code = <<
        local turn = wesnoth.current.turn
        local t = {}
        t[1] = { value = 2, type = 2, loc = { x = 5, y = 6 } }
        t[2] = { value = turn * 2 , type = 2, loc = { x = 4, y = 9 } }
        return t
    >>
[/goal]

As you can see, the return value must be an array containing the targets. Each target must contain three fields:

  • value: The value of the target, equivalent to that defined for WML goals
  • loc: A table with attributes x and y defining the target's location. This is one of the disadvantages with respect to the WML syntax. Each goal needs to be defined individually by its coordinates. The use of SUFs and SLFs is not possible.
  • type: A number defining the type of the target. File src/ai/default/contexts.hpp defines the available types as
enum TYPE { VILLAGE, LEADER, EXPLICIT, THREAT, BATTLE_AID, MASS, SUPPORT };

User-defined targets should usually be given type = 2, to mark them as EXPLICIT targets (the third in the list, with counting starting at 0).

Notes:

  • This code is parsed by the Lua engine and the targets are available to any engine desired.
  • Targets can be read in Lua using ai.get_targets()
    • This produces a Lua array like the one above, with one element for each hex that was found as a target (even if SUFs or SLFs were used in WML).
    • The table also includes all the automatically included targets.

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.


Lua AI — Legacy Methods

The recommended method of using cutom Lua AI functionality is described at Creating_Custom_AIs. A variety of alternative older methods also still exist. There should be few, if any, situations in which using these methods is advantageous, but for completeness they are described on a separate page.

The methods include:

  • Lua stage: There should not be anything that can be done with the Lua stage that cannot just as easily be done with the main_loop stage.
  • Lua engine: With the new type of Lua candidate actions, it is not necessary any more to define a Lua engine. This is only needed for the old-style CAs.
  • The old syntax for defining Lua candidate actions: There is no advantage of using the old syntax over the new one. On the contrary, among other things the old method requires the separate definition of a Lua engine, which is unnecessary with the new method.
  • Preload event: It is not necessary to set up a preload event any more (the one possible exception might be in order to set up a debug testing mechanism).
  • Behavior( sticky) candidate actions: These are Lua CAs which attach directly to specific units. They could, in principle, still be useful. However, using the new syntax for Lua CAs with a condition to filter for specific units is usually simpler. Several of the Micro_AIs demonstrate how to do so.


Debug access to the AI tree

debug_utils from Wesnoth Lua Pack is a very powerful tool to analyze what is going on when something is not working as expected. It can be used to display the content of essentially any Wesnoth Lua data structure. As an example, the following command:

debug_utils.dbms(ai, false, "variable", false)

shows all the default Lua AI functions -- assuming that the ai variable has been initialized.

Now it is possible to reload candidate actions on-the-fly, meaning that each new turn will reload the actual file from the hard-drive, if Wesnoth is launched with the --debug-lua launch argument. This works only with external candidate actions. Candidate actions defined in the engine inside the config file of the scenario will not be reloaded. The purpose for this is easy debugging. Now the developer does not have to reload the game to test the changes he did in the candidate action code.

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.