Help Wanted Ads

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.
 
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()

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.

Neither of those accounts for your problem though. Have you tried with different pathing flag combinations?

I will try the flags and see what happens.
 
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.
 
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.
 
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.

I agree that the current behavior is correct for units and wonders.
 
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.

No, I'm fine with it, I just didn't realize that the pathing engine didn't let you go through unrevealed territory. Especially by the time Golden Spike is built, you should have almost all the map revealed anyway. Via Appia is going to build between cities in my new plan, so it should at worst use the routes you took to get the Settler there (although if you used Galleys, a city may be cut off), or if you have the terrain between them revealed, it will naturally take the easiest path that it can see.
 
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.
 
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.
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.
 
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.

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
 
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.
 
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 ;)
 
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 ;)

That was one of my previous versions. The current version, which I think will work with AIAndy's test, will find the extreme west and east points on the continent and send the rails all the way to there. I'm not yet sure how it will work with world-girdling continents, but I think it will do fine. I just have to keep trying and find out what happens.
 
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.

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:
Code:
pHalfWayPlot = CyPlot.getLastPathPlotByIndex(int(getLastPathStepNum()/2))
if (pHalfWayPlot.getX() < iXStart):
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.
 
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:
Code:
pHalfWayPlot = CyPlot.getLastPathPlotByIndex(int(getLastPathStepNum()/2))
if (pHalfWayPlot.getX() < iXStart):
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.
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.
 
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.

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:
Code:
Attribute Error: 'CyPlot' object has no attribute 'getLastPathPlotByIndex'
so I'm doing something wrong, but I'm not sure what.
 
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:
Code:
Attribute Error: 'CyPlot' object has no attribute 'getLastPathPlotByIndex'
so I'm doing something wrong, but I'm not sure what.
getLastPathPlotByIndex and getLastPathStepNum both belong to CyMap.
 
getLastPathPlotByIndex and getLastPathStepNum both belong to CyMap.

Thank you. That fixed the code. Now I just have to figure out what the code is actually doing -- it's giving me another irregular effect in my initial test (a short northeast railroad, when it should be going west). I have to work on it some more. Thanks for explaining the new pathing. Once I get this working, I think I will try and redo Via Appia.
 
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.
 
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.

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)
 
Top Bottom