Difference between revisions of "AI Recruitment"

From The Battle for Wesnoth Wiki
(Aspect recruitment_instruction)
(Aspect recruitment_instruction)
Line 98: Line 98:
 
* '''number=-1: (integer)''' A number greater than 0 will tell the AI to recruit n units. -1 means ''as much as possible''. 0 means ''do not recruit''.
 
* '''number=-1: (integer)''' A number greater than 0 will tell the AI to recruit n units. -1 means ''as much as possible''. 0 means ''do not recruit''.
 
* '''importance=1: (integer)''' The importance of a recruitment tells the AI first to recruit units with the highest importance. If gold is lacking or the castle is full, only the most important units will be recruited, the other [recruit]-jobs will be dropped then.
 
* '''importance=1: (integer)''' The importance of a recruitment tells the AI first to recruit units with the highest importance. If gold is lacking or the castle is full, only the most important units will be recruited, the other [recruit]-jobs will be dropped then.
* '''leader_id="": (string)''' Id of the leader which shall execute this job. Empty sting means ''all leaders''. (Note: Because of current implementation, leader_id is only a recommendation for the AI. If the specified leader has no free hexes the AI will use other leaders to do the job. This is because the function check_recruit_action() in contexts.hpp will automatically choose a different leader if the specified hasn't any free hexes)   
+
* '''leader_id="": (string)''' ID of the leader which shall execute this job. Empty sting means ''all leaders''. (Note: This is only a recommendation for the AI. If the specified leader has no free hexes the AI will use other leaders to do the job. This is because the function check_recruit_action() in contexts.hpp will automatically choose another leader if the specified hasn't any free hexes)   
* '''total=no: (boolean)''' Sometimes it could be useful to define how many units of one type should be on the map. When total is set to ''yes'' the AI will count the own units on the map and will then recruit the difference between ''number'' and the counted amount.
+
* '''total=no: (boolean)''' When total is set to ''yes'' the AI will count the own units on the map which matches the given type and will then recruit the difference between ''number'' and the counted amount.
 
* '''blocker=yes: (boolean)''' If set to ''yes'', the AI will stop recruiting when this job cannot be done (because the wished leader is not on a keep for example). If set to ''no'' the AI will skip this job and continue with less important ones.
 
* '''blocker=yes: (boolean)''' If set to ''yes'', the AI will stop recruiting when this job cannot be done (because the wished leader is not on a keep for example). If set to ''no'' the AI will skip this job and continue with less important ones.
 
* '''pattern=no: (boolean)''' If set to yes, the unit to recruit will not be chosen by the AI but randomly according to the frequency in the type attribute. For example when "type=Grunt, Grunt, scout" and "number=6", the AI will recruit 6 units whereas the probability that one unit is a Grunt is twice as big as the probability that the AI will recruit a scout.  
 
* '''pattern=no: (boolean)''' If set to yes, the unit to recruit will not be chosen by the AI but randomly according to the frequency in the type attribute. For example when "type=Grunt, Grunt, scout" and "number=6", the AI will recruit 6 units whereas the probability that one unit is a Grunt is twice as big as the probability that the AI will recruit a scout.  

Revision as of 14:41, 12 September 2013

This page is work in progress

The new Recruitment Candidate Action is highly configurable and supports multiple leader recruiting...

How it works

Map analyis

In a first step the CA will analyse the map and try to find "important hexes". Those are locations on the map where a fight will occur with high probability. The important hexes are used later in combat simulations to calculate a average defense for the combatants.

If the game runs in debug mode the important hexes will be displayed with a white circle on them.

Implementation details for interested readers: [...]

Score Map

For all leaders which are available for recruiting a score map will be created including all unit-types from the recruit- and recall lists.

In the end the scores shall represent the desired unit-ratios on the map. (So the AI will not just recruit the unit with the highest score but the unit which approaches the wished mix best.) The scores are filled with values coming from combat analysis (see below).

After this the AI modifies the scores further in several ways:

  • Similar units get a penalty for being similar. Similar units are units in one advancement tree.
Example (Archer can advance to Ranger):
			before	after
Elvish Fighter: 	  50	 50
Elvish Archer:		  50	 25
Elvish Ranger:		  50	 25
  • The aspects recruitment_more, recruitment_diversity and recruitment_randomness are handled (see below).

Combat Analysis

For each unit-type our leaders can recruit the CA will simulate a fight with all enemy units on the map (If there are less then 5 enemy units, the enemies recruitment list(s) will be taken into account)

After Combat Analysis the score map could look like this:

Goblin Spearmanscore:		16.1252
Naga Fighter score:		0
Orcish Archer score:		61.6442
Orcish Assassin score:		88.0737
Orcish Grunt score:		100
Troll Whelp score:		26.7646
Wolf Rider score:		0

Implementation details for interested readers

Combat Analysis will use the function compare_unit_types(a, b). It takes two unit-types, simulates a fight (it uses a cache, because simulation is expensive) and returns a positive value if unit-type a is better then b and a negative value if unit-type b is better then a. If the return value is 2.0 it means that unit-type a is twice as good as unit-type b.

Here is a important code-snipped of compare_unit_types():

double value_of_a = damage_to_b / (b_max_hp * a_cost);
double value_of_b = damage_to_a / (a_max_hp * b_cost);

if (value_of_a > value_of_b) {
  return value_of_a / value_of_b;
} else if (value_of_a < value_of_b) {
  return -value_of_b / value_of_a;
} else {
  return 0.;
}

The return-value of compare_unit_types() will be multiplied by the enemies current hp and added to the score map.

At this point the scores could be quite unhandy. They could all be negative, very high or to similar. So the scores will be transformed linear. The resulting scores are between 0.0 and 100.0.

Simplified code-snipped:

double new_100 = max_score;
double new_0 = max_score - (1.5 * (max_score - average_score));

BOOST_FOREACH(double& score, scores) {
  score = 100 * ((score - new_0) / (new_100 - new_0)); // linear transformation
}

Aspect recruitment_instruction

This is a powerful aspect to completely control recruitment via WML. Internally after the score-maps are created the CA will start to execute 'jobs' (or [recruit]-tags). If there is no job, the AI will do nothing at all.

This aspect is quite complex and requires the composite form of an aspect.

[ai]
  [aspect]
    id=recruitment-instruction
    [facet]
       turns=
       [value]
         #(here multiple [recruit] and/or [limit] tags)
         [recruit]
           #(attributes)
         [/recruit]
         [limit]
           #(attributes)
         [/limit]          
       [value]
    [/facet]
  [/aspect]
[/ai]

Attributes inside [facet]:

  • turns="": (string) This key takes a comma separated list containing number specifying the turn. '-' can be used between two values to define a range. A empty String means all turns.
  • See here for more. But note, that invalidate_on_gamestate_change and invalidate_on_minor_gamestate_change will have no effect. The [facet] will be read at the beginning of the turn and is then immutable.

Attributes inside [recruit]: (with default values)

  • type="": (string) This key takes a comma separated list containing the unit-type, usage or level of the units that can be recruited. A empty string means all units. If more then one unit is specified the AI will decide according to the score map what to recruit.
  • number=-1: (integer) A number greater than 0 will tell the AI to recruit n units. -1 means as much as possible. 0 means do not recruit.
  • importance=1: (integer) The importance of a recruitment tells the AI first to recruit units with the highest importance. If gold is lacking or the castle is full, only the most important units will be recruited, the other [recruit]-jobs will be dropped then.
  • leader_id="": (string) ID of the leader which shall execute this job. Empty sting means all leaders. (Note: This is only a recommendation for the AI. If the specified leader has no free hexes the AI will use other leaders to do the job. This is because the function check_recruit_action() in contexts.hpp will automatically choose another leader if the specified hasn't any free hexes)
  • total=no: (boolean) When total is set to yes the AI will count the own units on the map which matches the given type and will then recruit the difference between number and the counted amount.
  • blocker=yes: (boolean) If set to yes, the AI will stop recruiting when this job cannot be done (because the wished leader is not on a keep for example). If set to no the AI will skip this job and continue with less important ones.
  • pattern=no: (boolean) If set to yes, the unit to recruit will not be chosen by the AI but randomly according to the frequency in the type attribute. For example when "type=Grunt, Grunt, scout" and "number=6", the AI will recruit 6 units whereas the probability that one unit is a Grunt is twice as big as the probability that the AI will recruit a scout.

Examples

Recruit 3 Grunts (and nothing more) in turn 3-5.

[ai]
  [aspect]
    id=recruitment-instructions
    [facet]
      turns=3-5
      [value] 
        [recruit]
          type=Orcish Grunt
          number=3
        [/recruit]
      [/value]
    [/facet]
  [/aspect]
[/ai]

Recruit so, that there are at least 4 scouts in total and then recruit other units.

[ai]
  [aspect]
    id=recruitment-instructions
    [facet]
      [value] 
        [recruit]
          type=scout
          number=4
          total=yes
          importance=1
        [/recruit]
        [recruit]
          importance=0
        [/recruit]
      [/value]
    [/facet]
  [/aspect]
[/ai]

Recruit 6 Grunts or level 2 units (whatever seems better for the AI) and nothing more.

[ai]
  [aspect]
    id=recruitment-instructions
    [facet]
      [value] 
        [recruit]
          type=Orcish Grunt, 2
          number=6
        [/recruit]
      [/value]
    [/facet]
  [/aspect]
[/ai]

Recruit 5 scouts with leader1 and 5 Grunts with leader 2. Do even try to recruit the Grunts if leader1 can not recruit all of the scouts.

[ai]
  [aspect]
    id=recruitment-instructions
    [facet]
      [value] 
        [recruit]
          type=scout
          number=5
          importance=2
          leader_id=leader1
          blocker=no
        [/recruit]
        [recruit]
          type=Orcish Grunt
          number=5
          importance=1
          leader_id=leader2
        [/recruit]
      [/value]
    [/facet]
  [/aspect]
[/ai]

Do not recruit in turn 4.

[ai]
  [aspect]
    id=recruitment-instructions
    [facet]
      turns=4
      [value] 
        [recruit]
          importance=1
          number=0
        [/recruit]
      [/value]
    [/facet]
  [/aspect]
[/ai]

This will not work. Only one facet can be active.

[ai]
  [aspect]
    id=recruitment-instructions
    [facet]
      turns=1-10
      [value] 
        [recruit]
          type=scout
          number=5
          importance=1
        [/recruit]
      [/value]
    [/facet]
    [facet]
      turns=3
      [value] 
        [recruit]
          type=Orcish Grunt
          number=3
          importance=2
        [/recruit]
      [/value]
    [/facet]
  [/aspect]
[/ai]

To achieve this behavior one have to create a facet for turn 1-2, a facet for turn 3 and another facet for turn 4-10.

Other aspects

Examples