wesnoth-wiki-changes
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Wesnoth-wiki-changes] WritingYourOwnAI


From: wiki
Subject: [Wesnoth-wiki-changes] WritingYourOwnAI
Date: Sun, 24 Oct 2004 23:03 +0200

UserAgent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7) Gecko/20040809 
Firefox/0.9.3
IP: 168.209.98.35
URI: http://wesnoth.slack.it/?WritingYourOwnAI
 - - - - -
Index: WritingYourOwnAI
===================================================================
RCS file: /home/wesnoth/cvsroot/wikiroot/WritingYourOwnAI,v
retrieving revision 1.9
diff -u -r1.9 WritingYourOwnAI
--- WritingYourOwnAI    24 Oct 2004 20:47:37 -0000      1.9
+++ WritingYourOwnAI    24 Oct 2004 21:03:21 -0000
@@ -7,9 +7,9 @@
 ai_interface contains three important functions which allow you to execute the 
three basic types of move available in
 the game:
 
-- attack_enemy(), which is used to order an attack on an enemy unit
-- move_unit(), which is used to order a unit to move from one location to 
another
-- recruit(), which is used to recruit a new unit
+* attack_enemy(), which is used to order an attack on an enemy unit,
+* move_unit(), which is used to order a unit to move from one location to 
another, and
+* recruit(), which is used to recruit a new unit.
 
 Of course, to decide where units are to move and attack, you must have 
information about the state of the game - the
 dimensions and layout of the map, the locations and type of units on the map, 
the types of units your side can recruit,
@@ -34,25 +34,34 @@
 will need access to in order to make moves. The two most important of these 
objects are the unit map (unit_map units)
 and the game map (gamemap map).
 
-The unit map is of type std::map<location,unit> and associates locations with 
units. This object can be used to find
-the
-location of, and information about, every unit on the board. See unit.hpp for 
a definition of the 'unit' object.
-
-The game map allows you to inspect the dimensions and layout of the playing 
board. Given a location, it can tell you
-the
-terrain type at that location. See map.hpp for a definition of this object You 
can combine this class with use of the
+The unit map is of type std::map<location,unit> and associates locations with 
units.
+This object can be used to find the
+location of, and information about, every unit on the board.
+See unit.hpp for a definition of the 'unit' object.
+
+The game map allows you to inspect the dimensions and layout of the playing 
board.
+Given a location, it can tell you the
+terrain type at that location.
+See map.hpp for a definition of this object. You can combine this class with 
use of the
 functions in pathfind.hpp to find various information about where units can 
move to.
 
-The team class (defined in team.hpp) is also very important. Each side is 
represented by a 'team' object. The team
-object can tell you the gold balance of a team, which villages (note that 
internally, villages are often called
-'towers') the team owns, what units the team can recruit, and which other 
teams are this teams friends or enemies.
+The team class (defined in team.hpp) is also very important.
+Each side is represented by a 'team' object. The team
+object can tell you the gold balance of a team, which villages
+(note that internally, villages are often called 'towers')
+the team owns, what units the team can recruit,
+and which other teams are this teams friends or enemies.
 
-The utility function current_team() can be used to get a reference to the team 
that your AI is in control of, but you
+The utility function current_team() can be used to get a reference
+to the team that your AI is in control of, but you
 can also use the vector 'teams' inside the info object to get a list of all 
teams.
 
-If you want to make your AI customizable within the configuration file, you 
can gain access to any parameters passed to
-your AI using team::ai_parameters(). This returns an object of type 'config' 
(config.hpp). config objects are
-representations of WML document fragments. When the user defines your side, if 
they put an [ai] tag inside it,
+If you want to make your AI customizable within the configuration file,
+you can gain access to any parameters passed to
+your AI using team::ai_parameters().
+This returns an object of type 'config' (config.hpp). These 'config' objects 
are
+representations of WML document fragments.
+When the user defines your side, if they put an [ai] tag inside it,
 everything inside the [ai] tag will be erturned by team::ai_parameters().
 
 ---
@@ -73,16 +82,16 @@
 
 Let us conclude with a small sample AI, called 'sample_ai'. How should this AI 
behave?
 
-- First it should detect if there are any enemies in range,
+* First it should detect if there are any enemies in range,
 and if there are it should attack them by moving onto the
 best defensive terrain next to them.
 Attacks should be made with the weapon for which damage*strikes*chance to hit 
is
 the highest.
-- If there are no enemies in range, it should move units onto
+* If there are no enemies in range, it should move units onto
 villages that don't already belong to it.
-- If there are no enemies or villages in range,
+* If there are no enemies or villages in range,
 it should move toward the enemy leader along the shortest possible route.
-- At the end of its turn, it should recruit random units
+* At the end of its turn, it should recruit random units
 until it runs out of money or doesn't have any space.
 
 In the following example, I will place all functions in-line
@@ -136,9 +145,12 @@
             for(unit_map::const_iterator i = get_info().units.begin(); i != 
get_info().units.end(); ++i) {
                 if(current_team().is_enemy(i->second.side()) {
 
-We're iterating over all units, but we're only interested in units that are 
enemies of our side. So, we access our team
-object, and ask if the side the unit is on is an enemy. If it is, then we're 
interested in seeing if any of our units
-can move to a hex that's adjacent to the enemy unit. We do this by getting the 
six locations around the enemy unit:
+We're iterating over all units,
+but we're only interested in units that are enemies of our side.
+So, we access our team object, and ask if the side the unit is on is an enemy.
+If it is, then we're interested in seeing if any of our units
+can move to a hex that's adjacent to the enemy unit.
+We do this by getting the six locations around the enemy unit:
 
                     location adjacent_tiles[6];
                     get_adjacent_tiles(i->first,adjacent_tiles);
@@ -163,9 +175,12 @@
                         typedef move_map::const_iterator Itor;
                         std::pair<Itor,Itor> range = 
dstsrc.equal_range(adjacent_tiles[n]);
 
-If you don't understand how 'equal_range' works, then look up documentation on 
how the standard container multimap
-works. 'range' now refers to all the possible movements that can end with our 
unit being at 'adjacent_tiles[n]'. Let's
-iterate over all those movements, and find if any of them give a better 
defensive rating than our current best defense.
+If you don't understand how 'equal_range' works,
+then look up documentation on how the standard container multimap works.
+'range' now refers to all the possible movements that can end
+with our unit being at 'adjacent_tiles[n]'.
+Let's iterate over all those movements,
+and find if any of them give a better defensive rating than our current best 
defense.
 We'll start our iteration by creating some aliases that ensure we don't go 
crazy ;)
 
                         while(range.first != range.second) {
@@ -177,18 +192,21 @@
                             const unit_map::const_iterator un = 
get_info().units.find(src);
                             assert(un != get_info().units.end());
 
-We can assume that the unit is in that location (hence the assert), because 
calculate_possible_moves said that it's the
-possible source of a move. Let's find out the type of terrain we're planning 
to move to:
+We can assume that the unit is in that location (hence the assert),
+because calculate_possible_moves said that it's the possible source of a move.
+Let's find out the type of terrain we're planning to move to:
 
                             const gamemap::TERRAIN terrain = 
get_info().map.get_terrain(dst);
 
-Okay, so we have the unit, and we have the terrain, now we should be able to 
find out the unit's defensive rating on
-this terrain. The 'unit' class has a convenient 'defense_modifier' function 
which will tell us the chance of hitting
-unit on certain terrain.
+Okay, so we have the unit, and we have the terrain,
+now we should be able to find out the unit's defensive rating on this terrain.
+The 'unit' class has a convenient 'defense_modifier' function
+which will tell us the chance of hitting that unit on a certain terrain.
 
                             const int chance_to_hit = 
un->second.defense_modifier(get_info().map,terrain);
 
-So, now we have all that, if it's the best chance to hit we've seen so far, or 
we haven't seen any other chances to hit
+So, now we have all that, if it's the best chance to hit we've seen so far,
+or we haven't seen any other chances to hit
 at all, then we add it as our best option seen.
 
                             if(best_defense == -1 || chance_to_hit < 
best_defense) {
@@ -202,27 +220,34 @@
                         }
                     }
 
-Now if we found a possible move, best_defense will not be -1, and the movement 
will be stored in 'best_movement'. So,
-if best_defense is -1, we want to move from best_movement.second to 
best_movement.first.
+Now if we found a possible move, best_defense will not be -1,
+and the movement will be stored in 'best_movement'.
+So, if best_defense is -1,
+we want to move from best_movement.second to best_movement.first.
 
                     if(best_defense != -1) {
                         
move_unit(best_movement.second,best_movement.first,possible_moves);
 
-Remember that 'possible_moves' thing? That comes in useful here, where we have 
to give it to the display object so it
+Remember that 'possible_moves' thing?
+That comes in useful here, where we have to give it to the display object so it
 can know the path to move the unit along. This is the only time we need to 
touch it.
 
-Immediately after moving, we want to attack. First we need to know which 
weapon to use. We'll write a 'choose_weapon'
-function later which will choose our weapon. It'll have to take the location 
of the attacker and the location of the
-defender, and it'll return an int referring to our weapon of choice. For now 
we'll just make use of this function:
+Immediately after moving, we want to attack.
+First we need to know which weapon to use. We'll write a 'choose_weapon'
+function later which will choose our weapon.
+It'll have to take the location of the attacker and the location of the
+defender, and it'll return an int referring to our weapon of choice.
+For now we'll just make use of this function:
 
                         const int weapon = 
choose_weapon(best_movement.first,i->first);
                         attack_enemy(best_movement.first,i->first,weapon);
 
-This will implement our attack. What now? We've attacked once, but we want to 
attack with as many units as we can
-attack
-with, right? We can't use the same move_maps again, because they'll be invalid 
now that we've moved and attacked. What
-we'll do is we'll call do_attacks() all over again, recursively, and return 
immediately. This way all our maps will be
-recalculated.
+This will implement our attack. What now?
+We've attacked once, but we want to attack with as many units as we can
+attack with, right? We can't use the same move_maps again,
+because they'll be invalid now that we've moved and attacked. What
+we'll do is we'll call do_attacks() all over again,
+recursively, and return immediately. This way all our maps will be 
recalculated.
 
                          do_attacks();
                          return;
@@ -231,8 +256,8 @@
              }
         }
 
-That's the entire function done. It'll keep attacking while it finds attacks, 
and when it finally runs out of attacks
-to
+That's the entire function done. It'll keep attacking while it finds attacks,
+and when it finally runs out of attacks to
 execute, it'll return nicely. Let's write that choose_weapon() function now:
 
     int choose_weapon(const location& attacker, const location& defender) {
@@ -241,31 +266,36 @@
 
         const std::vector<attack_type>& attacks = att->second.attacks();
 
-unit contains a convenient 'attacks' function which returns a vector of all a 
unit's possible attacks. We'll store the
+unit contains a convenient 'attacks' function
+which returns a vector of all a unit's possible attacks. We'll store the
 best attack found so far, and iterate over all attacks:
 
         int best_attack_rating = -1;
         int best_attack = -1;
         for(int n = 0; n != attacks.size(); ++n) {
 
-There is a nice function called evaluate_battle_stats() in actions.hpp which 
will give us all sorts of information
-about
+There is a nice function called evaluate_battle_stats() in actions.hpp
+which will give us all sorts of information about
 a potential battle. We make use of it here:
 
-            const battle_stats stats =
-evaluate_battle_stats(get_info().map,attacker,defender,n,get_info().units,get_info().state,get_info
-().gameinfo,0,false);
-
-A rather complicated function call, but most of the parameters can be pulled 
straight from get_info(). The last two
-parameters are a little confusing: the first one, 'attacker_terrain_override' 
is used if we wanted to know what the
-combat would look like if the attacker was on different terrain to what it is 
on now. If this is non-0, the function
-will assume the attacker is on the type of terrain given. This is useful if 
you want to test the possibility of moving
+            const battle_stats stats = evaluate_battle_stats(get_info().map,
+                  attacker, defender, n, get_info().units,
+                  get_info().state, get_info().gameinfo, 0, false);
+
+A rather complicated function call,
+but most of the parameters can be pulled straight from get_info().
+The last two parameters are a little confusing:
+the first one, 'attacker_terrain_override' is used if we wanted to know what 
the
+combat would look like if the attacker was on different terrain
+to what it is on now. If this is non-0, the function will assume
+the attacker is on the type of terrain given.
+This is useful if you want to test the possibility of moving
 to many different hexes without actually moving there.
 
-The last parameter is false, meaning that strings won't be included in the 
results. Strings are useful for showing to a
-player in a dialog, but not useful for an AI, and are expensive to calculate, 
so this should always be false from
-within
-AI algorithms.
+The last parameter is false, meaning that strings won't be included in the 
results.
+Strings are useful for showing to a player in a dialog,
+but not useful for an AI, and are expensive to calculate,
+so this should always be false from within AI algorithms.
 
 Let's use our stats to come up with a rating for this attack:
 
@@ -289,18 +319,22 @@
         move_map srcdst, dstsrc;
         calculate_possible_moves(possible_moves,srcdst,dstsrc,false);
 
-Now it's a simple matter of iterating over possible destinations, and seeing 
if they are villages not controlled by us:
+Now it's a simple matter of iterating over possible destinations,
+and seeing if they are villages not controlled by us:
 
         for(move_map::const_iterator i = dstsrc.begin(); i != dstsrc.end(); 
++i) {
             
if(get_info().map.underlying_terrain(get_info().map.get_terrain(i->first)) == 
gamemap::TOWER &&
                current_team().owns_tower(i->first) == false) {
 
-This may look a little intimidating, but it's fairly simple: map.get_terrain 
gets the terrain at a certain location. We
-want to pass it through 'underlying_terrain', because it may be a terrain 
alias -- for instance a human village, a
-merman village, or a snow village. Then we see if it's equal to gamemap::TOWER 
to see if its underlying type is in fact
-a village.
+This may look a little intimidating, but it's fairly simple:
+map.get_terrain gets the terrain at a certain location.
+We want to pass it through 'underlying_terrain',
+because it may be a terrain alias -- for instance a human village, a
+merman village, or a snow village. Then we see if it's equal to
+gamemap::TOWER to see if its underlying type is in fact a village.
 
-The right side of the && simply sees if our team owns the village at that 
location or not. If we don't own the village,
+The right side of the && simply sees if our team owns the village
+at that location or not. If we don't own the village,
 we've found the movement we want to make, and we recurse and return.
 
                 move_unit(i->second,i->first,possible_moves);
@@ -311,15 +345,18 @@
     }
 
 
-Just a couple more functions now. Firstly, do_moves() is meant to move our 
units toward the enemy leader. Well
-hmm...there may be multiple enemies and thus more than one leader, so we'll 
just go for the first enemy leader we can
+Just a couple more functions now. Firstly, do_moves() is meant
+to move our units toward the enemy leader. Well
+hmm...there may be multiple enemies and thus more than one leader,
+so we'll just go for the first enemy leader we can
 find. We start off by trying to find the enemy leader:
 
     void move_units() {
         unit_map::const_iterator leader;
         for(leader = get_info().units.begin(); leader != 
get_info().units.end(); ++leader) {
 
-A unit is a leader if it can recruit -- so we use the 'can_recruit' function 
to test if it's a leader.
+A unit is a leader if it can recruit -- so we use the 'can_recruit'
+function to test if it's a leader.
 
             if(leader->second.can_recruit() && 
current_team().is_enemy(leader->second.side())) {
                 break;
@@ -337,8 +374,8 @@
         move_map srcdst, dstsrc;
         calculate_possible_moves(possible_moves,srcdst,dstsrc,false);
 
-We want to find the move that'll take us as close as possible to the enemy 
leader. Let's make our variables to show us
-the best move so far,
+We want to find the move that'll take us as close as possible to the enemy 
leader.
+Let's make our variables to show us the best move so far,
 
         int closest_distance = -1;
         std::pair<location,location> closest_move;
@@ -353,8 +390,8 @@
             }
         }
 
-If closest_distance is not -1, we've found a valid move that'll take one of 
our units toward the enemy leader. We can
-make the move and recurse
+If closest_distance is not -1, we've found a valid move that'll take
+one of our units toward the enemy leader. We can make the move and recurse
 
         if(closest_distance != -1) {
             move_unit(closest_move.second,closest_move.first,possible_moves);
@@ -362,7 +399,8 @@
         }
     }
 
-Okay, all our movement functions are done! Now all we've got left is the 
recruitment function. We start by getting the
+Okay, all our movement functions are done!
+Now all we've got left is the recruitment function. We start by getting the
 units that we can recruit.
 
     void do_recruitment() {
@@ -383,7 +421,8 @@
     }
 };
 
-That's it! We've made our sample_ai. All we have to do is add it to create_ai 
in ai.cpp and we're done!
+That's it! We've made our sample_ai.
+All we have to do is add it to create_ai in ai.cpp and we're done!
 
 ||AI - specific parameters||
 
@@ -395,6 +434,3 @@
 ||See Also||
 
 * HomePage
-
-
-http://wesnoth.slack.it/?HomePage






reply via email to

[Prev in Thread] Current Thread [Next in Thread]