Another bad bit of code maybe -- CvTeamAI.cpp

Yakk

Cheftan
Joined
Mar 6, 2006
Messages
1,288
This is brought on by playing the AD1000 (or AD1001) scenerio: why don't the spanish kick the arabs out of spain? They have more than enough military strength to boot them out of spain itself -- heck, they could even push further if they where aggressive enough.

Using chipolte, I get that the Spanish stay on a defensive stance against the arabs. That isn't what we want.

So I'm doing code archeology
In AI_calculateAreaAIType:

Code:
		if (iAreaCities > 0)
		{
			if (countEnemyDangerByArea(pArea) > iAreaCities)
			{
				return AREAAI_DEFENSIVE;
			}
		}

Areas are, I think, continents.

countEnemyDangerByArea counts the number of enemy combat units in the area.

iAreaCities is:
Code:
		iAreaCities = countNumCitiesByArea(pArea);
the total number of cities owned by this "team" in the area.

So if there are more enemy combat units in an area than the team has cities, at that point we fall into defensive. We don't even examine if our military strength is stronger than theirs!

Now, we can avoid the above feature by earlier escapes:

We go over the other teams, and check for warplans with them.
If they attacked us recently, we bias towards being defensive.
If they have at least 1 city or 5 units on the area (read: continent), they are a valid war target for the area.

If we chose the war (ie, we where not attacked), and we either:
A> Just recently started the war (previous 10 turns), or
B> Have a sneak attack ready
then we have a HUGE bias towards an offensive area for the war (in fact, it is automatic!)

Outside of that, here is the main bit of code that can avoid the seeminly buggy AREAAI_DEFENSIVE decision:
Code:
		if (bTargets)
		{
			if ((countNumAIUnitsByArea(pArea, UNITAI_ATTACK) + countNumAIUnitsByArea(pArea, UNITAI_ATTACK_CITY) + countNumAIUnitsByArea(pArea, UNITAI_PILLAGE)) > (((AI_countMilitaryWeight(pArea) * iOffensiveThreshold) / 100) + 1))
			{
				return AREAAI_OFFENSIVE;
			}
		}

Breaking this down...

Total number of units on my side that are set to an aggressive AI setting:
(countNumAIUnitsByArea(pArea, UNITAI_ATTACK) + countNumAIUnitsByArea(pArea, UNITAI_ATTACK_CITY) + countNumAIUnitsByArea(pArea, UNITAI_PILLAGE))

Is at least 20 to 25% of the total military "weight" we have, plus 1:
> (((AI_countMilitaryWeight(pArea) * iOffensiveThreshold) / 100) + 1)

Military Weight is the sum over each player:
return (pArea->getPopulationPerPlayer(getID()) + pArea->getCitiesPerPlayer(getID()) + 1);

So (1+population) per city, plus 1 per player.

So two size 6 cities gives us a military weight of 15, which requires 4 aggressively AI'd units to be offensive in that area.

That seems ... somewhat strange. We are making a rather key decision (be aggressive or defensive) depending on the fraction of local team units who think they should attack to local team cities.

And once we pass that test, we tutle if there are more enemy units than our team has cities -- which is a pretty ridiculous rule!

...

While I see a lot of "team" calculations here, I don't see it taking into account the local strength of other people at war with the enemy civilizations.

I'm thinking something along the lines of:
Estimated Enemy Strength in Area
------------------------------------------
Estimated Enemy of Enemy Strength in Area

as being a key parameter -- if the enemy is outnumbered by it's enemies, then we can be more confident about being aggressive towards it.

...

Another possible culprit is :
void CvUnitAI::AI_attackMove()
from CvUnitAI.cpp -- in it, you'll notice that the first option is "check if the city has enough protectors". If that is overly strong, the AI won't use it's knights to take out the city in southern spain.

Total number of units on my side that are set to an aggressive AI setting:
(countNumAIUnitsByArea(pArea, UNITAI_ATTACK) + countNumAIUnitsByArea(pArea, UNITAI_ATTACK_CITY) + countNumAIUnitsByArea(pArea, UNITAI_PILLAGE))

Is at least 20 to 25% of the total military "weight" we have, plus 1:
> (((AI_countMilitaryWeight(pArea) * iOffensiveThreshold) / 100) + 1)

Military Weight is the sum over each player:
return (pArea->getPopulationPerPlayer(getID()) + pArea->getCitiesPerPlayer(getID()) + 1);

So (1+population) per city, plus 1 per player.

So two size 6 cities gives us a military weight of 15, which requires 4 aggressively AI'd units to be offensive in that area.

Hmm. That doesn't seem to be the problem.

I'm gonna keep poking at this. :)

It makes a good case study: the correct option for the Spanish is an all-out assault on Cordoba, which the Spanish win. In general, if Europe does an all-out assault on Byzantium/Arabs, they come out ahead -- otherwise, the Arabs continue to dominate.
 
Aha! I figured out where the Spanish decide to not attack and instead turtle.

Look at this:

Planning: AI_guardCity:Knight(11)
Information about guarding city:Barcelona bLeave:1 bSearch:0 iMaxPath:2147483647
-6 extra units required because city is in danger, or the unit is not planning on leaving
The city (Barcelona) is under garrisoned...

That -6 is a calculation from the Danger Plot function.

Basically, the city danger works out to:
Every hostile owned square next to the city and every hostile owned square with a route within 2 units of the city, plus every hostile unit within 2 units of the city.

In this case, we get 2 archers defending Cordoba and another 4 points worth of border danger, totaling 6 danger units in Barcelona.

This results in the AI saying "I need the usual sized garrison, PLUS 6!!!".

In general, this will lead to the AI sitting units in the city rather than attacking out of the city: the "does this city need more defenders" code is run before nearly any unit is allowed to move.

In essence, whenever you move a stack of units near an enemy city, you paralyze any units in the city, as they paranoidly sit there saying "I can't leave! We could be attacked! Eeeek!".

I'm trying some improvements -- my first pass is to pay attention to how strong the threatening stack is instead of just how many units there are.
 
So my fix was two-fold.

First, I changed the border boost from (count of enemy tiles) to (count of enemy tiles+1)/2, rounded down.

This gives the same boost for something that is just barely on the border, and doesn't generate the insane boost for something that is completely on the border -- honestly, at some point, you gotta let go. ;)

The advantage of this is that it frees up units for other purposes.

The next change was teaching it to ignore units that really aren't a threat.

I take the sum of current strength in the city tile, divide by the number of units in the current tile, generating an average-strength-per-unit in the current tile.

Then, when we are trying to figure out the danger from other units, we sum up their strength and divide it by the average strength of a unit from the current tile.

The result is the AI looks at two measly archers near the city, and says "that really isn't a danger worth allocating two units for. I've got longbows."

In the AD 1000 scenerio, the overall result was a bunch more conquest, because more units where freed up for fighting and fewer where locked down by sitting around guarding cities.

In my debugging, I discovered that the AI really spams out "is there danger for this tile". If we have the memory, I could see building a "danger-map of the world" might actually speed up the AI.

Idea:
We build a danger-map of the world.
Each square has the following information:
A> Enemy defensive/offensive threat that we know about that can reach the square in 0, 1 and 2 turns.
B> Our defensive/offensive power we can bring to bear on the square in 0, 1 and 2 turns. In both cases, defensive cost (how much defensive power do we lose somewhere else?) is maintained.
C> A guesstimate enemy danger, based off of enemy power and proximity to enemy terrain and/or cities (ie, we presume that enemy cities contain

This is much harder, but might generate more sensible responses by the AI than the current heuristics...

Building the world-chart could take O(n^3) in the size of the map. That isn't good.

One could punt, and ignore special unit movement abilities, and store "ring-limits" for each node on the map (ie, you can get this far with 1 movement point, this far with 2, etc).

Each team would have to have a different ring-path sometimes.

A K move ring-pathing would take O(X*TransportSpeed), summed from 1 to 8. That works out to O(K^2). For K=6 (up to 6 movement points!) and TransportSpeed=4, that hits O of 576. Blast -- on a 1000 x 1000 map, that will get memory expensive.

Different movement promotion combinations require different ring-pathing.

Both "how far can a unit here go" and "how far away can a unit get here" could be useful. They are both very similar in structure.

A quick test to see if one is within a ring-path of either of those would allow incremental updating whenever the route or terrain information on a square changes.

I guess we are stuck with heuristics. :)
 
Great find. I'm surprised nobody else has commented on this yet. The AI is poor when it comes to determining how to handle its military, and your observation of the paralyzation of the enemy when you move a large stack of units towards its city is correct.

I hope they stamp this issue out with BTS.
 
Screw BTS, I managed to make the Spanish take Cordoba myself. :)

So, a heuristic for the next step: Stack Warfare.

Stack Warfare takes our stack and an enemy stack.

It asks the question:
If I destroy that stack, what defense will I need here?
It then figures out how little of the current stack it can afford to leave behind. This generates a possible offensive stack. (these are relatively quick operations)

Next, it does a quick heuristic "does my possible offensive stack stand a chance against that stack". If that succeeds (ie, our offensive stack isn't teeny tiny puny compared to the attacker), it does three combat simulations (not odds calculations, just simulations -- simulations are quick) between the offensive stack and the target stack.

If at least 2 of those simulations "succeed" (P(2 victories in 3 simulations) = P(victory)^2*P(defeat)*3. P(3 victories in 3 simulations) = P(victory)^3), we commit and attack.

The result:

If you move an attacking stack near an enemy city, it will think "Hmm, can I crush that? Well, I need some defenses afterwards... But even taking that into account, I have some excess units. Those excess units -- are they anywhere close to being a challenge to the enemy stack? Hmm, yes, they are close. Ok, let's do some more work -- wow, I have at least some chance of beating the enemy! Ok, KILL!"

Hmm. Exploit: send bait near enemy. Let enemy attack it. Smash with overwealming force. Retreat and repeat.

So additional factors needed:
1> Material cost ratio. How much power will I lose, and how much power will they lose?
2> Empire production ratios. Can I afford that even a poor tradeoff, given my much larger production capacity?
3> Ambush detection. After we smash the target area, what is the estimated threat level, and can we defend the smash-attack stack? If not, is it worth risking the sacrafice of the smash-attack for smashing the attacking stack?

That would make the enemy far more responsive to being attacked. :)
 
I tend to find that one of the main difficulties is that the units have to do their thing in some arbitrary order. So you'd have to calculate this at the turn of the first possible unit for your stack, and then press-gang them all into one group. It's doable and it goes without saying that I'd like to see the code.

As for AI_calculateAreaAIType, I can see what it's trying to do. The right hand side of the AREAAI_OFFENSIVE calculation is an estimate of your production capacity. So if we were MASSING instead, we wouldn't have to wait too long before going on the attack. So if we have enough units to attack, attack else if the enemy has any presence at all in our territory, build defences and hope to counter attack and drive them off, and finally, if neither applies, build attacking units until we have enough to do something.

Being AREAAI_DEFENSIVE, means building reserve, city counter and collateral AITYPE units which should be decent counter-attackers. If you can get them to do that properly, it wouldn't be a bad strategy.

I suppose it might make sense to move the current AREAAI_DEFENSIVE check to the end and replace it with one that checks how many defensive units we have before panicking.
 
A way that could be done to eliminate the exploit you mentioned is to only use units to attack that could retreat back to the city. That would be humans way of attacking an invading stack. Basically if the stack is within 1 square of the city and on a road, use basically every good percentage unit possible, but if the distance is further away, putting a strong emphasis on using mounted units (this is even better because I think almost all mounted units lack defensive modifiers anyway).
 
Back
Top Bottom