CTP2 Bureau : Strategies
CTP2 Bureau
The Modding
Knowledgebase
Apolyton CTP2 forums
Apolyton CTP2 archives
Source Code Project SVN

Strategies

Ahenobarb wrote in another thread: "I created my own strategy, but cannot figure out how to make the AI use it." The short answer is that you have to use the SetStrategicState and/or ConsiderStrategicState functions, but this isn't at all helpful. MrOgre's explanation in the AI Customization doc is really quite good as far as it goes. It's just, er umm, a bit abbreviated. I started to write this before my computer broke down. I've got a back up computer - but it's modem doesn't work. So, I broke down and got an ASDL connection, and it's finally come through. In the meantime, rather than wasting time lurking around the other forums, I've been doing a lot of programming and writing. Here's the long answer.

by Peter Triggs

This guide has been extracted from this thread on the Apolyton forums. Peter Triggs has attached a strategyexample.slc.

1: Personalities and Civilizations

In Civ2 and Civ3 an AI civ's strategy is determined by the civ itself, i.e, there are a few flags which are set on a civ by civ basis and determine that civ's strategy. In CTP it's a bit more complicated:

[MrOwl] We also have AI personalities. ... So different opponents will "feel" different because their play styles will be so different. ... Each civ comes with two distinct personalities that are randomly chosen.

(MrOwl is Steve Mariotti, the lead programmer for Civ:CTP; the quote is from a Poly chat on Oct 3, 1998.) Once the civ's leader is chosen by the computer, it's the flags in his personality structure that determine that civ's base strategic and diplomatic states. For example, if you're playing against the Irish and the computer has given them Brian Boru (who has the Stalin personality) as a leader, the relevent data is:

PERSONALITY_STALIN {
  Exploration: Minimal
  Expansion: Average
  Discovery: Military
  Conquest: Agressive
  Trustworthiness: Chaotic
  Alignment: Evil
}

The first three flags determine the base strategic state for the Irish and the last three their diplomatic states. (We don't know a lot about how the diplomatic state selection logic works.)

The Discovery flag is what determines how the Irish strategic state is initialized:

(MrOgre, AI Customization)
The parameters defined in the strategies.txt file are used to control almost every aspect of how an AI controls their empire and tasks their units. ...

The strategic state of each player (AI or human) is initialized when the game begins based on hard coded rules that load one of the six default strategies (see table 1). 

There's an obvious typo in table 1. The correct data is:

STRATEGY_BARBARIAN   
 Always loaded for player 0. (This inherits STRATEGY_DEFAULT.)
STRATEGY_SCIENTIST_DEFAULT 
 Loaded for players with personality in which DiscoveryScientist bit set.
STRATEGY_MILITARIST_DEFAULT
 Loaded for players with personality in which DiscoveryMilitary bit set.
 
STRATEGY_ECONOMIC_DEFAULT
 Loaded for players with personality in which DiscoveryEconomic bit set.
 
STRATEGY_ECOTOPIAN_DEFAULT
 Loaded for players with personality in which DiscoveryEcotopian bit set.
 
STRATEGY_DIPLOMATIC_DEFAULT
 Loaded for players with personality in which DiscoveryDiplomatic bit set.
 
STRATEGY_DEFAULT
 All other personalities. (Including humans.)

2: Strategic States

So, first, what's a strategic state? Essentially, a strategic state is given by the data contained in a STRATEGY_XXX_DEFAULT strategy. If you look at, say, STRATEGY_MILITARIST_DEFAULT and imagine removing any of the data that can be changed, you're left with a sort of template. A strategic state can then be thought of as any acceptable set of data that can be used to fill in this template. (Actually, this isn't completely accurate because you can add new GoalElements and BuildListSequenceElements. Also, there's some data in STRATEGY_ATTACK and STRATEGY_LAUNCH_NUKES that's not in any of the default strategies, but it's a good approximation.)

Next, what is it for a strategic state to be initialized? There is a SLIC event, InitStrategicState(int_t), that occurs on turn 0 (and, IIRC, only on turn 0). When it occurs, the computer loads the correct default strategy for player[0]. If you've written your own default strategy, you can initialize it along these lines:

HandleEvent(InitStrategicState) 'Example_InitAtStart' post {
    if (player[0].owner == 2){
         SetStrategicState(player[0].owner, StrategyDB(STRATEGY_AHENOBARB_DEFAULT));
    }
}

Then as soon as the InitStrategicState event occurs during player 2's first turn, his hard-coded default strategy will be overwritten by STRATEGY_AHENOBARB_DEFAULT.

In theory, we're not restricted to turn 0, though:

You can also change the initial strategic state after the game has begun by resetting the strategic state from within a SLIC function that handles the NextStrategicState event and calls the SetStrategicState function.

So, if you want a player to use your new default strategy later in the game, you might have:

HandleEvent(NextStrategicState) 'Example_InitLaterOn' pre {
    if (player[0] == 2 && HasAdvance(player[0],ID_ADVANCE_GUNPOWDER)){
         SetStrategicState(player[0].owner, StrategyDB(STRATEGY_AHENOBARB_DEFAULT));
         DisableTrigger('Example_InitLaterOn' ); //once is probably sufficient
    }
}

In any case you have to bear in mind that some of the data in your new default strategy will be over-written by the data in the Exploration, Expansion, and Start strategies depending on the other flags in the player's personality. (More on this shortly.)

Anyway, that's based on what MrOrge wrote in the AI Customization Doc. If you look at the way Tony Evans did his scenarios, there's another possibility:

// when strategies are first computed, change to our scenario strategy
HandleEvent(InitStrategicState) 'ND_InitStrategicState' post {
    int_t strategyindex;
 strategyindex = StrategyDB(STRATEGY_ND_SCENARIO_DEFAULT);
 if(IsPlayerAlive(player[0])) {
  ConsiderStrategicState(player[0], 9999, strategyindex,-1,-1,-1);
  ComputeCurrentStrategy(player[0]);
 }
}
// every time strategies are computed, consider our scenario strategy
HandleEvent(NextStrategicState) 'ND_NextStrategicState' pre {
    int_t strategyindex;
 strategyindex = StrategyDB(STRATEGY_ND_SCENARIO_DEFAULT);
 if(IsPlayerAlive(player[0])) {
  ConsiderStrategicState(player[0], 9999, strategyindex,-1,-1,-1);
 }
}

Notice the use of the ConsiderStrategicState function followed by the ComputeCurrentStrategy function in the first handler and also the fact that he in effect re-initializes STRATEGY_ND_SCENARIO_DEFAULT every turn with the second handler. I'd guess that the point of doing this is to over-ride (note the '9999' priority) all the other strategies that the AI might be trying to patch in. But, in general, it would probably better to follow MrOgre's advice because:

Not all parameters can be changed using the ConsiderStrategicState function. The following structure elements are only changed by a SetStrategicState function call: PopAssignmentElement elements, the order of Government elements and SliderElement elements.

Azmel2 also mentioned this. It looks to me like the only time you'd want to reset a player's default strategy later in the game is if you wanted to vary the above data. Actually, I've never had any luck doing this and there isn't any example of the correct use of the SetStrategicState function in any of the scenarios. But this hasn't bothered me because you can do so much with the ConsiderStrategicState function.

3: Developing Personalities

What about all those other little strategies in strategies.txt? After the InitStrategicState event has occured, on turn 0 (and on every succeeding turn) the NextStrategicState event occurs. This is used to transition from the player's initial strategic state to what might be called his base personality strategic state and also to patch in partial strategies that allow the AI to react to different game dependent situations.

Just as the Discovery flag in the player's personality structure determines what DEFAULT strategy is initialized, so also the Exploration and Expansion flags determine which of the EXPLORE_XXX, SETTLE_XXX, and XXX_START strategies are used. The relevent data here is:

STRATEGY_EXPLORE_NEAR  
 Loaded for players with personality that has Exploration:Minimal.
STRATEGY_EXPLORE_FAR 
 Loaded for players with personality that has Exploration:Medium.
STRATEGY_EXPLORE_WIDE  
 Loaded for players with personality that has Exploration:Wide.
----------
STRATEGY_AGRESSIVE_START 
 Loaded for players with personality that has Exploration:Wide or Exploration: Medium.
STRATEGY_CAREFUL_START  
 Loaded for players with personality that has Exploration:Minimal.
----------
STRATEGY_SETTLE_COMPACT 
 Loaded for players with personality that has Expansion:Minimum.
STRATEGY_SETTLE_COMPACT 
 Loaded for players with personality that has Expansion:Average.
STRATEGY_SETTLE_LARGE 
 Loaded for players with personality that has Expansion:Maximum.

If it weren't for these, there would only be 5 personality types: the one's that correspond to the XXX_default strategies. What they do is allow for a differentiation of these 'default' personalities.

So, for example, Brian Boru (our example Stalin personality) will have the following strategies considered every NextStrategicState event: STRATEGY_EXPLORE_NEAR, STRATEGY_CAREFUL_START, STRATEGY_SETTLE_COMPACT. (Actually, the START strategies are only in effect for the first 80 turns.)

What happens (I'm almost positive) is that the game uses the ConsiderStrategicState function to overwrite the values in the initialized default strategy with the values in the above three strategies. For example, the MinSettleDistance 4 of STRATEGY_MILITARIST_DEFAULT is replaced by MinSettleDistance 5 of STRATEGY_SETTLE_COMPACT. Likewise for all the other data in the above three strategies so that what we end up with is a set of data that represents the distinctive Stalin personality profile.

4: Priority Based List Processing

In the chat mentioned above, MrOwl remarked that the AI used an 'established AI principle' which he described as "priority-based list processing (give me the best 15 things to do here)". This must be his own terminology because I haven't been able to find out anything about it through Google. However, you can see priorities all over the place and one place they're used is in:

VOID ConsiderStrategicState(player, priority, dbIndex)

Merge the Strategy whose StrategyDB index is 'dbIndex' into the player's initial (default) strategic state with a
priority of 'priority'. 

MrOgre describes this operation as follows:

When a new strategy is “considered” using ConsiderStrategicState, parameters defined by the new strategy record overwrite the values defined in the base strategy (set by SetStrategicState). As strategies are merged in values are overwritten in an order determined by their priority; lower priority strategic state records being overwritten by records that were considered with a higher priority. In this way you can have two handlers fire that load strategy records that modify the same parameter. The changes caused by the strategy record with the higher priority will be the one used.

This is how the AI adjusts to changing game situations. I just described how some of the strategies in strategies.txt are determined by the AI civ leader's personality. The others are loaded under circumstances that we largely don't know about. But here's an example in a reply by Richard Myers to a question of WesW's:

WW: Finally, at the bottom of the file, there is a list of Defense Strategies. What triggers these? I know nothing about when they come into effect, or how long they last.
RM: At the beginning of each turn, the Maximum Threat for an AI is computed, and as the threat increases, the minimum garrison in each city increases. Essentially this is a way to make sure the AI protects its cities with more units when things get more dangereous later in the game. If you add a new strategic state that is triggered every turn (with a priority > 1000 or so) which redefines the garrison counts, then you can use your own system.

The war strategies (STRATEGY_SEIGE, STRATEGY_ATTACK, STRATEGY_DEFEND, and STRATEGY_LAUNCH_NUKES) may be (in fact, I suspect must be) handled in some special way. I have my own system for building units and while developing it found myself in a situation where an AI civ should have been building cruise missiles but was building infantry units instead. They were coming from the data in STRATEGY_SEIGE, which I thought I had overridden. So I whipped out the offending data and my system is working fine (fingers crossed).

But, anyway, this is where things really do begin to be fun. You can write your own strategies (which can be almost any subset of the data in a default strategy) and then use the ConsiderStrategicState function in a NextStrategicState handler to get an AI civ to use them in whatever circumstances you care to define.

For example, it's the GOALS section of a strategy that determines how the AI moves it's units around the map. The priority field in each GoalElement sets a base priority for how much it would like to make a move of that kind. If it's an attacking move, however, the data in the FORCE MATCHING structures determine how strong the army has to be before it will actually make the move (which may be suicidal). So, if you wanted an AI civ to attack sort of like the Barbs, you could define a strategy like this:

STRATEGY_BANZAI {
      GoalElement { Goal GOAL_ATTACK     
       Priority   800000  MaxEval  25 MaxExec  25 }
 GoalElement { Goal GOAL_HARASS_CITY 
       Priority   800000  MaxEval  25 MaxExec  25 }
 GoalElement { Goal GOAL_HARASS      
       Priority   800000  MaxEval  25 MaxExec  25 }
 GoalElement { Goal GOAL_PILLAGE     
       Priority   800000  MaxEval  10 MaxExec   5 }
 GoalElement { Goal GOAL_SEIGE     
       Priority   790000  MaxEval  25 MaxExec  10 }
   
        Offensive {
  AttackMatch   0.0
  DefenseMatch  0.0
  RangedMatch   0.0
  BombardMatch  0.0
  ValueMatch    0.0
         }
        Defensive {
  AttackMatch   0.0
  DefenseMatch  0.0
  RangedMatch   0.0
  BombardMatch  0.0
  ValueMatch    0.0
 }
        Harass {
       AttackMatch   0.0
  DefenseMatch  0.0
  RangedMatch   0.0
  BombardMatch  0.0
  ValueMatch    0.0
        }
}

Then to get the AI to use it, you could write a handler like 'AI_Suicide_Attacks' which I've included in the attached StrategyExample.slc (so as not to screw up the width of this thread). Here if the AI civ is fighting a Human and has less than half the number of militaryunits that the Human has, rather than going defensive, it will go into Banzai tactics. (Actually, I think this would be uninteresting as gameplay; but it's an example.)

  Administration
This site is currently maintained by: BureauBert (Webmaster)  Maquiladora (Manager)  Legal Notice: Legal Notice Statistics: 82961 unique users since 02.05.2004 (» Site Statistics) Hosted by: WebhostOne
Share this page:  VK Facebook Twitter LiveJournal OK MR Google+ LinkedIn tumblr Pinterest Blogger Digg Evernote