Creating Custom AIs

From The Battle for Wesnoth Wiki
Revision as of 17:39, 28 February 2016 by Mattsc (talk | contribs) (Evaluation Function: minor rewordings)

Too Many Methods for Creating Custom AIs

The methods available for creating custom AIs have been developed over many development cycles. As a result, it is now much easier to create custom AIs than it used to be. The down side is that there are now a large number of different methods to do more or less the same thing, which is unnecessary and, quite frankly, confusing. Also, while most of these methods still work for backward compatibility reasons, many of them are not maintained any more.

This page therefore only describes one method of creating custom AIs, custom Lua candidate actions. Links to legacy documentation of some of the old methods are still included at the bottom of this page, but just as the methods themselves, these wiki pages are not maintained and updated any more. We recommend not using them any more and, ideally, update old code to the new methods.

Note: The current type of custom Lua candidate actions was originally called external Lua CAs, to distinguish them from the original method (now the old method) of setting up Lua CAs. However, as external Lua CAs are now the standard for creating custom AIs, we drop the 'external' in the description. It is mentioned here only in case you come across the term somewhere in older documentation.

Different Types of AI Modifications

Modifications of the AI behavior can be accomplished in a variety of different ways:

  • Adding new candidate actions
    • This is the main content of this page.
  • Changing the existing candidate actions:
    • Modifying the evaluation scores of the candidate actions
    • Deleting candidate actions
    • Instructions for these tasks are also given here.
  • Configuring the parameters/weights which determine how the default AI evaluates its moves.
    • This can be done easily in WML by setting aspects and goals.
    • It is also possible to assign dynamic aspects using Lua.
    • Technically this is not really creating custom AIs, but rather customizing the existing AI. It is not described on this page. See the links provided instead.

Setting Up a Custom AI

Creating Custom Candidate Actions

The recommended way of creating a custom AI is to add Lua candidate actions (CAs) to the main_loop stage of the default AI. Note that, in the following, we assume a solid knowledge of how the evaluation loop of this stage works, what candidate actions are in general and how they are used in the default AI, as well as some basic knowledge of Lua in Wesnoth and Lua AI.

We demonstrate how this is done on the example of the Return Guardian Micro AI. This is a very simple AI that is a variation of the default Wesnoth guardian. It has a defined guard position and only two states:

  • When the guardian is not on the guard position, it tries to return there.
  • When it is on the guard position, it is left free to attack enemy units that are within range, but may not do any other moves.

Note that some minor changes have been made to the code of the actual Return Guardian Micro AI for ease of presentation.

The code for the AI is as follows (we assume that this is in a file called 'my_addon/lua/return_guardian.lua'):

 1 local AH = wesnoth.require "ai/lua/ai_helper.lua"
 2
 3 local function get_guardian(cfg)
 4     local filter = cfg.filter or { id = cfg.id }
 5     local guardian = AH.get_units_with_moves {
 6         side = wesnoth.current.side,
 7         { "and", filter }
 8     }[1]
 9
10     return guardian
11 end
12
13 local ca_return_guardian = {}
14
15 function ca_return_guardian:evaluation(ai, cfg)
16     local guardian = get_guardian(cfg)
17     if guardian then
18         if (guardian.x == cfg.return_x) and (guardian.y == cfg.return_y) then
19             return 99990
20         else
21             return 100010
22         end
23     end
24
25     return 0
26 end
27
28 function ca_return_guardian:execution(ai, cfg)
29     local guardian = get_guardian(cfg)
30
31     -- In case the return hex is occupied:
32     local x, y = cfg.return_x, cfg.return_y
33     if (guardian.x ~= x) or (guardian.y ~= y) then
34         x, y = wesnoth.find_vacant_tile(x, y, guardian)
35     end
36
37     local next_hop = AH.next_hop(guardian, x, y)
38     if (not next_hop) then next_hop = { guardian.x, guardian.y } end
39
40    if ((next_hop[1] ~= guardian.x) or (next_hop[2] ~= guardian.y)) then
41        ai.move_full(guardian, next_hop[1], next_hop[2])
42    else
43        ai.stopunit_moves(guardian)
44    end
45 end
46
47 return ca_return_guardian

The first 12 lines are not directly relevant to setting up the AI, they simply define some utility functions. The first important line is l.13 in combination with lines 15, 28 and 47. A Lua candidate action needs to return a table with an evaluation and an execution function. The mentioned lines set up that table and the two functions. Its name is chosen to be ca_return_guardian, but that is really irrelevant as it is a variable local to the file.

The execution and evaluation function can have up to three arguments:

  • ai: The Lua AI function table. The execution function always needs this argument to be passed, the evaluation function only in some situations. See below for details.
  • cfg: A comma-separated list of configuration parameters, which are passed from the [candidate_action] tag using the eval_parms and exec_parms keys. These needs to be in Lua table format, but without the outer curly brackets { }. See below for examples.
  • self: If the persistent variable data is needed, the eval/exec function definitions need to include a third parameter self. The persistent variable is then accessible as self.data. See below for more information on the data variable.

These arguments must appear in order (ai, cfg, self) in the function calls, but as always in Lua, trailing unused arguments can be omitted.

Evaluation Function

In our example, the call to the evaluation contains the first two of these arguments, the ai table and the configuration table cfg. The ai table is not needed for this particular evaluation function. It is there only as a placeholder because the cfg table is needed and always needs to be passed as the second argument.

The cfg table contains information about the guardian unit and the guard position. We will show how this is passed to the evaluation function later. For now, we only need to know that it contains either a filter or id key, used in l.16 (call to function get_guardian defined in l.4) to check whether there is a guardian unit on the map, and keys return_x and return_y defining the guard position.

A CA evaluation function returns a numerical score which determines when in the main_loop stage evaluation loop it is executed. The return guardian code evaluation has three possible return values. First (or rather: last), if no guardian unit is found it returns 0 (l.25), which means nothing is to be done for this CA.

If a guardian unit is found, there are two possible scores. The code first checks whether the guardian is at its guard position (l.18). If so, the return score is 99,990, if not, it is 100,010. The relevance of these values is that they are just below and just above the score of the combat (attack) CA of the default AI. Thus, if the guardian is not at its return position, the return guardian CA has a higher score than any attacks done by the default AI, and the unit is returned to its guard position whether there is an enemy unit in range or not.

If, on the other hand, the guardian is already at its guard position, the return score is lower than that of the default combat CA. The default AI is thus free to use the unit in an attack.

It is important to notice that the function may not return 0 when the guardian is at its guard position. Imagine the situation that the guardian is at its position and has no unit to attack. If the return score were 0 in this case, CAs with score lower than the combat CA would take over. In particular, the move-to-targets CA would kick in and move the guardian toward one of the default AI goals. This could be done by removing these other CAs, but then it would not be available for other, non-guardian units of the AI side either. A better way to deal with this is to remove the movement of the guardian unit if it is at its guard position and no attack happened. This is done by using a return score of 99,990 in this situation, in combination with what the execution function does, as described in the following.

Execution Function

The execution function is called with the same two arguments as the evaluation function. The cfg table is needed again in order to find the guardian (l.29) and the position to move to. The position is either the return hex (l.32) or, in case it is occupied by a unit other than the guardian itself, a vacant hex close to it (l.34). Lines 37 and 38 then find the closest hex the unit can move to in practice, using the ai_helper.lua utility function next_hop().

Lines 40–44 contain the actual execution of the AI move. If the guardian is not at the next_hop position, it will be moved there and movement will be set to zero; that's what ai.move_full() does, as opposed to ai.move(). If, on the other hand, the next_hop position is its current position (which means that it is either already at the guard position or cannot get any closer), movement is removed using ai.stopunit_moves(), so that the other CAs of the default AI do not move it away afterward. Notes:

  • In the latter case, we do not remove attacks, so that the default AI is still free to attack if the guardian ends up next to an enemy unit.
  • For a description of the functions of the ai table, see here.

This is it for the AI code itself. We can see that it is possible to set up reasonable versatile AI behavior with just a few dozen lines of code by working with and around the behavior of the default AI. In the following, we show how to add this code to the default AI. But first:

A few more notes:

  • It is very important that CA evaluation functions do not change the gamestate as that leads to OOS errors.
  • Also very important: If a candidate action is selected for execution but fails to change the game state (e.g. by trying to do an illegal move/attack/recruit), then it will be blacklisted until the end of turn. This is done to prevent infinite loops and the blacklisting is handled automatically by the engine. This means, however, that you need to be make sure that the execution of a move is possible, otherwise all subsequent moves which the CA would have done during this turn will not happen. This sort of check can, for example, be done with the ai.check*() Lua AI functions.
  • The table returned by the code (ca_return_guardian in our example) may also contain other functions. For example, the get_guardian() function could be included in the table rather than by closure as we did it here. It does not matter what other elements the table contains, as long as the evaluation and execution functions exist.
  • For many more examples of other candidate actions, check out the Micro AI CAs] in the Wesnoth repository.

Including Custom Candidate Actions in Scenarios

The last step needed is adding this CA code into the AI configuration. The following instruction are for adding the AI to a scenario (it works for both single- and multi-player). Custom AIs can also be added to eras and modifications in a very similar way. That is described below.

Let us assume that the code above is saved in a file called my_addon/lua/return_guardian.lua and that we want to activate it for a unit with id 'Bob' of Side 1 with guard position (10,15). The easiest way of inserting the candidate action is by using the MODIFY_AI_ADD_CANDIDATE_ACTION macro.

{MODIFY_AI_ADD_CANDIDATE_ACTION 1 main_loop (
    [candidate_action]
        engine=lua
        name=return_guardian_bob
        id=return_guardian_bob
        max_score=100010
        location="~add-ons/my_addon/lua/return_guardian.lua"
        eval_parms="id = 'Bob', return_x = 10, return_y = 15"
        exec_parms="id = 'Bob', return_x = 10, return_y = 15"
    [/candidate_action]
)}

This macro needs to be placed either in a [side][ai] tag or in a [modify_side] tag in an event. Add links.

Explanation of the keys:

  • engine: The engine to be used for this CA. Possible values are 'cpp', 'lua' and 'fai', but for a Lua AI CA it always has to be 'lua'.
  • id and name: These are, in principle, optional. However, they are generally useful to assign for clarity. Furthermore, if you want to remove or change a candidate action later, you need to know its id.
  • max_score: The maximum score the CA evaluation function may return. This parameter is also optional, but it is useful to reduce the evaluation time of the main_loop stage, which only evaluates CAs with max_score larger than those of the currently highest-scoring CA found. (add more explanation, but not yet sure if this should be done here or on another page)
  • location: File name, including path, of the AI code for the CA
  • eval_parms, exec_parms: The parameters to be passed to the evaluation and execution functions of the AI. These need to be in form of a Lua table, without the outer curly brackets. They are passed to the functions as the second argument, called cfg in the example above.

Thus, the examples above result in

cfg = { id = 'Bob', return_x = 10, return_y = 15 }

to be passed to the evaluation and execution functions. As pointed out, this needs to be passed in Lua table format. Thus, if you want to pass, for example, a [filter] tag that includes the unit id and type, it would look like this

eval_parms="{ filter, { id = 'Bob', type = 'Assassin' } }, return_x = 10, return_y = 15"

That's most of what one needs to know for the creation of custom AIs. Some additional information on variations of using custom CAs and additional modifications to the default AI is given below. We also list some of the other methods available for adding custom AIs. As we point out above, however, we do not recommend using those any more. Neither the code itself nor the wiki pages are maintained any more at this time and, in an attempt to simplify and standardize the AI code, they might be removed at some point in the future.

Note, it used to be necessary to include an AI specifically for a side by adding a line like {ai/aliases/stable_singleplayer.cfg} in the [side] tag. This is not needed any more and is done automatically, unless ...

Custom AIs in Eras and Modifications

Custom AIs can also be included in eras and modifications for use on any MP map that allows those. The setup is entirely equivalent to that in scenarios, with two very minor differences:

  • The MODIFY_AI_ADD_CANDIDATE_ACTION macro needs to be placed inside [era][ai] or [modification][ai] instead of in [side][ai] or an event.
  • The decription attribute needs to be set inside the [ai] tag. This is the text which will show up in the computer player selection menu during the MP game setup.

As an example, the following code adds the CA described above to a modification.

[modification]
    id=guardian_bob
    name= _ "Guardian Bob AI"
    description= _ "Adds code for Guardian Bob to the default AI"
    [ai]
        description=_"Multiplayer_AI^Default AI + Guardian Bob"
        {MODIFY_AI_ADD_CANDIDATE_ACTION 1 main_loop (
            [candidate_action]
                engine=lua
                name=return_guardian_bob
                id=return_guardian_bob
                max_score=100010
                location="~add-ons/my_addon/lua/return_guardian.lua"
                eval_parms="id = 'Bob', return_x = 10, return_y = 15"
                exec_parms="id = 'Bob', return_x = 10, return_y = 15"
            [/candidate_action]
        )}
    [/ai]
[/modification]

The Persistent data Variable

It is sometimes necessary to transfer information between the evaluation and execution functions of the same CA or between CAs. It might also be necessary to have this information persist from turn to turn and across save/load cycles. The Wesnoth AI therefore adds a Lua table called data which is added to the third argument, self, of the evaluation and execution function declarations described above.

Some more information on its implementation in Lua AI is given here. For most practical intents and purposes it is sufficient to know that you can write at will to this table and read the information again from other functions and/or at other turns. The only things to take into account for that are:

  • The functions requiring access to the table need to have the third parameter specified in their declaration. If it is called self, the table is accessible as self.data.
  • If you want content to persist over save/load cycles, all content of data needs to be in WML table format. Other format content can also be used during an AI turn, for example to pass it between the evaluation and execution functions of the same CA, but it is lost when saving the game.
  • The CAs of the Micro AIs make extensive use of the data variable. You can check out their code for examples.

Mid-scenario Candidate Action Changes

The instructions given above are written for setting up custom candidate actions at the beginning of a game. However, the [modify_ai] tag and the macros using it can be used at any time during a scenario in an [event] tag, it is entirely equivalent to the methods described above. See here for details and examples.

Additional Information for Creating Custom AIs

Adding the main_loop Stage from Scratch

Sometimes it might be useful or convenient to add the entire main_loop stage from scratch, rather than adding or deleting candidate actions. If that is desired, the syntax for the example of the default AI plus Guardian Bob from above would look like this

[ai]
    description=_"Multiplayer_AI^Default AI + Guardian Bob"
    version=10710
    [stage]
        id=main_loop
        name=ai_default_rca::candidate_action_evaluation_loop
        {AI_CA_GOTO}
        {AI_CA_RECRUITMENT}
        {AI_CA_MOVE_LEADER_TO_GOALS}
        {AI_CA_MOVE_LEADER_TO_KEEP}
        {AI_CA_COMBAT}
        {AI_CA_HEALING}
        {AI_CA_VILLAGES}
        {AI_CA_RETREAT}
        {AI_CA_MOVE_TO_TARGETS}
        {AI_CA_LEADER_SHARES_KEEP}
        [candidate_action]
            engine=lua
            name=return_guardian_bob
            id=return_guardian_bob
            max_score=100010
            location="~add-ons/my_addon/lua/return_guardian.lua"
            eval_parms="id = 'Bob', return_x = 10, return_y = 15"
            exec_parms="id = 'Bob', return_x = 10, return_y = 15"
        [/candidate_action]
    [/stage]
[/ai]

This can be included in [side], [era] or [modification] tags.

Notes:

  • The description attribute is ignored if this is included in a [side] tag. It can be omitted in that case.
  • It used to be necessary to include even the unmodified default AI in scenarios in this way. Usually this was done by including a file such as {ai/ais/ai_default_rca.cfg} in the [side] tag. While it does no harm to still do so, it is not needed any more. If no other stage is defined, the default AI is automatically included for all computer controlled sides.

Modifying Default AI Candidate Actions

In addition to customizing the default AI behavior using aspects and goals, the default candidate actions can be deleted selectively and their scores can be changed.

Deleting Default AI Candidate Actions

Just as all the previous tasks described on this page, this can also be done with the [modify_ai] tag. For example, if we wanted to create an AI that does not attack, we can remove the combat CA.

[modify_ai]
    side=2
    action=delete
    path=stage[main_loop].candidate_action[combat]
[/modify_ai]

Changing the Score of a Default AI Candidate Action

The return score is pretty much the only thing that can be changed about a default AI CA (other than the customizing with aspects and goals described at the link above). This can be done using action=change in the [modify_ai] tag. For example, if we want to have an AI that attacks only after the healing, villages and retreat CAs (with default scores 80,000, 60,000 and 40,000) have finished, we can change the combat CA to have a score of 30,000.

[modify_ai]
    side=1
    action=change
    path=stage[main_loop].candidate_action[]
    [candidate_action]
        id=combat
        engine=cpp
        name=ai_default_rca::combat_phase
        max_score=30000
        score=30000
    [/candidate_action]
[/modify_ai]

Notes:

  • Note that the change action is really a delete action followed by an add action. Thus, the full content of the [candidate_action] tag needs to be given, not just the parameter that is being changed.
  • Macro version also exist for these actions, see here for more.
  • The configurations of the default CAs can be found in file data/core/macros/ai_candidate_actions.cfg.

On the Fly Candidate Action Testing

[ to be written ]

Legacy Methods for Creating Custom AIs

Several other (older) methods still exist for creating custom AIs. However, there is nothing that can be done with them that cannot also be done with the methods described on this page. Thus, while the old methods are still functional at the moment, they might be removed at some point in order to reduce the load on the code maintainers. We therefore do not recommend using them for new code any more and, ideally, to port old code to the new methods.

Nevertheless, for the time being the documentation for using the old methods still exist. This includes:


See also: Wesnoth AI