range(N) results in 0 to N-1.Edit 2 - does the 'range' construct in Pythin go from 1 to N, or from 0 to N-1 ? If it goes from 1 to N then that'll be your problem, since one end of the path will be at index 0.
range(N) results in 0 to N-1.Edit 2 - does the 'range' construct in Pythin go from 1 to N, or from 0 to N-1 ? If it goes from 1 to N then that'll be your problem, since one end of the path will be at index 0.
I have no time to run it but some fast comments about the code:
- The first loop does not seem to consider wrapping (a continent which is on both sides of the X coordinate 0)
- The second loop should not use CyMap().numPlots() but instead CyMap.getLastPathStepNum()
Neither of those accounts for your problem though. Have you tried with different pathing flag combinations?
It is not trivial as where does west end and east start. The continent might also be huge and go around the entire world or nearly the entire world.You're right about the wrapping. I didn't think about that. I will fix it so that it checks the X-distance instead of the X-coordinate; is there an easy test for wrapping? I can think of a logical test (if the difference is negative, add the width of the map to the tested number), but there might be something shorter that I don't know about. I'll also fix the second loop.
It is not trivial as where does west end and east start. The continent might also be huge and go around the entire world or nearly the entire world.
So maybe restrict west to 120 degrees west of the starting plot and restrict the maximum path length to what 180 degrees in a direct line would be.
Yes, I'll work on that. I think there is a way to deal with a wraparound continent. Is this a unique thing to C2C map scripts that continents can cross over the map edges? On an all-ground map, the railroad will simply wrap around the entire world, which I am okay with.
I think I got the problem solved with the short railroad -- unexplored terrain is not "safe". On my save game, the target square of (3,12) was not visible to the civilization. Therefore, of the squares of (4,X), (4,4) was closest to the city, and that's why the railroad built to there. When I used WorldBuilder to reveal the map, that let the railroad build all the way to the (3,12) target.
Yeh, the pathing engine doesn't allow pathing through unrevealed terrain (the are lots of exploits available fairly easily to find out more than you should know about the map if we allow it). I think this is correct behavior for a wonder also, though I could add a flag to ignore this restriction if you really wanted me to.
Yeh, the pathing engine doesn't allow pathing through unrevealed terrain (the are lots of exploits available fairly easily to find out more than you should know about the map if we allow it). I think this is correct behavior for a wonder also, though I could add a flag to ignore this restriction if you really wanted me to.
if ((pPlot.getX() < iXMaxWest) and (pPlot.getX() < iXStart) and (iXMaxWest <= iXStart))
or (((pPlot.getX() - CyMap().getGridWidth()) < iXMaxWest) and (iXMaxWest <= iXStart) and (pPlot.getX() > iXStart) and ((CyMap().getGridWidth() - (pPlot.getX() + 1)) < (CyMap().calculatePathDistance(pPlot, pStart))))
or ((pPlot.getX() < iXMaxWest) and (pPlot.getX() > iXStart) and (iXMaxWest > iXStart) and ((CyMap().getGridWidth() - (pPlot.getX() + 1)) < (CyMap().calculatePathDistance(pPlot, pStart)))):
Check the half way point. Don't use the calculatePathDistance function but the generatePath derivatives as then you can query the path (and especially try to generate each path only once, path generation is expensive and calculatePathDistance does that as well).This last part is where I am getting hung up. The issue is that on tall continents, the calculatePathDistance is returning a number high enough to make what should be a west-oriented railroad go north-northeast instead. So the question that I have is:
- Is there a way to determine which DIRECTION a path from one plot to another is going?
- Alternatively, is there an easy way to extract the sign of the displacement in one direction of a path? (That is, does the path go west - a negative X-displacement - or east - a positive X-displacement?)
- If that's not possible, how do you determine if a path passes through a particular square? This would be more complicated and probably involve more extra code, but I think it's possible from the description of the pathing function. I think setting the "tripwire" to any square in the column just east of the building city would work.
If anyone could help me with this, it would be really appreciated.
I'm still working my way through the Golden Spike code, and I have it nearly complete except for the wraparound issue. I have a test that is running into an issue with tall continents that also wrap around. This will also be a problem on all-land or mostly-land maps.
This is the current test (it's actually one line, but I broke it up into three to clarify it):
Code:if ((pPlot.getX() < iXMaxWest) and (pPlot.getX() < iXStart) and (iXMaxWest <= iXStart)) or (((pPlot.getX() - CyMap().getGridWidth()) < iXMaxWest) and (iXMaxWest <= iXStart) and (pPlot.getX() > iXStart) and ((CyMap().getGridWidth() - (pPlot.getX() + 1)) < (CyMap().calculatePathDistance(pPlot, pStart)))) or ((pPlot.getX() < iXMaxWest) and (pPlot.getX() > iXStart) and (iXMaxWest > iXStart) and ((CyMap().getGridWidth() - (pPlot.getX() + 1)) < (CyMap().calculatePathDistance(pPlot, pStart)))):
pPlot is the plot being checked.
iXStart is the X-coordinate of the city building the Spike, and pStart is the plot.
iXMaxWest is the X-coordinate of the best candidate so far.
Previous tests confirm that pPlot is on the same continent as the Spike and is owned by the player or not owned at all. Tests after this will choose between two plots on the same X-coordinate (pick the one closer to the city) and verify that there is a valid path to this plot before making it the new best candidate.
So the test is three parts:
- If the current plot and the best plot are both between the west map edge and the city, just check that the new plot is further west than the old plot.
But if the current plot is across the map edge:
- If the previous best plot was on the other side of the map, offset the X-coordinate to get a proper comparison.
- Check to make sure that the shortest path from the plot to the city doesn't go west - it should go east. Therefore, it should be shorter than the distance from the plot to the east map edge.
This last part is where I am getting hung up. The issue is that on tall continents, the calculatePathDistance is returning a number high enough to make what should be a west-oriented railroad go north-northeast instead. So the question that I have is:
- Is there a way to determine which DIRECTION a path from one plot to another is going?
- Alternatively, is there an easy way to extract the sign of the displacement in one direction of a path? (That is, does the path go west - a negative X-displacement - or east - a positive X-displacement?)
- If that's not possible, how do you determine if a path passes through a particular square? This would be more complicated and probably involve more extra code, but I think it's possible from the description of the pathing function. I think setting the "tripwire" to any square in the column just east of the building city would work.
If anyone could help me with this, it would be really appreciated.
westEndFound = false
eastEndFound = false
westPlot = buildingCityPlot
eastPlot = buildingCityPlot
westCandidate = buildingCityPlot
eastCandidate = buildingCityPlot
while( !westEndFound OR !eastEndFound )
if ( !westEndFound )
#advance west end one space further west
westIsCityContinent = (westPlot.area == city.area)
nextPlot = westPlot.west
# have we wrapped?
if (nextPlot == eastPlot)
break
if (westIsCityContinent && nextPlot.area != city.area)
# this is a coast - its a candidate for west end if we can path to it
if canPath(city, westPlot)
westCandidate = westPlot
else
# can't path this far west so previous candidate was it
westEndFound = true
# now advance east end so that they progress out from the city with equal opportunity
if ( !eastEndFound )
#advance east end one space further west
eastIsCityContinent = (eastPlot.area == city.area)
nextPlot = eastPlot.east
# have we wrapped?
if (nextPlot == westPlot)
break
if (eastIsCityContinent && nextPlot.area != city.area)
# this is a coast - its a candidate for east end if we can path to it
if canPath(city, eastPlot)
eastCandidate = eastPlot
else
# can't path this far east so previous candidate was it
eastEndFound = true
Easiest way (somewhat different algorithm, but should aways work, and also a lot more efficient). My Python sucks so this is just pseudo-code for the algorithm:
Code:westEndFound = false eastEndFound = false westPlot = buildingCityPlot eastPlot = buildingCityPlot westCandidate = buildingCityPlot eastCandidate = buildingCityPlot while( !westEndFound OR !eastEndFound ) if ( !westEndFound ) #advance west end one space further west westIsCityContinent = (westPlot.area == city.area) nextPlot = westPlot.west # have we wrapped? if (nextPlot == eastPlot) break if (westIsCityContinent && nextPlot.area != city.area) # this is a coast - its a candidate for west end if we can path to it if canPath(city, westPlot) westCandidate = westPlot else # can't path this far west so previous candidate was it westEndFound = true # now advance east end so that they progress out from the city with equal opportunity if ( !eastEndFound ) #advance east end one space further west eastIsCityContinent = (eastPlot.area == city.area) nextPlot = eastPlot.east # have we wrapped? if (nextPlot == westPlot) break if (eastIsCityContinent && nextPlot.area != city.area) # this is a coast - its a candidate for east end if we can path to it if canPath(city, eastPlot) eastCandidate = eastPlot else # can't path this far east so previous candidate was it eastEndFound = true
at the end of the above loop wesCandidate and eastCandidate are the two ends you need to path from the city to. Since it only relies on checking that eastPlot != westPlot for the wrapping condition, rather tan doing a coordinate comparison, it should handle whatever the city coordinates are reative to the 0 coordinate
OK, I get most of it. What does westPlot.west mean? Does it mean check the square immediately west of the starting square? I'd like the Spike to be able to move north and south if need be.
Yeh, it's just pseudocode. The path generatin part will cause it to move north and south as needed - just the terminal lots (at coasts) would be at the same fairies as the building city. I thought that was the defining property of the spike...?
It's just a suggestion anyway. It doesn't come with a warrantee
Check the half way point. Don't use the calculatePathDistance function but the generatePath derivatives as then you can query the path (and especially try to generate each path only once, path generation is expensive and calculatePathDistance does that as well).
If the path plot number CyMap.getLastPathStepNum() / 2 is not east or west enough of the starting city, then it is not a good target plot for this spike.
if generatePath(pPlot, pStart, PathingFlags.MOVE_IGNORE_DANGER, 1, 1000) == 1:
pHalfWayPlot = CyPlot.getLastPathPlotByIndex(int(getLastPathStepNum()/2))
if (pHalfWayPlot.getX() < iXStart):
Better use CyMap().generatePathForHypotheticalUnit and you can use the return value to see if there is a path between the plots in the first place (in general you can also just ignore return values in Python but in this case it should be checked).Can you explain generatePath a little bit more? I understand now what you're talking about, and I think that will produce the effect I want. I'm just not sure how to use generatePath other than for a Boolean test. I don't think I can just put it in, can I? Do I just use a dummy if statement? Like
Code:if generatePath(pPlot, pStart, PathingFlags.MOVE_IGNORE_DANGER, 1, 1000) == 1:
I think this is what will work once the path is generated:
because that will check that the path is going in the right direction (through the squares west of the city). iXStart is the X-coordinate of the building city.Code:pHalfWayPlot = CyPlot.getLastPathPlotByIndex(int(getLastPathStepNum()/2)) if (pHalfWayPlot.getX() < iXStart):
Better use CyMap().generatePathForHypotheticalUnit and you can use the return value to see if there is a path between the plots in the first place (in general you can also just ignore return values in Python but in this case it should be checked).
getLastPathPlotByIndex and getLastPathStepNum will always refer to the last successful generatePath or generatePathForHypotheticalUnit.
pHalfWayPlot = CyPlot().getLastPathPlotByIndex(int(getLastPathStepNum()/2))
Attribute Error: 'CyPlot' object has no attribute 'getLastPathPlotByIndex'
getLastPathPlotByIndex and getLastPathStepNum both belong to CyMap.Okay, I already had generatePathForHypotheticalUnit in the code at the end of the testing (check if the path is valid after checking everything else). I moved it up so that I check the path is valid and generate the last plot at the same time. Now I am getting an Attribute Error when I try to use getLastPathPlotByIndex.
This is the line of code I am trying to use:
Code:pHalfWayPlot = CyPlot().getLastPathPlotByIndex(int(getLastPathStepNum()/2))
The error message is:
so I'm doing something wrong, but I'm not sure what.Code:Attribute Error: 'CyPlot' object has no attribute 'getLastPathPlotByIndex'
getLastPathPlotByIndex and getLastPathStepNum both belong to CyMap.
Well, I have found a new wrinkle in trying to build the Golden Spike. I have attached a save game that demonstrates this behavior and a zip of my current GoldenSpike.py file.
When the Spike is built, the east leg of the railroad actually goes to the spot where the Android Worker is standing. This is because the path that the Android Worker uses to reach the city is a diagonal path. The city's coordinate is (7,3). The Android Worker is at (6,7). The path that is being selected as the halfway plot for the path is (8,5), which meets the requirements for an east-moving leg. I chose an Android Worker as the pathing unit because it had no combat strength (so it wouldn't care about defensive terrain) and it had flat movement costs (so it wouldn't care about difficult terrain). Apparently, it's choosing the shortest possible route into safe territory, which is a diagonal.
Should I choose a different unit? Is there anything else that might work as a fix? The only thing I can think of is a blanket prohibition on using the squares exactly adjacent to the opposite side, but I am afraid that might introduce more corner-case issues on very large wraparound continents. Or is this whole scenario not likely to come up in a real game? I admit I use Duel maps with no rivals and no barbarians to test my ideas.