Creating Custom AIs
This page is currently under construction (Feb 2016) |
Contents
Too Many Methods for Creating Custom AIs
The methods available for creating custom AIs have been developed over quite a few 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, 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, external Lua candidate actions, which are the result of the most recent AI framework refinement work. Links to legacy documentation of some of the old methods are included at the end, but just as the methods themselves, these wiki pages are not maintained and updated any more.
Different Types of AI Modifications
Modifications of the AI behavior can be accomplished in a variety of different ways:
- Configuring the parameters/weights which determine how the 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.
- Changing the existing candidate actions (need links):
- Modifying the evaluation scores of the CAs
- Deleting candidate actions
- Adding new candidate actions
Technically, the first two methods are not really creating a custom AI, but rather customizing the existing AI. We only list them here for completeness and refer to the links above for how to accomplish those. They might, however, be used in combination with the third method, which is the content of the remainder of this page.
Setting Up a Custom AI
Creating External Candidate Actions
The recommended way of creating a custom AI is to add external 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.
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 compared to the actual Return Guardian Micro AI in order to simplify the description.
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. An external 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 LuaAI function table (need link). The execution function will always need this argument to be passed, the evaluation function only sometimes.
- 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 outside
{ }
. 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 (l.15–26)
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 a guardian unit exists, 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 will be 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 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 note 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, 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 dealt with 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 return score of 99,990 in this situation, in combination with what the execution function does.
Execution Function (l.28–45)
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.
- 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.
Setting Up External Candidate Actions
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 external 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.
Additional Information for Creating Custom AIs
[ This section to be filled in ]
The Persistent data Variable
Adding the main_loop Stage from Scratch
Custom AIs in Eras and Modifications
Mid-scenario Changes
Modifying Default AI Candidate Actions
- Removing CAs
- Changing default CA scores
- Macros vs. the [modify_ai] tag
BCAs
On the Fly Candidate Action Testing
Legacy Methods for Creating Custom AIs
- Old Lua AI method with engine definition
- Other stages
- Formula AI
- It is, in principle, also possible to change existing or add new CAs by modifying the C++ source code. This is, of course, not useful for dynamically altering AI behavior in existing Wesnoth versions, but it can be done for committing bug fixes or new features to future versions of Wesnoth.