Help Wanted Ads

Could you post a screenshot. I think I know what you mean, and if so it's easy for me to add another flag for toy that should address it, but I'm not certain I fully understand what you mean (I'm not really in a position ro load a save since my copy is currently non functional while I debug the viewports)

Sure. You can see the Android Worker and the city here. I'm not sure what flags are being used here, because this is not the same path that the railroad uses. (This is a Go-To, <G> on the keyboard.)

View attachment 324602

This is where the railroad goes. The halfway point is (8,5) which is the grassy hill north of the coal. Even the path that the Android uses in the previous screenshot would work, because that would not meet the requirements for an east-moving path.

View attachment 324603

Maybe the problem is the pathing flag I am using? I am currently using PathingFlags.MOVE_SAFE_TERRITORY because that is the flag that has to be used inevitably to test whether a Worker can build railroads to that space, but maybe I should use a different flag here and then do the test again with MOVE_SAFE_TERRITORY after every other test has been done. I was trying to use only one path per plot because AIAndy said path generation was expensive. On the other hand, the Golden Spike should only occur once per game, so maybe it will not be too bad.
 
Sure. You can see the Android Worker and the city here. I'm not sure what flags are being used here, because this is not the same path that the railroad uses. (This is a Go-To, <G> on the keyboard.)

View attachment 324602

This is where the railroad goes. The halfway point is (8,5) which is the grassy hill north of the coal. Even the path that the Android uses in the previous screenshot would work, because that would not meet the requirements for an east-moving path.

View attachment 324603

Maybe the problem is the pathing flag I am using? I am currently using PathingFlags.MOVE_SAFE_TERRITORY because that is the flag that has to be used inevitably to test whether a Worker can build railroads to that space, but maybe I should use a different flag here and then do the test again with MOVE_SAFE_TERRITORY after every other test has been done. I was trying to use only one path per plot because AIAndy said path generation was expensive. On the other hand, the Golden Spike should only occur once per game, so maybe it will not be too bad.

Ok, it's not what I thought you meant and I'm afraid I'm just confused. I presume one end of the path you're asking it to generate is the city. Which plot is the other end, and what is the issue with the path generated? from the pictures it looks like one end is at the android worker, but I thought the spike was meant to stay at the same latitude as the building city (the end points at least), so this doesn't seem like a valid end point to be asking it to generate a path to.
 
Ok, it's not what I thought you meant and I'm afraid I'm just confused. I presume one end of the path you're asking it to generate is the city. Which plot is the other end, and what is the issue with the path generated? from the pictures it looks like one end is at the android worker, but I thought the spike was meant to stay at the same latitude as the building city (the end points at least), so this doesn't seem like a valid end point to be asking it to generate a path to.

One end of the path is the city that builds the Spike. The other end of the path is supposed to be the point on the same continent that is furthest east (or west) of the building city, regardless of latitude and taking into account wraps around the map. Since I have access to the pathing feature, I can make the railroad go as far north or south as I want -- it's no longer limited to the same latitude. The latitude limitation was due to me using pPlot.getY() == pCity.getY() as one of my functions, since I was using a single for loop to count through the squares and lay down the rails at the same time. I don't have to do that anymore - I just have to locate the proper squares and the pathing function can do the rest.

In the save game that I posted, the path goes to the spot where the Android is standing, but that's not where it should go. Here's a screenshot from as far out as I can zoom.

View attachment 324611

The railroad should be running northeast to the square "East Leg Target" (17,12), but because of the logical tests I am using and the direction that the Android's path is going in, it winds up only going to the square that I marked "East Leg Actual".

The issue is that the logical tests check to make sure that the halfway point of the path is in the right direction relative to the city and the other endpoint, but because of the diagonal movement of the Android making the path, the shorter point winds up satisfying all the tests and gets to be the second endpoint. What I need is parameters for the path-generating function (CyMap().generatePathForHypotheticalUnit) that ignores everything except for oceans and keeping the total number of diagonals moved to a minimum.

Does that clear things up?
 
Why does that plot win versus the actual one when the actual one is further east?
 
Why does that plot win versus the actual one when the actual one is further east?

Yeh. Why check the halfway point is further east? Imagine a city on this map (city is C, M is mountain, W water, - other terrain:


WW----MMMM----WW
WW-----C-M----WW
WW-------M-----WW
WW------MM----WW
WW-----MM-----WW
WW-------------WW


The path to go east MUST come back west to get around the mountains, and in general might have to come arbitrarily far west depending on the map. It seems to me that this sort of condition invalidatesd the 'half way' test.
 
Why does that plot win versus the actual one when the actual one is further east?

It's because of the direction that the hypothetical Android Worker path goes. The sequence of tests is (I'm just doing the east leg of the railway here):
  • Is the plot on the same continent? Yes.
  • Is the plot owned by the Spike's owner or unowned? Yes.
  • Can a legal path be generated from the city to the plot? This is currently done by a hypothetical Android Worker using the MOVE.SAFE.TERRITORY flag. Yes.
  • Find the path's halfway point. No problem.
  • Is the halfway point between the city and the current plot? This is the problem. The path moves diagonally such that the single plot I am testing as the halfway point fulfills the test for an east-moving path. If the path did not move as far east diagonally, it would not work.
  • Is the new plot at least as far east (relative to the city) as the old plot? Yes. Relatively, it's farther east.
  • If the new plot is exactly as far east as the old plot, is it closer vertically to the city? It's farther east.

So the issue is that I use CyMap().generatePathForHypotheticalUnit to generate a path between the city and the target square, but it doesn't generate as straight a path as I need for the test to work. I think unit paths have some special biases that I don't know how to correct for. Does CyMap().calculatePathDistance generate a path that can be queried? And what kind of path does it generate? What I need is as straight a path as possible; exactly one straight leg and exactly one diagonal leg, can be done in either order as long as the diagonal leg is as short as possible and doesn't detour for anything except impassable terrain.

The Go-To path for the Android Worker is:
(6,7) - (6,6) - (7,5) - (8,4) - (7,3)

The Safe Move path for the Android Worker is (and this is the route the railway takes):
(6,7) - (7,6) - (8,5) - (8,4) - (7,3)

There are two paths that the Android Worker could take that would be ideal:
(6,7) - (7,6) - (7,5) - (7,4) - (7,3) OR
(6,7) - (6,6) - (6,5) - (6,4) - (7,3)

I don't know what set of parameters (unit and flag) would force this kind of path to always be generated.
 
Yeh. Why check the halfway point is further east? Imagine a city on this map (city is C, M is mountain, W water, - other terrain:


WW----MMMM----WW
WW-----C-M----WW
WW-------M-----WW
WW------MM----WW
WW-----MM-----WW
WW-------------WW


The path to go east MUST come back west to get around the mountains, and in general might have to come arbitrarily far west depending on the map. It seems to me that this sort of condition invalidatesd the 'half way' test.

If the hypothetical path can ignore mountains, though, it will work. By the time you get to the Spike, you should have Mountaineering and then the Transcontinental Railroad can go over the mountains. This is also a reason why I try to test my stuff before I post it to the SVN. I'm trying to find these issues first.
 
Take the length of the path into account. If getLastPathStepNum() is smaller than the X coordinate difference then the path does not actually go east.
 
Take the length of the path into account. If getLastPathStepNum() is smaller than the X coordinate difference then the path does not actually go east.

By definition it goes to where you asked it to (else it would have returned false). How it gets there is another matter of course.
 
By definition it goes to where you asked it to (else it would have returned false). How it gets there is another matter of course.

You're right. I finally got something to work that handles wraparounds. The important step is not the halfway point, but the first step - is the path going in the right direction? For example, this is the code for the west-pointing leg:

Code:
pCheckPlot = CyMap().getLastPathPlotByIndex(1)
if (pCheckPlot.getX() == pPlot.getX() + 1) or ((pCheckPlot.getX() == 0) and (pPlot.getX() == (CyMap().getGridWidth() - 1))) or (pPlot.getX() == (iXStart - 1)):

pStart (iXStart, iYStart) is the plot of the city building the Spike. pPlot is the plot being checked. The path is generated from pPlot to pStart. I will upload the new Python file to the SVN once the freeze is over. I will also probably create a new thread in case anyone finds some extreme weirdness.

This may not be able to handle extremely convoluted continents. It also doesn't handle world-girdling continents, although I can write a separate section to handle that. Do you think that would be a good idea?
 
OK, time for another question, and this is probably something simple that I have missed. I think I have Golden Spike completed. I'm trying to work now on Via Appia. Instead of upgrading all Roads to Paved Roads, it will instead create a Paved Road network between all of the building player's cities on the same continent. To do this, I am trying to create two lists using Python:
  • A list of the ID's of all cities owned by the building player
  • A parallel list of "false" for each city, which will be each city's status in the network. After I create this list, the slice of the list corresponding to the building city will be set to "true".

This way, I can use an arbitrary integer to get a city's ID and whether or not that city has been linked by Via Appia. The algorithm itself is:
  • Start from the building city and test all other cities on the same continent to find the nearest one not in the network
  • Test that city against all cities already in the network and pick the nearest one
  • Build paved roads between the two cities
  • Add the new city to the network and start again using the new city as the beginning point

To do this, I need a way to keep track of the linked status of the cities; thus, the two lists and using the append function to build the lists before I start actually building the roads. Then I can use a simple for i in range loop to go through the cities and connect them to each other.

I can use this code to get partially there:
Code:
cityList = player.getCityList()
for city in cityList:

and this generates a bunch of PyHelpers.PyCity instance at followed by a hexadecimal number. (The number of instances matches the number of cities controlled, so I'm fairly sure this is the right approach.) The question that I have is, how do I convert the instances to their index numbers? It seems like there should be a Python function that does this, but I can't seem to find it in the API. I tried CyCity().getID(city), but that gives me an ArgumentError: Python argument types in CyCity.getID(CyCity, instance) did not match C++ signature: getID(class CyCity {lvalue}) message.
 
getID is a method of the city class. So you need to use
city.getID()

One important thing: Use a hash instead of a list to store if a city is already in the network if you need an efficient test if a certain city is in the network or not.

Hmm, alternatively I would suggest something like this (pseudo code):
Code:
DistanceList: List of city/distance pairs.
for all cities on continent
  if city is not ViaAppiaCity
    calculate distance to ViaAppiaCity
    append city/distance to DistanceList

sort DistanceList by distance ascending
for all cities A in DistanceList
  min_dist = MAX_INT
  min_city = NULL
  for all cities B in DistanceList(start of list to city before A)
    calculate distance between A and B
    if distance < min_dist
      min_dist = distance
      min_city = B
  build road between A and min_city
Somewhat expensive but then it is not called often.
 
ok so i think i have a very interesting unit idea: why not make a unit when instead of riding horses\llamas\bulls players would be able to ride real people!!!! mostly slaves and servents

so instead of having horse cavalry we could have something like a human cavalry of a human chariot or something like that.

what do you think???
 
getID is a method of the city class. So you need to use
city.getID()

One important thing: Use a hash instead of a list to store if a city is already in the network if you need an efficient test if a certain city is in the network or not.

Hmm, alternatively I would suggest something like this (pseudo code):
Code:
DistanceList: List of city/distance pairs.
for all cities on continent
  if city is not ViaAppiaCity
    calculate distance to ViaAppiaCity
    append city/distance to DistanceList

sort DistanceList by distance ascending
for all cities A in DistanceList
  min_dist = MAX_INT
  min_city = NULL
  for all cities B in DistanceList(start of list to city before A)
    calculate distance between A and B
    if distance < min_dist
      min_dist = distance
      min_city = B
  build road between A and min_city
Somewhat expensive but then it is not called often.

Also for the 'calculate distance' stp use the opath generator and get the length of the resulting path. That way cities that are 'close' on opposite sides of a mountain range but actually take a LOOOONG road between them will sort more 'correctly'. Also this will spot cities that CANNOT be linked (because there is no path) so you can drop those off the list at that check.

Edit - alternatively I could expose that nice polynomial-time function that solves the travelling salesman problem I have up my sleeve for you ;)
 
Also for the 'calculate distance' stp use the opath generator and get the length of the resulting path. That way cities that are 'close' on opposite sides of a mountain range but actually take a LOOOONG road between them will sort more 'correctly'. Also this will spot cities that CANNOT be linked (because there is no path) so you can drop those off the list at that check.

Edit - alternatively I could expose that nice polynomial-time function that solves the travelling salesman problem I have up my sleeve for you ;)

Here is the code I have created. I tested it myself on a 37-city map and it connects all of the cities with Paved Roads. I am definitely using generatePathforHypotheticalUnit using a hypothetical Worker, since that is what would be available at that time. Let me know if you can think of a way to improve on it.

Code:
	if iBuildingType == gc.getInfoTypeForString( 'BUILDING_APPIAN_WAY' ):

		iPlayer = pCity.getOwner()
		pPlayer = gc.getPlayer(iPlayer)
		player = PyPlayer(iPlayer)
		iMinPath1 = 10000000
		iMinPath2 = 10000000
		iUnit = gc.getInfoTypeForString("UNIT_WORKER")
		lCityIndex = []
		lCityNet = []

		CyInterface().addImmediateMessage(CyTranslator().getText("TXT_APPIAN_BUILT",()), None)

		cityList = player.getCityList()
		for city in cityList:
			lCityIndex.append(city.getID())
			lCityNet.append(0)

		lCityNet[lCityIndex.index(pCity.getID())] = 1
		pCity1 = pCity
		
		for i in range(pPlayer.getNumCities()):
			pTCity = pPlayer.getCity(lCityIndex[i])
			if lCityNet[i] == 0 and pTCity.area().getID() == pCity.area().getID() and pTCity.getOwner() == iPlayer:
				if (CyMap().generatePathForHypotheticalUnit(pCity1.plot(), pTCity.plot(), iPlayer, iUnit, PathingFlags.MOVE_SAFE_TERRITORY, 1000) == 1):
					if CyMap().getLastPathStepNum() < iMinPath1:
						iMinPath1 = CyMap().getLastPathStepNum()
						for j in range(pPlayer.getNumCities()):
							pTCity2 = pPlayer.getCity(lCityIndex[j])
							if lCityNet[j] == 1 and pTCity2.area().getID() == pCity.area().getID() and pTCity2.getOwner() == iPlayer:
								if (CyMap().generatePathForHypotheticalUnit(pTCity.plot(), pTCity2.plot(), iPlayer, iUnit, PathingFlags.MOVE_SAFE_TERRITORY, 1000) == 1):
									if CyMap().getLastPathStepNum() < iMinPath2:
										iMinPath2 = CyMap().getLastPathStepNum()
										pCity2 = pTCity2
			if (CyMap().generatePathForHypotheticalUnit(pTCity.plot(), pCity2.plot(), iPlayer, iUnit, PathingFlags.MOVE_SAFE_TERRITORY, 1000) == 1):
				for k in range(CyMap().getLastPathStepNum()):
					pPavePlot = CyMap().getLastPathPlotByIndex(k)
					pPavePlot.setRouteType(gc.getInfoTypeForString("ROUTE_PAVED"))
				pCity1 = pCity2
				lCityNet[lCityIndex.index(pCity1.getID())] = 1
				pMinPath1 = 10000000
				pMinPath2 = 10000000
 
getID is a method of the city class. So you need to use
city.getID()

One important thing: Use a hash instead of a list to store if a city is already in the network if you need an efficient test if a certain city is in the network or not.

city.getID() worked for me, and after some interesting results on my part, I finally have a new working Via Appia.

Could you explain hashes? I don't quite understand them yet. I have a new working code that uses a list. I posted the code in my previous reply.
 
Could you explain hashes? I don't quite understand them yet. I have a new working code that uses a list. I posted the code in my previous reply.
Hashes (the name in Python is Dictionaries) allow you to store key/value pairs and access the value from the key in an efficient way (unlike using the index function on a list which takes linear time).
Small example (storing city id and belonging to the network):
Code:
network = {}
network[cityX.getID()] = 1
if (network[cityY.getID()]):
 
While your code works, I don't think it does what you think it does in some parts.

Code:
if CyMap().getLastPathStepNum() < iMinPath1:
						iMinPath1 = CyMap().getLastPathStepNum()
This part does not do anything because the next time you reach that code you will already have set iMinPath1 to 10000000 again.
If you want to add cities that are closer to the Via Appia city first to the city network, then you will have to go with the pseudo code I wrote further up and make a list with the distances and then sort it.

Code:
pPlayer = gc.getPlayer(iPlayer)
		player = PyPlayer(iPlayer)
Is there a reason to have both of those instead of only one?

The getOwner checks seem unnecessary as you only deal with cities that are returned from methods that only return cities of that player.
 
While your code works, I don't think it does what you think it does in some parts.

Code:
if CyMap().getLastPathStepNum() < iMinPath1:
						iMinPath1 = CyMap().getLastPathStepNum()
This part does not do anything because the next time you reach that code you will already have set iMinPath1 to 10000000 again.
If you want to add cities that are closer to the Via Appia city first to the city network, then you will have to go with the pseudo code I wrote further up and make a list with the distances and then sort it.

Code:
pPlayer = gc.getPlayer(iPlayer)
		player = PyPlayer(iPlayer)
Is there a reason to have both of those instead of only one?

The getOwner checks seem unnecessary as you only deal with cities that are returned from methods that only return cities of that player.

I'll try those, thank you. I was just happy that I actually got something that worked.
 
Code:
pPlayer = gc.getPlayer(iPlayer)
		player = PyPlayer(iPlayer)
Is there a reason to have both of those instead of only one?

Actually, I do. I tested it both ways and if I leave either of them out, I get flagged for an error message 'CyPlayer' object has no attribute 'getCityList'. It's at different line numbers depending on if I leave out the pPlayer or the player, so I am not completely sure what the problem is. With both of them in, it works fine.
 
Top Bottom