AIs and the Art of War

I've just taken two medieval/renaissance cities with my industrial army, and it seems they don't build defensive units anymore. They had 400+% fortifications, but once I got that down, it was all over: 1 organ gun, 1 crusader, and a half-dozen law enforcement and health units.

While I was besieging, they were building... geology labs!!!

While I did have tech superiority, they could have had a stack of CG3 something-or-others, and I might have at least lost some riflemen, rather than about two siege rams.

I really hope this isn't the AI's response to the new siege warfare system...
It's not that... it's really just that the AI flat out isn't terribly well developed for warfare strategies at this point. I PLAN to work in a lot of improvements in the not too distant future. There flat out is nothing more complex in the code than the AI so this is not to be done lightly. A full understanding needs to be earned before anything but small tweaks are introduced. This kind of decision making they do in these cases is deep in complex-land.

A while back, I had a large barb stack come for one of my cities, but the shortest path meant coming via a desert tile next to the city (with terrain damage on). After a turn on that tile, it seems they were deciding they were no longer strong enough to attack. They never attacked. They just went back and forth between the desert tile and the one beyond it. For centuries! Of course eventually I built a tech-superior force to [go the long way round and] clean them up.

This was early game. Later on you get the same effect from city defence buildings (ie. traps etc.)

Having decided they are no longer strong enough for the assault, it would be nice if they could learn to either: go away/make peace; or: calculate how many reinforcements are needed, and build them. But I think maybe as soon as they heal up, they've forgotten what happened and think they're strong enough again...
You're probably right... but as a human your thinking of a bigger picture than the AI can easily be taught. Again... deep in the labyrinth are these kinds of decisions to be made and the more intelligent you make the AI the slower the game will run. But I do wish to follow some of Koshling's advice on these matters and eventually this might be improved. Very difficult stuff...
 
Right now, also, the AI tends to stop and heal up when any single unit in it's group is damaged, unless abandoning it's original plan and splitting the group.
So with Archer Ranged attack you can effectively stop a huge stack dead in it's track, as long as the top units can not easily beat the archer or archers down in a fair fight (hill, forest).

A simple ability to sidestep obstacles thrown in its way, archer damage or terrain damage, would go a long way in making the AI slightly more effective, though how to with deciding what percent damage is considered safe in what scenario is probably really hard.

Cheers
 
Currently AI is almost 100% impotent when it comes to regarding war. It is a step in a worse direction compared to how things were maybe ½year-year ago. In my latest deity game I haven't seen any cities exchange owners. Not even AI attacking a barb city which is near its border and the game is in middle ages now. What I have seen is the AI building big stacks comprised almost totally of not very useful attack units, for example javelineer (early in the game) or longbowman. No plans for attacking and conquering cities.
 
In my last three test games I have noticed something about the AI players and expansion, as soon as I clear the barbarian cities from near their nations they start to expand.

In each case the nations concerned were not tech leaders but they settled three or more cities in quick succession after I removed the barbs. Could this failure to expand have anything to do with the fact that the barbarians have more advanced units than the AI nations? Don't the barbs now have similar tech level to the leader? Also when the AI nations look at the barbarian "threat" do they only look local or does every barbarian unit in the world count?

When testing I get a stack of 5 to 15 Bison Riders, if possible, to go round and remove the barbarian threat. I came across one barbarian city in the arctic that had three stacks of units. It took about 10 attacks with time spent healing for the 15 to remove those three stacks. There are only 7 Bison Riders now, I forgot to send a healer along with them. That means that that one city in the arctic had about 70-100 units, mostly archers. I should have made a save for analysis but it was past midnight.
 
In my last three test games I have noticed something about the AI players and expansion, as soon as I clear the barbarian cities from near their nations they start to expand.

In each case the nations concerned were not tech leaders but they settled three or more cities in quick succession after I removed the barbs. Could this failure to expand have anything to do with the fact that the barbarians have more advanced units than the AI nations? Don't the barbs now have similar tech level to the leader? Also when the AI nations look at the barbarian "threat" do they only look local or does every barbarian unit in the world count?

When testing I get a stack of 5 to 15 Bison Riders, if possible, to go round and remove the barbarian threat. I came across one barbarian city in the arctic that had three stacks of units. It took about 10 attacks with time spent healing for the 15 to remove those three stacks. There are only 7 Bison Riders now, I forgot to send a healer along with them. That means that that one city in the arctic had about 70-100 units, mostly archers. I should have made a save for analysis but it was past midnight.
Alberts observed earlier that settlers were appearing a bit too sensitive to local threat levels. Unfortunately those threat levels from nearby barb cities aren't apparently triggering the AI to do something aggressive about it towards the barbs so as to clear the way. This will certainly need to be addressed as time permits.
 
Alberts observed earlier that settlers were appearing a bit too sensitive to local threat levels. Unfortunately those threat levels from nearby barb cities aren't apparently triggering the AI to do something aggressive about it towards the barbs so as to clear the way. This will certainly need to be addressed as time permits.

I am thinking perhaps the AI should take the "sea turtle method". Send out lots of Settlers and some are bound to make a new city. Sure many will get picked off but its better than keeping them all safe in your first city and never expand at all.

Note this is not the best use of resources but it would get the AI to be able to expand more than it currently does.
 
They were doing that but we asked Koshling to make some adjustments so they'd not send settlers into warzones which they were doing constantly. Since then this has been the issue.
 
Noticed a (new?) (SVN) issue where AI Wanderers just sit on a tilwe when I have a Spiked Clubman ready to hit them on the next turn. They have the opportunity to move to safety but don't seem to take the chance
 
The AI is a pushover. When I'm taking their cities, they'll have stacks of units in the outside squares and even as I'm attacking their city, they leave their units outside. I thought maybe they were doing this because of the surround and destroy option (they didn't want to be flanked), but they'll do this even with that option turned off.
 
I'm currently looking into developing a new Healer AI and some functions to help the AI utilize the new Build Up and other alternative Fortification missions.

I've started by evaluating the City Defense AI routine with the intention to see how and when they are establishing themselves as fortified. Starting from the beginning, though, has the benefit of showing me why some of the stupid decisions the AI are making are taking place.

I wanted to take a moment to point out some of what I THINK I've found may be some flaws (perhaps serious ones) and before making any kind of adjustment, asking the community, particularly those that understand the AI and C++ (Alberts2, Afforess, Koshling, AIAndy etc...) to concur or offer counter-observations and perhaps explanations as to how I may be seeing them wrong. This is a place to be very careful so I'm seeking collaboration on anything I'd look to adjust here.

So far, following up to this point:
Code:
void CvUnitAI::AI_cityDefenseMove()
{
	PROFILE_FUNC();

	if ( checkSwitchToConstruct() )
	{
		return;
	}
	
/************************************************************************************************/
/* BETTER_BTS_AI_MOD                      08/20/09                                jdog5000      */
/*                                                                                              */
/* Unit AI, Efficiency                                                                          */
/************************************************************************************************/
	//bool bDanger = (GET_PLAYER(getOwnerINLINE()).AI_getPlotDanger(plot(), 3) > 0);
	bool bDanger = (GET_PLAYER(getOwnerINLINE()).AI_getAnyPlotDanger(plot(), 3));
/************************************************************************************************/
/* BETTER_BTS_AI_MOD                       END                                                  */
/************************************************************************************************/

/************************************************************************************************/
/* BETTER_BTS_AI_MOD                      09/18/09                                jdog5000      */
/*                                                                                              */
/* Settler AI                                                                                   */
/************************************************************************************************/

	if ( MISSIONAI_REGROUP == getGroup()->AI_getMissionAIType() )
	{
		if (AI_group(UNITAI_SETTLE, 2, -1, -1, false, false, false, 1, true))
		{
			return;
		}
And moving through AI_group(etc...):
Code:
bool CvUnitAI::AI_group(UnitAITypes eUnitAI, int iMaxGroup, int iMaxOwnUnitAI, int iMinUnitAI, bool bIgnoreFaster, bool bIgnoreOwnUnitType, bool bStackOfDoom, int iMaxPath, bool bAllowRegrouping, bool bWithCargoOnly, bool bInCityOnly, MissionAITypes eIgnoreMissionAIType)
{
	PROFILE_FUNC();

	CvUnit* pLoopUnit;
	CvUnit* pBestUnit;
	int iPathTurns;
	int iValue;
	int iBestValue;
	int iLoop;
	bool bCanDefend = getGroup()->canDefend();

	// if we are on a transport, then do not regroup
	if (isCargo())
	{
		return false;
	}

	if (!bAllowRegrouping)
	{
		if (getGroup()->getNumUnits() > 1)
		{
			return false;
		}
	}
	
	if ((getDomainType() == DOMAIN_LAND) && !canMoveAllTerrain())
	{
		if (area()->getNumAIUnits(getOwnerINLINE(), eUnitAI) == 0)
		{
			return false;
		}
	}

	if (!AI_canGroupWithAIType(eUnitAI))
	{
		return false;
	}

	if ( GET_PLAYER(getOwnerINLINE()).AI_getNumAIUnits(eUnitAI) == 0 )
	{
		return false;
	}

	int iOurImpassableCount = 0;
	CLLNode<IDInfo>* pUnitNode = getGroup()->headUnitNode();
	while (pUnitNode != NULL)
	{
		CvUnit* pImpassUnit = ::getUnit(pUnitNode->m_data);
		pUnitNode = getGroup()->nextUnitNode(pUnitNode);

		iOurImpassableCount = std::max(iOurImpassableCount, GET_PLAYER(getOwnerINLINE()).AI_unitImpassableCount(pImpassUnit->getUnitType()));
	}

	iBestValue = MAX_INT;
	pBestUnit = NULL;

	CvReachablePlotSet plotSet(getGroup(), bCanDefend ? 0 : MOVE_OUR_TERRITORY, AI_searchRange(iMaxPath));

	// Loop over groups, ai_allowgroup blocks non-head units anyway
	CvSelectionGroup* pLoopGroup = NULL;
	for(pLoopGroup = GET_PLAYER(getOwnerINLINE()).firstSelectionGroup(&iLoop); pLoopGroup != NULL; pLoopGroup = GET_PLAYER(getOwnerINLINE()).nextSelectionGroup(&iLoop))
	{
		pLoopUnit = pLoopGroup->getHeadUnit();
		if( pLoopUnit == NULL )
		{
			continue;
		}

		CvPlot* pPlot = pLoopUnit->plot();
		if (/*AI_plotValid(pPlot) &&*/ plotSet.find(pPlot) != plotSet.end())
		{
			if (iMaxPath > 0 || pPlot == plot())
			{
				if (!isEnemy(pPlot->getTeam()))
				{
					if (AI_allowGroup(pLoopUnit, eUnitAI))
To AI_allowGroup(etc...) then following that function:
Code:
bool CvUnitAI::AI_allowGroup(const CvUnit* pUnit, UnitAITypes eUnitAI) const
{
	CvSelectionGroup* pGroup = pUnit->getGroup();
	CvPlot* pPlot = pUnit->plot();

	if (pUnit == this)
	{
		return false;
	}

	if (!pUnit->isGroupHead())
	{
		return false;
	}

	//	Don't join a unit that was itself wondering what to do this turn
	if ( (static_cast<const CvUnitAI*>(pUnit))->m_contractsLastEstablishedTurn == GC.getGameINLINE().getGameTurn() &&
		 (m_contractualState == CONTRACTUAL_STATE_AWAITING_WORK || m_contractualState == CONTRACTUAL_STATE_NO_WORK_FOUND))
	{
		return false;
	}

	if (pGroup == getGroup())
	{
		return false;
	}

	if (pUnit->isCargo())
	{
		return false;
	}

	if (pUnit->AI_getUnitAIType() != eUnitAI)
	{
		return false;
	}

	switch (pGroup->AI_getMissionAIType())
	{
	case MISSIONAI_GUARD_CITY:
		// do not join groups that are guarding cities
		// intentional fallthrough
	case MISSIONAI_LOAD_SETTLER:
	case MISSIONAI_LOAD_ASSAULT:
	case MISSIONAI_LOAD_SPECIAL:
		// do not join groups that are loading into transports (we might not fit and get stuck in loop forever)
		return false;
		break;
	default:
		break;
	}

	if (pGroup->getActivityType() == ACTIVITY_HEAL)
	{
		// do not attempt to join groups which are healing this turn
		// (healing is cleared every turn for automated groups, so we know we pushed a heal this turn)
		return false;
	}

	if (!canJoinGroup(pPlot, pGroup))
to canJoinGroup(etc...) and following that to
Code:
bool CvUnit::canJoinGroup(const CvPlot* pPlot, CvSelectionGroup* pSelectionGroup) const
{
	CvUnit* pHeadUnit;
	
	// do not allow someone to join a group that is about to be split apart
	// this prevents a case of a never-ending turn
	if (pSelectionGroup->AI_isForceSeparate())
	{
		return false;
	}
	
	if (pSelectionGroup->getOwnerINLINE() == NO_PLAYER)
	{
		pHeadUnit = pSelectionGroup->getHeadUnit();

		if (pHeadUnit != NULL)
		{
			if (pHeadUnit->getOwnerINLINE() != getOwnerINLINE())
			{
				return false;
			}
		}
	}
	else
	{
		if (pSelectionGroup->getOwnerINLINE() != getOwnerINLINE())
		{
			return false;
		}
	}

	if (pSelectionGroup->getNumUnits() > 0)
	{
		if (!(pSelectionGroup->atPlot(pPlot)))
		{
			return false;
		}

		//	Can't join a group that is loaded onto a transport as this
		//	would bypass the transport's record of what units it has on
		//	board
		if (pSelectionGroup->getHeadUnit()->isCargo())
		{
			if(pSelectionGroup->getHeadUnit()->isHuman())
			{
				if(pSelectionGroup->getHeadUnit()->getTransportUnit() == getTransportUnit())
				{
					return true;
				}
			}
			return false;
		}

		if (pSelectionGroup->getDomainType() != getDomainType())
		{
			return false;
		}
	}

	return true;
Apparently, based on some evaluating I've been doing on how group counts can exceed unit counts, a group can't be eliminated until the end of the round after its last unit has died, at which point it THEN clears out but until then that means there can be a hollow group shell with an AI setting and all based on the unit that died that used to belong to it.

So with this being possible, does this last bit mean that if a group doesn't have any units in it at all then it can easily default to being capable of accepting a new member merging into it?

Could this be one of the explanations as to why we have a number of healers standing around doing very little? Healers may be selected as city defense units (which is one thing I'm also looking to fix by generating a new ai for them) so may find themselves joining a recently killed settler stack (of which there's only ever one unit to guard them?!? No wonder they're getting killed out there with just one healer to guard them as they try to get to the city site!) that doesn't even have a settler in it - so then mulls about wondering how to resolve itself?

Perhaps here we could solve a few issues by making a group with 0 units return a false right away so it is then destined to be cleaned out of the system rather than ever having a unit join it and reinvigorate it with new purposelessness?

Does anyone concur or am I reading anything wrong here?

EDIT: Testing found this to be a somewhat incorrect assertion - it keeps units from being able to join groups they generate for themselves so is a major problem to try to resolve this in this manner. I still think it may be possible that empty groups are potentially being joined here unintentionally but it's probably not often and I'm not sure how one would keep that from happening since an empty group must be joinable for a unit to generate one for itself and join it.


And additionally, I'd like to at least enforce 2 units to protect a settler so would changing this:
Code:
if (AI_group(UNITAI_SETTLE, 2, -1, -1, false, false, false, 1, true))
to this:
Code:
if (AI_group(UNITAI_SETTLE, 3, -1, -1, false, false, false, 1, true))
at least be one of the steps necessary in this?

One thing I'm also wondering and I'll eventually find it with further analysis I'm sure but in general terms, how does a group get set to MISSION_REGROUP? Is this done by the contract broker, when the unit is trained, or only after it's somehow lost it's previous group and it's focus somehow? (or other?)

Anyhow, as I said, I'm being very patient and thorough with this process and I'm only spotting a few things along the way. For the most part I'm just trying to learn the existing structures so I can more readily create a new one that works very appropriately - but if we can fix some issues as we go that'd be cool too considering how desperately awful some of the AI decisions have been of late.
 
Point 2)

Additionally:
Code:
bool CvUnitAI::AI_guardCityMinDefender(bool bSearch)
{
	PROFILE_FUNC();
	
	CvCity* pPlotCity = plot()->getPlotCity();
	if ((pPlotCity != NULL) && (pPlotCity->getOwnerINLINE() == getOwnerINLINE()))
	{
		int iCityDefenderCount = pPlotCity->plot()->plotCount(PUF_isUnitAIType, UNITAI_CITY_DEFENSE, -1, getOwnerINLINE());
		if ((iCityDefenderCount - 1) < pPlotCity->AI_minDefenders())
		{
			if ((iCityDefenderCount <= 2) || (GC.getGame().getSorenRandNum(5, "AI shuffle defender") != 0))
			{
				getGroup()->pushMission(MISSION_SKIP, -1, -1, 0, false, false, MISSIONAI_GUARD_CITY, NULL);
I have two questions/propositions:
1) WHY does the AI NEVER use MISSION_FORTIFY ? Is there supposed to be a place where if the unit is an AI and it has skipped then it selects an appropriate manner in which it should be sleeping? (I'm still looking into this.)

EDIT: Looks like I answered this question - it appears to me that the AI would only need to skip a unit's turn for it to gain a fortification round and that if a unit CAN fortify and it hadn't moved and thus has fortification rounds, by default it would have a fortification combat modifier gain. This is probably broken at the moment so I'm fixing this up in a more appropriate manner now.

Still... it strikes me that by skipping every round this means the AI has to constantly re-evaluate this unit's decisions EVERY round. How much do you figure we could speed up turn times by having units that were evaluated to be the best defender in the city and those filling the minimum defense role go ahead and fortify and be then be woken up by any unit that replaces them in that seat? hmm...


2) I would LOVE to kill this whole line:
Code:
if ((iCityDefenderCount <= 2) || (GC.getGame().getSorenRandNum(5, "AI shuffle defender") != 0))
as it explains why the city is so happy to leave itself with so few solid defenders. 3-4 at minimum seems fair.


And here:
Code:
int CvCityAI::AI_minDefenders()
{
	int iDefenders = 1;
	int iEra = GET_PLAYER(getOwnerINLINE()).getCurrentEra();
	if (iEra > 0)
	{
		iDefenders++;
	}
	if (((iEra - GC.getGame().getStartEra() / 2) >= GC.getNumEraInfos() / 2) && isCoastal(GC.getMIN_WATER_SIZE_FOR_OCEAN()))
	{
		iDefenders++;
	}
/********************************************************************************/
/* 	City Defenders						24.07.2010				Fuyu			*/
/********************************************************************************/
	if (getProductionUnitAI() == UNITAI_SETTLE)
	{
		iDefenders++;
	}
/********************************************************************************/
/* 	City Defenders												END 			*/
/********************************************************************************/

	return iDefenders;
}
I'd like to make:
Code:
	if (iEra > 0)
	{
		iDefenders++;
	}
Code:
	iDefenders += iEra;
instead.
And
Code:
	if (getProductionUnitAI() == UNITAI_SETTLE)
	{
		iDefenders++;
	}
should be iDefenders += 2 if we want to make sure we're sending 2 defenders off with the settler right?


Of course, all this is somewhat predicated on changes to the generic system. I have some ideas that I'd like to package into a game option for testing before full implementation. Might be a good time to start putting this option together anyhow.
 
I'd like to make:
Code:
	if (iEra > 0)
	{
		iDefenders++;
	}
Code:
	iDefenders += iEra;
instead.

The first gives you an extra defender per era. The second gives iEra factorial. So in iEra =0 (Prehistoric) you would have zero defenders, iEra=1 then 1 defender; ... iEra=4 you would have 4+3+2+1+0 = 10 defenders. iEra=9 (future) then 9+8+7+6+5+4+3+2+1+0 or 41 defenders. Is that what you are after?
 
Incorrect. The first, since we're not looping through all eras and adding one each cycle, is only checking if the era is greater than prehistoric and if so add another defender to the amount considered to be the minimum.

The second would give an extra defender per era.

The commit currently going in settled on 1 (if after prehistoric) + 1/3d the iEra(which naturally rounds down) so +1 per 3 eras.
 
And additionally, I'd like to at least enforce 2 units to protect a settler so would changing this:
Code:
if (AI_group(UNITAI_SETTLE, 2, -1, -1, false, false, false, 1, true))
to this:
Code:
if (AI_group(UNITAI_SETTLE, 3, -1, -1, false, false, false, 1, true))
at least be one of the steps necessary in this?

This seems wrong but as always i might be wrong too. I have to looks at this and the other stuff later after work.
 
2) I would LOVE to kill this whole line:
as it explains why the city is so happy to leave itself with so few solid defenders. 3-4 at minimum seems fair.

I think it should be scaled to
a) city size
b) location

Something like:

2 +rounded up (citysize /5) and *3 if boarder city.

A size 10 boarder city should have 12 defender then, which seems ok.
A size 30 city would get 8 and 24 if it's a boarder city.
 
I think it should be scaled to
a) city size
b) location

Something like:

2 +rounded up (citysize /5) and *3 if boarder city.

A size 10 boarder city should have 12 defender then, which seems ok.
A size 30 city would get 8 and 24 if it's a boarder city.

There's consideration for naval vulnerability but once I figure out how to define a border city in a halfway accurate manner then I'd love to add one or two there and pull back a bit on the interior cities.

City size may be a valid cause for more but there IS some consideration for happiness needs which may naturally bring about some of that.

I'd also like to get the AI to build a fair sized defensive response stack that will always seek to go to the most threatened city. But this may require a special AI since city defense AIs apparently never group themselves (unless it's to accompany a settler) which makes some sense from what I've seen so far.

Another thing I've noticed is that City Counter really needs further evaluating. This is where they're supposed to be getting their spearmen and I'm wondering how effective the mechanism is right now.

Regardless... my next task is to develop healer and property control specific AIs.
 
Just playing a large game and notice in World Builder that the AI seesm unable to take Barbarian Cities......this might be limiting some from exapnading at all
 
Just playing a large game and notice in World Builder that the AI seesm unable to take Barbarian Cities......this might be limiting some from exapnading at all

Yeah there's certainly work to be done on their attack mechanisms and I've also noticed they never seem to try to take on Barb cities so some high level decisions to do so may be in order there too. Koshling noted a while back that the high level decision making was a bit lacking - I'd say he's right. They need to define targets and send their invasion forces with much more efficacy.
 
Ok i found the right spot to give settlers more units as escort and the reason why they always started moving as soon as one unit joined them. But how strong should the escort be at the moment it is
Code:
2*GET_PLAYER(getOwnerINLINE()).strengthOfBestUnitAI(DOMAIN_LAND, UNITAI_CITY_DEFENSE)
i think i change it to
Code:
4*GET_PLAYER(getOwnerINLINE()).strengthOfBestUnitAI(DOMAIN_LAND, UNITAI_CITY_DEFENSE)


Edit:

Why is this AI code so :confused::eek::crazyeye::sad::(
You think you have it right and it's not working again. I had a few settlers moving out with 3 units as escort and then one settler moves to found a city without any escort.
 
This seems wrong but as always i might be wrong too. I have to looks at this and the other stuff later after work.

Ok i found the right spot to give settlers more units as escort and the reason why they always started moving as soon as one unit joined them. But how strong should the escort be at the moment it is
Code:
2*GET_PLAYER(getOwnerINLINE()).strengthOfBestUnitAI(DOMAIN_LAND, UNITAI_CITY_DEFENSE)
i think i change it to
Code:
4*GET_PLAYER(getOwnerINLINE()).strengthOfBestUnitAI(DOMAIN_LAND, UNITAI_CITY_DEFENSE)

This is obviously a 2 part issue. That evaluation must trigger the settler ai to move onward, yes. But the other side of the coin is what I addressed.

Code:
if (AI_group(UNITAI_SETTLE, 3, -1, -1, false, false, false, 1, true))
The 3 in the second parameter is the indication of the maximum number of units in the group that it will allow the unit to join with (including itself.) So by changing it to 3 it allows the settler group to take two additional defensive units from city defense AI.
 
Top Bottom