Creating Custom AIs
Contents
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. Note that, 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.
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 game state as that leads to out of sync (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.
- For a description of these functions, and all other functions of the ai table, see here.
- 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 (this works in both single and multi player) at scenario setup time. Custom AIs can also be added to eras and modifications or while a game is in progress in very similar ways.
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.
[ai] {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]
This needs to be placed in the [side] tag of side 1. Of course, you can also use the [modfiy_ai] tag directly without going through the macro. See here for the syntax.
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 might 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. See here for a little more on that.
- location: File name, including path, of the AI code for the CA
- eval_parms, exec_parms: (string) The parameters to be passed to the evaluation and execution functions of the AI. These need to be strings 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.
- The strings do not necessarily need to be in WML object Lua table format. However, this is, of course, necessary if they are to be used in functions that require that format, as in the filter example shown below.
- Note: In the next development cycle, these string parameters may be replaced by WML tags.
The example above requires
cfg = { id = 'Bob', return_x = 10, return_y = 15 }
to be passed to the evaluation and execution functions. This is used in the [candidate_action] tag code snippet shown above — without the out { }
, as mentioned.
Alternatively, if you want to pass, for example, a [filter] tag that includes the unit id and type, it would look like this
cfg={ { filter, { id = 'Bob', type = 'Assassin' } }, return_x = 10, return_y = 15" }
resulting in the following parameter string definition
eval_parms="{ filter, { id = 'Bob', type = 'Assassin' } }, return_x = 10, return_y = 15"
Again, identical except for the outer curly brackets, and repeated for clarity.
That's most of what one needs to know for the creation of custom AIs. Some additional information on variations of using custom CAs as well as other AI modifications AI is given in the following. We also list some of the other methods available for adding custom AIs at the bottom of this page. 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.
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 keep in mind 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.
Custom AIs in Eras and Modifications
Custom AIs can also be included in eras and modifications for use with any MP map that allows them. The setup is entirely equivalent to that for 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].
- 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]
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.
Notes:
- In this case, the [modify_ai] tag is placed directly inside the event, without requiring an [ai] tag.
- 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:
- Old Lua AI candidate action syntax requiring a Lua engine definition
- Behavior candidate actions
- Formula AI
- Using stages other than the main_loop stage
See also: Wesnoth AI