Sending unit to a coastal city

OrionVeteran

Deity
Joined
Dec 25, 2003
Messages
2,443
Location
Newport News VA
I'm developing code to send a unit to a coastal city. This coastal city must have a harbor and trade routes established. There could be several coastal cities that might qualify. What I want to do is pick the best city, meaning a coastal city that is not surrounded by ice that could block its path to more than one other coastal city. How can I identify a coastal city that has access to multiple other coastal cities (that belong to my empire) and select that city over the city whose path is blocked by ice?

Here is what I have so far.

Spoiler :

Code:
def doNTIPlacementAI(pUnit):
	# Orion's Combined Forces Mod
	# Sends NTI unit to a city where the AI can build a Fleet
	iOwner = pUnit.getOwner()
	AIpPlayer = gc.getPlayer(iOwner)
	iHarbor = gc.getInfoTypeForString("BUILDING_HARBOR"):
	AICan = False
	
	#CyInterface().addImmediateMessage("A", "")
	for iCity in range(AIpPlayer.getNumCities()):
		pCity = AIpPlayer.getCity(iCity)
		# Is this a coastal city?
		if pCity.isCoastal():
			# Does the city have a harbor?
			if pCity.isHasBuilding(iHarbor):
				HarborInfo = gc.getBuildingInfo(iHarbor)
				if HarborInfo.getCoastalTradeRoutes() > 0:
					#Send unit to pCity
 
Firstly, you shouldn't use a ordered list array to loop through player cities. Because there are frequently gaps in the index. So replace range() - which returns a list type with values from zero and up - with PyHelpers.PyPlayer.getCityList() - don't forget to use PyPlayer.GetCy() to retrieve the CyCity instance from the PyCity object. (You could create your own function for this however, only returning a list of ocean port cities, see below.)

Secondly, I don't think that you wanna use CvBuildingInfo.getCoastalTradeRoutes() because that would only return the number of trade routes added by the building, right? Instead, you could add yet another loop to check the current city against all other cities with CyCity.isConnectedTo(). You probably need 3 variables to count, compare and to store the city with the most connections.

Furthermore, CyCity.isCoastal() takes a iMinWaterSize parameter, which is actually good because it lets you weed out cities by inland lakes.

Finally, you probably need CyCity.waterArea() to only compare cities that share a CyArea - meaning that they are located around the same body of water - within reasonable distance from each-other.

Now, with that said, this sort of AI programming should probably be done in the SDK. Because anything along these lines will be slow at best. You still need to figure out hos to push the move order to the unit. And you need to make sure that the unit can/should make the journey (meaning that the city is reachable and not too far away) in the first place.
 
Firstly, you shouldn't use a ordered list array to loop through player cities. Because there are frequently gaps in the index. So replace range() - which returns a list type with values from zero and up - with PyHelpers.PyPlayer.getCityList() - don't forget to use PyPlayer.GetCy() to retrieve the CyCity instance from the PyCity object. (You could create your own function for this however, only returning a list of ocean port cities, see below.)

Secondly, I don't think that you wanna use CvBuildingInfo.getCoastalTradeRoutes() because that would only return the number of trade routes added by the building, right? Instead, you could add yet another loop to check the current city against all other cities with CyCity.isConnectedTo(). You probably need 3 variables to count, compare and to store the city with the most connections.

Furthermore, CyCity.isCoastal() takes a iMinWaterSize parameter, which is actually good because it lets you weed out cities by inland lakes.

Finally, you probably need CyCity.waterArea() to only compare cities that share a CyArea - meaning that they are located around the same body of water - within reasonable distance from each-other.

Now, with that said, this sort of AI programming should probably be done in the SDK. Because anything along these lines will be slow at best. You still need to figure out hos to push the move order to the unit. And you need to make sure that the unit can/should make the journey (meaning that the city is reachable and not too far away) in the first place.


I assume that when you say:

Code:
you shouldn't use a ordered list array to loop through player cities. Because there are frequently gaps in the index.

...you mean that the index gaps slow down the performance speed, as compared to the PyHelpers code.

Here is what I have so far:

Spoiler :

Code:
def doNTIPlacementAI(pUnit):
	# Orion's Combined Forces Mod
	# Sends NTI unit to a city where the AI can build a Fleet
	
	iOwner = pUnit.getOwner()
	AIpPlayer = gc.getPlayer(iOwner)
	iHarbor = gc.getInfoTypeForString("BUILDING_HARBOR")
	iRefCityList = PyHelpers.PyPlayer(iOwner).getCityList()
	AICan = False

	#CyInterface().addImmediateMessage("A", "")	
	for pyCity in iRefCityList:
		pCity = pyCity.GetCy()
		# Is this a coastal city?
		if pCity.isCoastal():
			# Does the city have a harbor?
			if pCity.isHasBuilding(iHarbor):
				AICity = pCity
				AIiCity = [COLOR="Red"][B]iCity[/B][/COLOR]			
				AICan = True
				break		


	if AICan:
		if pUnit.generatePath(AICity.plot(), 0, False, None):
			# Get the Era for the units in the AI city
			AISelectedEra = getAIFleetEra(iOwner, AIiCity)
					
			if pUnit.getX() != AICity.getX() or pUnit.getY() != AICity.getY():
				# Move the NTI to the AI city, where Fleet can be built.
				pUnit.getGroup().pushMission(MissionTypes.MISSION_MOVE_TO, AICity.getX(), AICity.getY(), 0, False, True, MissionAITypes.NO_MISSIONAI, pUnit.plot(), pUnit)
				
			else:
				doFleetUnitPreChecks(iOwner, AISelectedEra, pCity)


I need to replace iCity (the city number). What can I use as a replacement?
 
I assume that when you say:

...you mean that the index gaps slow down the performance speed, as compared to the PyHelpers code.
No, what I mean is that a straight index can include invalid CyCity instances - cities that have been conquered/lost along the way. Using range() will inevitably give you invalid objects in the long run.

So a player with 5 cities could have a index like: 0, 2, 3, 5, 7 while range(5) returns a list like: [0, 1, 2, 3, 4] - city 1 and 4 wouldn't be valid cities anymore and the index would simply miss cities 5 and 7 in this example.

The same goes for units - you should use PyPlayer.getUnitList() for looping through a player's units or you will end up with errors/unforeseen results.

I need to replace iCity (the city number). What can I use as a replacement?
Do you really need the city's index number? Well, then you get it with pCity.getID() - or even pyCity.getID().

I doubt that you can pass a None value to CyUnit.generatePath() but everything beyond that point in your code is uncharted territory for me. :p
 
No, what I mean is that a straight index can include invalid CyCity instances - cities that have been conquered/lost along the way. Using range() will inevitably give you invalid objects in the long run.

So a player with 5 cities could have a index like: 0, 2, 3, 5, 7 while range(5) returns a list like: [0, 1, 2, 3, 4] - city 1 and 4 wouldn't be valid cities anymore and the index would simply miss cities 5 and 7 in this example.

The same goes for units - you should use PyPlayer.getUnitList() for looping through a player's units or you will end up with errors/unforeseen results.


Do you really need the city's index number? Well, then you get it with pCity.getID() - or even pyCity.getID().

I doubt that you can pass a None value to CyUnit.generatePath() but everything beyond that point in your code is uncharted territory for me. :p

Ah yes. pCity.getID() is so obvious. I just couldn't remember it. LOL! You can trust me when I say the code for moving the NTI is tested code that works without error in the Army mod. What I need now is to work on identifying the coastal city with the most connections. Perhaps you could recommend an example for me to test.
 
Try something like this:
Spoiler :
PHP:
def getConnectedCoastalCity(ePlayer):
	"""
	Returns the CyCity object of ePlayer with the most connections to other coastal 
	cities on the same seaboard. Returns None of there are no coastal cities.
	"""
	lCoastalCities = getCoastalCities(ePlayer)
	iHighestNum = 0
	pMostConnected = None
	for pCostalCity in lCoastalCities:
		iNumConnections = 0
		pOcean = pCostalCity.waterArea()
		for pTargetCity in lCoastalCities:
			if pCoastalCity == pTargetCity: continue
			if pCoastalCity.isConnected(pTargetCity):
				if pOcean == pTargetCity.waterArea():
					iNumConnections += 1
		if iNumConnections > iHighestNum:
			iHighestNum = iNumConnections
			pMostConnected = pCoastalCity
	return pMostConnected

def getCoastalCities(ePlayer):
	"""
	Returns a list array of costal cities next to a mass of water of at least 25 map plots.
	"""
	lCoastalCities = list()
	for pCity in (city.GetCy() for city in PyHelpers.PyPlayer(ePlayer).getCityList()):
		if pCity.isCoastal(25):
			lCoastalCities.append(pCity)
	return lCoastalCities
 
Try something like this:
Spoiler :
PHP:
def getConnectedCoastalCity(ePlayer):
	"""
	Returns the CyCity object of ePlayer with the most connections to other coastal 
	cities on the same seaboard. Returns None of there are no coastal cities.
	"""
	lCoastalCities = getCoastalCities(ePlayer)
	iHighestNum = 0
	pMostConnected = None
	for pCostalCity in lCoastalCities:
		iNumConnections = 0
		pOcean = pCostalCity.waterArea()
		for pTargetCity in lCoastalCities:
			if pCoastalCity == pTargetCity: continue
			if pCoastalCity.isConnected(pTargetCity):
				if pOcean == pTargetCity.waterArea():
					iNumConnections += 1
		if iNumConnections > iHighestNum:
			iHighestNum = iNumConnections
			pMostConnected = pCoastalCity
	return pMostConnected

def getCoastalCities(ePlayer):
	"""
	Returns a list array of costal cities next to a mass of water of at least 25 map plots.
	"""
	lCoastalCities = list()
	for pCity in (city.GetCy() for city in PyHelpers.PyPlayer(ePlayer).getCityList()):
		if pCity.isCoastal(25):
			lCoastalCities.append(pCity)
	return lCoastalCities

Thanks, I'll give it a try. One question though: What does the number 25 in the following line represent?

Code:
if pCity.isCoastal([COLOR="Red"][B]25[/B][/COLOR]):
 
I already explained this. :D
Furthermore, CyCity.isCoastal() takes a iMinWaterSize parameter, which is actually good because it lets you weed out cities by inland lakes.
You could have looked it up in the API also, or in the function documentation string! :lol:
Returns a list array of costal cities next to a mass of water of at least 25 map plots.
The value might be way to small though. You might need something in the hundreds, or more. :dunno: (I didn't wanna break the sample code with an overly inflated value.)
 
I already explained this. :D

You could have looked it up in the API also, or in the function documentation string! :lol:
.)

I did look it up, but I didn't understand what iMinWaterSize meant. Now I do.

The value might be way to small though. You might need something in the hundreds, or more. :dunno: (I didn't wanna break the sample code with an overly inflated value.)

I can now set a iMinWaterSize value, as the code crasheded once without the parameter value. Thank you for the clarification.
 
Well I don't know anything about the parameter - I simply assumed that it was the size of the ocean that was referenced by the parameter. You should totally try it - or perhaps even look up the source code for the method in the SDK.
 
Back
Top Bottom