OK here is the base sequence the AI goes through when choosing a GRAND STARTEGY
1. Get the base FLAVOUR for each strategy type (ie SCIENCE, CULTURE, DOIMATION and DIPLOMACY). Scale this by the base game FLAVOURS for each VICTORY type.
2. Determine the suitability for each VICTORY type, using a unique algorithm for each, based on current game factors (more on this later).
3. Modify the suitability of each victory type by the number of other players WE THINK are pursuing the same victory method. Essentially scales linearly between 100% (Ie no one else is pursuing this victory) to 50% (everyone is pursuing this victory method).
4. Add a BIAS for whatever the current strategy is so we don't chop and change easily.
FYI : I have added the code from the BNW codebase for both the base algorithm (ie above) and the code for each of the strategies (enumerated below). The code is at the end of the post.
Steps 1,3 and 4 are pretty straight forward, step 2 is more complex and breaks down as follows :
CONQUEST
0. Take the maximum FLAVOUR value out of DECEPTIVENESS, HOSTILITY and WARLIKENESS. Then subtract the FLAVOUR value for FRIENDLINESS.
1. Add the BOLDNES FLAVOUR for the AI.
2. Reduce likelihood by a small amount for each era that passes.
3. Modify by the ratio of the AI's MILITARY STRENGTH when compared to the WORLD AVERAGE.
4. Increase the likelihood if the AI is currently at WAR.
5. Increase the likelihood if the AI considers that there are to many RIVAL CIVS and not enough land (based on the number of players the AI has met, and the amount of land it has discovered).
6. If the AI has no nukes, but others do, decrease the likelihood.
CULTURE
1. Take the base FLAVOUR for CULTURE of this AI. (NOTE: This is the CULTURE FLAVOUR not the GRAND STARTEGY CULTURE FLAVOUR).
2. Increase it based on the era (earlier eras are worth more).
3. Increase priority for each CIV that is behind the AI in culture.
4. DECREASE priority for each CIV that is ahead of the AI in culture.
5. Increase priority for each CIV we are INFLUENTIAL over.
DIPLOMACY
1. Reduce priority for each MINOR attacked by the AI.
2. BEFORE leagues are available : Add the DIPLOMACY FLAVOUR and scale it by the era (earlier ERA's yield a higher scaling).
3. AFTER LEAGUES are available :
3a. If we have enough votes to WIN, switch to DIPLOMACY immediately.
3b. If we have 3/4 of the votes needed to win increase priority.
3c. If we have the most votes increase priority.
3d. If we do not have the most votes but could make them up from unallied city states then increase priority.
4. Increase priority for our CITY STATE FRIENDSHIP FLAVOUR and CITY STATE BONUS FLAVOUR.
5. Decrease priority for the AI's CITY STATE COMBAT FLAVOUR.
NOTE : I am assuming LEAGUEs is a reference to the WORLD CONGRESS but am not sure and do not have time to check right now.
SCIENCE
1. Start with the base AI FLAVOUR for SCIENCE (not the SV FLAVOUR).
2. Modify the chance based upon the number of eras that have passed (ie later eras increase the chance).
3. If the AI has built the APOLLO PROGRAM significantly increase the priority.
As you can see from this the SV is more of a default victory to use when none of the others apply. Earlier eras are going to favour culture, diplomacy and conquest, whilst later years favour science.
BTW I removed a couple of small steps from the above four algorithms for brevity. For example each strategy does a sanity check to make sure that the victory method is enabled for this game. There are a couple of other small checks here and there that I omitted. I do not believe this really makes much difference in the grand scheme of things but for those who are interested please review the code below.
Here is the code for those interested.
BASE ALGORITHIM
Code:
/// Runs every turn to determine what the player's Active Grand Strategy is and to change Priority Levels as necessary
void CvGrandStrategyAI::DoTurn()
{
DoGuessOtherPlayersActiveGrandStrategy();
int iGrandStrategiesLoop;
AIGrandStrategyTypes eGrandStrategy;
CvAIGrandStrategyXMLEntry* pGrandStrategy;
CvString strGrandStrategyName;
// Loop through all GrandStrategies to set their Priorities
for(iGrandStrategiesLoop = 0; iGrandStrategiesLoop < GetAIGrandStrategies()->GetNumAIGrandStrategies(); iGrandStrategiesLoop++)
{
eGrandStrategy = (AIGrandStrategyTypes) iGrandStrategiesLoop;
pGrandStrategy = GetAIGrandStrategies()->GetEntry(iGrandStrategiesLoop);
strGrandStrategyName = (CvString) pGrandStrategy->GetType();
// Base Priority looks at Personality Flavors (0 - 10) and multiplies * the Flavors attached to a Grand Strategy (0-10),
// so expect a number between 0 and 100 back from this
int iPriority = GetBaseGrandStrategyPriority(eGrandStrategy);
if(strGrandStrategyName == "AIGRANDSTRATEGY_CONQUEST")
{
iPriority += GetConquestPriority();
}
else if(strGrandStrategyName == "AIGRANDSTRATEGY_CULTURE")
{
iPriority += GetCulturePriority();
}
else if(strGrandStrategyName == "AIGRANDSTRATEGY_UNITED_NATIONS")
{
iPriority += GetUnitedNationsPriority();
}
else if(strGrandStrategyName == "AIGRANDSTRATEGY_SPACESHIP")
{
iPriority += GetSpaceshipPriority();
}
// Random element
iPriority += GC.getGame().getJonRandNum(/*50*/ GC.getAI_GS_RAND_ROLL(), "Grand Strategy AI: GS rand roll.");
// Give a boost to the current strategy so that small fluctuation doesn't cause a big change
if(GetActiveGrandStrategy() == eGrandStrategy && GetActiveGrandStrategy() != NO_AIGRANDSTRATEGY)
{
iPriority += /*50*/ GC.getAI_GRAND_STRATEGY_CURRENT_STRATEGY_WEIGHT();
}
SetGrandStrategyPriority(eGrandStrategy, iPriority);
}
// Now look at what we think the other players in the game are up to - we might have an opportunity to capitalize somewhere
int iNumPlayersAliveAndMet = 0;
int iMajorLoop;
for(iMajorLoop = 0; iMajorLoop < MAX_MAJOR_CIVS; iMajorLoop++)
{
if(GET_PLAYER((PlayerTypes) iMajorLoop).isAlive())
{
if(GET_TEAM(GetPlayer()->getTeam()).isHasMet(GET_PLAYER((PlayerTypes) iMajorLoop).getTeam()))
{
iNumPlayersAliveAndMet++;
}
}
}
FStaticVector< int, 5, true, c_eCiv5GameplayDLL > viNumGrandStrategiesAdopted;
int iNumPlayers;
// Init vector
for(iGrandStrategiesLoop = 0; iGrandStrategiesLoop < GetAIGrandStrategies()->GetNumAIGrandStrategies(); iGrandStrategiesLoop++)
{
iNumPlayers = 0;
// Tally up how many players we think are pusuing each Grand Strategy
for(iMajorLoop = 0; iMajorLoop < MAX_MAJOR_CIVS; iMajorLoop++)
{
if(GetGuessOtherPlayerActiveGrandStrategy((PlayerTypes) iMajorLoop) == (AIGrandStrategyTypes) iGrandStrategiesLoop)
{
iNumPlayers++;
}
}
viNumGrandStrategiesAdopted.push_back(iNumPlayers);
}
FStaticVector< int, 5, true, c_eCiv5GameplayDLL > viGrandStrategyChangeForLogging;
int iChange;
// Now modify our preferences based on how many people are going for stuff
for(iGrandStrategiesLoop = 0; iGrandStrategiesLoop < GetAIGrandStrategies()->GetNumAIGrandStrategies(); iGrandStrategiesLoop++)
{
eGrandStrategy = (AIGrandStrategyTypes) iGrandStrategiesLoop;
// If EVERYONE else we know is also going for this Grand Strategy, reduce our Priority by 50%
iChange = GetGrandStrategyPriority(eGrandStrategy) * /*50*/ GC.getAI_GRAND_STRATEGY_OTHER_PLAYERS_GS_MULTIPLIER();
iChange = iChange * viNumGrandStrategiesAdopted[eGrandStrategy] / iNumPlayersAliveAndMet;
iChange /= 100;
ChangeGrandStrategyPriority(eGrandStrategy, -iChange);
viGrandStrategyChangeForLogging.push_back(-iChange);
}
ChangeNumTurnsSinceActiveSet(1);
// Now see which Grand Strategy should be active, based on who has the highest Priority right now
// Grand Strategy must be run for at least 10 turns
if(GetActiveGrandStrategy() == NO_AIGRANDSTRATEGY || GetNumTurnsSinceActiveSet() >= /*10*/ GC.getAI_GRAND_STRATEGY_NUM_TURNS_STRATEGY_MUST_BE_ACTIVE())
{
int iBestPriority = -1;
int iPriority;
AIGrandStrategyTypes eBestGrandStrategy = NO_AIGRANDSTRATEGY;
for(iGrandStrategiesLoop = 0; iGrandStrategiesLoop < GetAIGrandStrategies()->GetNumAIGrandStrategies(); iGrandStrategiesLoop++)
{
eGrandStrategy = (AIGrandStrategyTypes) iGrandStrategiesLoop;
iPriority = GetGrandStrategyPriority(eGrandStrategy);
if(iPriority > iBestPriority)
{
iBestPriority = iPriority;
eBestGrandStrategy = eGrandStrategy;
}
}
if(eBestGrandStrategy != GetActiveGrandStrategy())
{
SetActiveGrandStrategy(eBestGrandStrategy);
m_pPlayer->GetCitySpecializationAI()->SetSpecializationsDirty(SPECIALIZATION_UPDATE_NEW_GRAND_STRATEGY);
}
}
LogGrandStrategies(viGrandStrategyChangeForLogging);
}
CONQUEST
Code:
/// Returns Priority for Conquest Grand Strategy
int CvGrandStrategyAI::GetConquestPriority()
{
int iPriority = 0;
// If Conquest Victory isn't even available then don't bother with anything
VictoryTypes eVictory = (VictoryTypes) GC.getInfoTypeForString("VICTORY_DOMINATION", true);
if(eVictory == NO_VICTORY || !GC.getGame().isVictoryValid(eVictory))
{
if(!GC.getGame().areNoVictoriesValid())
{
return -100;
}
}
int iGeneralWarlikeness = GetPlayer()->GetDiplomacyAI()->GetPersonalityMajorCivApproachBias(MAJOR_CIV_APPROACH_WAR);
int iGeneralHostility = GetPlayer()->GetDiplomacyAI()->GetPersonalityMajorCivApproachBias(MAJOR_CIV_APPROACH_HOSTILE);
int iGeneralDeceptiveness = GetPlayer()->GetDiplomacyAI()->GetPersonalityMajorCivApproachBias(MAJOR_CIV_APPROACH_DECEPTIVE);
int iGeneralFriendliness = GetPlayer()->GetDiplomacyAI()->GetPersonalityMajorCivApproachBias(MAJOR_CIV_APPROACH_FRIENDLY);
int iGeneralApproachModifier = max(max(iGeneralDeceptiveness, iGeneralHostility),iGeneralWarlikeness) - iGeneralFriendliness;
// Boldness gives the base weight for Conquest (no flavors added earlier)
iPriority += ((GetPlayer()->GetDiplomacyAI()->GetBoldness() + iGeneralApproachModifier) * (12 - m_pPlayer->GetCurrentEra())); // make a little less likely as time goes on
CvTeam& pTeam = GET_TEAM(GetPlayer()->getTeam());
// How many turns must have passed before we test for having met nobody?
if(GC.getGame().getElapsedGameTurns() >= /*20*/ GC.getAI_GS_CONQUEST_NOBODY_MET_FIRST_TURN())
{
// If we haven't met any Major Civs yet, then we probably shouldn't be planning on conquering the world
bool bHasMetMajor = false;
for(int iTeamLoop = 0; iTeamLoop < MAX_CIV_TEAMS; iTeamLoop++)
{
if(pTeam.GetID() != iTeamLoop && !GET_TEAM((TeamTypes) iTeamLoop).isMinorCiv())
{
if(pTeam.isHasMet((TeamTypes) iTeamLoop))
{
bHasMetMajor = true;
break;
}
}
}
if(!bHasMetMajor)
{
iPriority += /*-50*/ GC.getAI_GRAND_STRATEGY_CONQUEST_NOBODY_MET_WEIGHT();
}
}
// How many turns must have passed before we test for us having a weak military?
if(GC.getGame().getElapsedGameTurns() >= /*60*/ GC.getAI_GS_CONQUEST_MILITARY_STRENGTH_FIRST_TURN())
{
// Compare our military strength to the rest of the world
int iWorldMilitaryStrength = GC.getGame().GetWorldMilitaryStrengthAverage(GetPlayer()->GetID(), true, true);
if(iWorldMilitaryStrength > 0)
{
int iMilitaryRatio = (GetPlayer()->GetMilitaryMight() - iWorldMilitaryStrength) * /*100*/ GC.getAI_GRAND_STRATEGY_CONQUEST_POWER_RATIO_MULTIPLIER() / iWorldMilitaryStrength;
// Make the likelihood of BECOMING a warmonger lower than dropping the bad behavior
if(iMilitaryRatio > 0)
iMilitaryRatio /= 2;
iPriority += iMilitaryRatio; // This will add between -100 and 100 depending on this player's MilitaryStrength relative the world average. The number will typically be near 0 though, as it's fairly hard to get away from the world average
}
}
// If we're at war, then boost the weight a bit
if(pTeam.getAtWarCount(/*bIgnoreMinors*/ false) > 0)
{
iPriority += /*10*/ GC.getAI_GRAND_STRATEGY_CONQUEST_AT_WAR_WEIGHT();
}
// If our neighbors are cramping our style, consider less... scrupulous means of obtaining more land
if(GetPlayer()->IsCramped())
{
PlayerTypes ePlayer;
int iNumPlayersMet = 1; // Include 1 for me!
int iTotalLandMe = 0;
int iTotalLandPlayersMet = 0;
// Count the number of Majors we know
for(int iMajorLoop = 0; iMajorLoop < MAX_MAJOR_CIVS; iMajorLoop++)
{
ePlayer = (PlayerTypes) iMajorLoop;
if(GET_PLAYER(ePlayer).isAlive() && iMajorLoop != GetPlayer()->GetID())
{
if(pTeam.isHasMet(GET_PLAYER(ePlayer).getTeam()))
{
iNumPlayersMet++;
}
}
}
if(iNumPlayersMet > 0)
{
// Check every plot for ownership
for(int iPlotLoop = 0; iPlotLoop < GC.getMap().numPlots(); iPlotLoop++)
{
if(GC.getMap().plotByIndexUnchecked(iPlotLoop)->isOwned())
{
ePlayer = GC.getMap().plotByIndexUnchecked(iPlotLoop)->getOwner();
if(ePlayer == GetPlayer()->GetID())
{
iTotalLandPlayersMet++;
iTotalLandMe++;
}
else if(!GET_PLAYER(ePlayer).isMinorCiv() && pTeam.isHasMet(GET_PLAYER(ePlayer).getTeam()))
{
iTotalLandPlayersMet++;
}
}
}
iTotalLandPlayersMet /= iNumPlayersMet;
if(iTotalLandMe > 0)
{
if(iTotalLandPlayersMet / iTotalLandMe > 0)
{
iPriority += /*20*/ GC.getAI_GRAND_STRATEGY_CONQUEST_CRAMPED_WEIGHT();
}
}
}
}
// if we do not have nukes and we know someone else who does...
if(GetPlayer()->getNumNukeUnits() == 0)
{
for(int iMajorLoop = 0; iMajorLoop < MAX_MAJOR_CIVS; iMajorLoop++)
{
PlayerTypes ePlayer = (PlayerTypes) iMajorLoop;
if(GET_PLAYER(ePlayer).isAlive() && iMajorLoop != GetPlayer()->GetID())
{
if(pTeam.isHasMet(GET_PLAYER(ePlayer).getTeam()))
{
if (GET_PLAYER(ePlayer).getNumNukeUnits() > 0)
{
iPriority -= 50;
break;
}
}
}
}
}
return iPriority;
}
CULTURE
Code:
/// Returns Priority for Culture Grand Strategy
int CvGrandStrategyAI::GetCulturePriority()
{
int iPriority = 0;
// If Culture Victory isn't even available then don't bother with anything
VictoryTypes eVictory = (VictoryTypes) GC.getInfoTypeForString("VICTORY_CULTURAL", true);
if(eVictory == NO_VICTORY || !GC.getGame().isVictoryValid(eVictory))
{
return -100;
}
// Before tourism kicks in, add weight based on flavor
int iFlavorCulture = m_pPlayer->GetFlavorManager()->GetPersonalityIndividualFlavor((FlavorTypes)GC.getInfoTypeForString("FLAVOR_CULTURE"));
iPriority += (10 - m_pPlayer->GetCurrentEra()) * iFlavorCulture * 200 / 100;
// Loop through Players to see how we are doing on Tourism and Culture
PlayerTypes eLoopPlayer;
int iOurCulture = m_pPlayer->GetTotalJONSCulturePerTurn();
int iOurTourism = m_pPlayer->GetCulture()->GetTourism();
int iNumCivsBehindCulture = 0;
int iNumCivsAheadCulture = 0;
int iNumCivsBehindTourism = 0;
int iNumCivsAheadTourism = 0;
int iNumCivsAlive = 0;
for(int iPlayerLoop = 0; iPlayerLoop < MAX_CIV_PLAYERS; iPlayerLoop++)
{
eLoopPlayer = (PlayerTypes) iPlayerLoop;
CvPlayer &kPlayer = GET_PLAYER(eLoopPlayer);
if (kPlayer.isAlive() && !kPlayer.isMinorCiv() && !kPlayer.isBarbarian() && iPlayerLoop != m_pPlayer->GetID())
{
if (iOurCulture > kPlayer.GetTotalJONSCulturePerTurn())
{
iNumCivsAheadCulture++;
}
else
{
iNumCivsBehindCulture++;
}
if (iOurTourism > kPlayer.GetCulture()->GetTourism())
{
iNumCivsAheadTourism++;
}
else
{
iNumCivsBehindTourism++;
}
iNumCivsAlive++;
}
}
if (iNumCivsAlive > 0 && iNumCivsAheadCulture > iNumCivsBehindCulture)
{
iPriority += (GC.getAI_GS_CULTURE_AHEAD_WEIGHT() * (iNumCivsAheadCulture - iNumCivsBehindCulture) / iNumCivsAlive);
}
if (iNumCivsAlive > 0 && iNumCivsAheadTourism > iNumCivsBehindTourism)
{
iPriority += (GC.getAI_GS_CULTURE_TOURISM_AHEAD_WEIGHT() * (iNumCivsAheadTourism - iNumCivsBehindTourism) / iNumCivsAlive);
}
// for every civ we are Influential over increase this
int iNumInfluential = m_pPlayer->GetCulture()->GetNumCivsInfluentialOn();
iPriority += iNumInfluential * GC.getAI_GS_CULTURE_INFLUENTIAL_CIV_MOD();
return iPriority;
}
DIPLOMACY
Code:
/// Returns Priority for United Nations Grand Strategy
int CvGrandStrategyAI::GetUnitedNationsPriority()
{
int iPriority = 0;
PlayerTypes ePlayer = m_pPlayer->GetID();
// If UN Victory isn't even available then don't bother with anything
VictoryTypes eVictory = (VictoryTypes) GC.getInfoTypeForString("VICTORY_DIPLOMATIC", true);
if(eVictory == NO_VICTORY || !GC.getGame().isVictoryValid(eVictory))
{
return -100;
}
int iNumMinorsAttacked = GET_TEAM(GetPlayer()->getTeam()).GetNumMinorCivsAttacked();
iPriority += (iNumMinorsAttacked* /*-30*/ GC.getAI_GRAND_STRATEGY_UN_EACH_MINOR_ATTACKED_WEIGHT());
int iVotesNeededToWin = GC.getGame().GetVotesNeededForDiploVictory();
int iVotesControlled = 0;
int iVotesControlledDelta = 0;
int iUnalliedCityStates = 0;
if (GC.getGame().GetGameLeagues()->GetNumActiveLeagues() == 0)
{
// Before leagues kick in, add weight based on flavor
int iFlavorDiplo = m_pPlayer->GetFlavorManager()->GetPersonalityIndividualFlavor((FlavorTypes)GC.getInfoTypeForString("FLAVOR_DIPLOMACY"));
iPriority += (10 - m_pPlayer->GetCurrentEra()) * iFlavorDiplo * 150 / 100;
}
else
{
CvLeague* pLeague = GC.getGame().GetGameLeagues()->GetActiveLeague();
CvAssert(pLeague != NULL);
if (pLeague != NULL)
{
// Votes we control
iVotesControlled += pLeague->CalculateStartingVotesForMember(ePlayer);
// Votes other players control
int iHighestOtherPlayerVotes = 0;
for (int iPlayerLoop = 0; iPlayerLoop < MAX_CIV_PLAYERS; iPlayerLoop++)
{
PlayerTypes eLoopPlayer = (PlayerTypes) iPlayerLoop;
if(eLoopPlayer != ePlayer && GET_PLAYER(eLoopPlayer).isAlive())
{
if (GET_PLAYER(eLoopPlayer).isMinorCiv())
{
if (GET_PLAYER(eLoopPlayer).GetMinorCivAI()->GetAlly() == NO_PLAYER)
{
iUnalliedCityStates++;
}
}
else
{
int iOtherPlayerVotes = pLeague->CalculateStartingVotesForMember(eLoopPlayer);
if (iOtherPlayerVotes > iHighestOtherPlayerVotes)
{
iHighestOtherPlayerVotes = iOtherPlayerVotes;
}
}
}
}
// How we compare
iVotesControlledDelta = iVotesControlled - iHighestOtherPlayerVotes;
}
}
// Are we close to winning?
if (iVotesControlled >= iVotesNeededToWin)
{
return 1000;
}
else if (iVotesControlled >= ((iVotesNeededToWin * 3) / 4))
{
iPriority += 40;
}
// We have the most votes
if (iVotesControlledDelta > 0)
{
iPriority += MAX(40, iVotesControlledDelta * 5);
}
// We are equal or behind in votes
else
{
// Could we make up the difference with currently unallied city-states?
int iPotentialCityStateVotes = iUnalliedCityStates * 2;
int iPotentialVotesDelta = iPotentialCityStateVotes + iVotesControlledDelta;
if (iPotentialVotesDelta > 0)
{
iPriority += MAX(20, iPotentialVotesDelta * 5);
}
else if (iPotentialVotesDelta < 0)
{
iPriority += MIN(-40, iPotentialVotesDelta * -5);
}
}
// factor in some traits that could be useful (or harmful)
iPriority += m_pPlayer->GetPlayerTraits()->GetCityStateFriendshipModifier();
iPriority += m_pPlayer->GetPlayerTraits()->GetCityStateBonusModifier();
iPriority -= m_pPlayer->GetPlayerTraits()->GetCityStateCombatModifier();
return iPriority;
}
SCIENCE
Code:
/// Returns Priority for Spaceship Grand Strategy
int CvGrandStrategyAI::GetSpaceshipPriority()
{
int iPriority = 0;
// If SS Victory isn't even available then don't bother with anything
VictoryTypes eVictory = (VictoryTypes) GC.getInfoTypeForString("VICTORY_SPACE_RACE", true);
if(eVictory == NO_VICTORY || !GC.getGame().isVictoryValid(eVictory))
{
return -100;
}
int iFlavorScience = m_pPlayer->GetFlavorManager()->GetPersonalityIndividualFlavor((FlavorTypes)GC.getInfoTypeForString("FLAVOR_SCIENCE"));
// the later the game the greater the chance
iPriority += m_pPlayer->GetCurrentEra() * iFlavorScience * 150 / 100;
// if I already built the Apollo Program I am very likely to follow through
ProjectTypes eApolloProgram = (ProjectTypes) GC.getInfoTypeForString("PROJECT_APOLLO_PROGRAM", true);
if(eApolloProgram != NO_PROJECT)
{
if(GET_TEAM(m_pPlayer->getTeam()).getProjectCount(eApolloProgram) > 0)
{
iPriority += /*150*/ GC.getAI_GS_SS_HAS_APOLLO_PROGRAM();
}
}
return iPriority;
}
I hope this helps people decipher why AI's are choosing to do what.
FYI If you want to have an idea of what each AI is doing check their attitude to CS's. If they are attacking them then they are almost certainly following conquest. If they are bullying then they are not likely to be following CULTURE or DIPLOMACY and are more likely to be following CONQUEST. If they are PROTECTING CS's then they are likely following DIPLOMACY or CULTURE. The AI's GS SIGNIFICANTLY affects how it deals with CS's so they make a good barometer for AI GS interpretation. In particular the above applies to CS's that are CLOSE to the AI in question. Ones further away are less useful as barometers.