Help with Python's getTradeCity please

jerrybp

Chieftain
Joined
May 17, 2010
Messages
20
Hello,
I'm trying to code an event in Python, where I need to get a city's trading partners (cities), with no success.

From the API here (is that the most updated?) I can see that in CyCity there's a getTradeCity(i) function that supposedly gives me another city, which I understand is a trading partner (a city for which there is an active trade route). But when I use it I get nothing.

More precisely, my code in CvRandomEventInterface.py is:
Code:
def CattleDifussion(argsList):
	iEvent = argsList[0]
	kTriggeredData = argsList[1]
	player = gc.getPlayer(kTriggeredData.ePlayer) 
	city = player.getCity(kTriggeredData.iCityId)
	routes=city.getTradeRoutes()
        random=gc.getGame().getSorenRandNum(routes,"")
        tradecity = city.getTradeCity(random)
        x=tradecity.getX()
        y=tradecity.getY()
So basically I'm taking the city the event took, and then picking a city's random trading partner, and asking for the coordinates X, Y of that partner. The problem is I get -1 and -1 for x and y. It's like tradecity is empty.... (tradecity.getName() for example returns nothing). The (event's) city instance is correct though, for I can access its data with no problems.

What am I doing wrong?

I've seen the getTradeCity function used in other codes (see here for example), so I assume it should work, but it doesn't for me.

Ideas?
Thanks
 
Firstly, welcome! :king:

Secondly, wrong sub-forum. :p

Thirdly, always use code tags for you code. :rolleyes:

Lastly, I believe this to be the most up-to-date API. :D

Your code seems ok to me, but you could implement a conditional statement for tradeCity.isNone() to make sure the CyCity instance is not invalid. If it isn't the code needs to pick another city. You might use iteration to loop through the trade cities - but in random order. Then end the loop once isNone() doesn't pass.

Why there would be invalid CyCity instances I wouldn't know, but obviously there are...
 
I guess:

PHP:
random=gc.getGame().getSorenRandNum(routes,"")

produces an invalid value.
Between the "" you have to place some text, on which the RNG will base it's result, and there's no text, so...

Secondly, wrong sub-forum. :p

Moderator Action: Thread moved.

Why there would be invalid CyCity instances I wouldn't know, but obviously there are...

If you capture a city, your enemy will still have technically the instance of the city, but it's not valid.
 
I guess:

PHP:
random=gc.getGame().getSorenRandNum(routes,"")

produces an invalid value.
Between the "" you have to place some text, on which the RNG will base it's result, and there's no text, so...
Yeah? :eek: I always though the string was for debugging/logging...

If you capture a city, your enemy will still have technically the instance of the city, but it's not valid.
So the original city would have a captured city in its trade routes pool then? For how long would it do trade with an invalid city? At what point does the pool refresh and point the route to the new CyCity instance?

Are CyCity instances really deleted when cities change owners? :confused: Couldn't they just get another ID with another CyPlayer?
 
Yeah? :eek: I always though the string was for debugging/logging...

:think: if it is, then i have never said such a thing :blush:.


So the original city would have a captured city in its trade routes pool then? For how long would it do trade with an invalid city? At what point does the pool refresh and point the route to the new CyCity instance?

That was just an example, i have myself no idea, how this could happen here :dunno:.

Are CyCity instances really deleted when cities change owners? :confused: Couldn't they just get another ID with another CyPlayer?

No, CyCity instances are not deleted.
The old instance stays in the pool, the conquerer gets a new one.
 
No, CyCity instances are not deleted.
The old instance stays in the pool, the conquerer gets a new one.
Why is the old CyCity invalid, then? :confused:

I'm guessing I should just learn how to read the SDK in order to answer all these Python questions... :rolleyes:
 
Hi, thanks. I'll edit the code to enclosure it in code-style. I swear I'll never let it happen again! :)

The random=gc.getGame().getSorenRandNum(routes,"") line is working fine. I'm making the game print the results on screen as I play and it is throwing values just right. The problem isn't there.

I could check for a valid instance, but I'll get'em all invalid! :) Since I'm seeing the values on screen, and since I can see them change turn by turn, I'm certain all possible values from 0 to routes are being used one turn or another. Since the number of cities I have in my test is small and the test is very early in the game, the number of routes is quite small. So, looping will not give me something new I haven't seen already....

Any other ideas?
 
Why is the old CyCity invalid, then? :confused:

Okay, i've been a bit unprecise here: The instance is valid, but it doesn't have any values anymore.

I could check for a valid instance, but I'll get'em all invalid! :)

:think: but the city itself (for which you are checking the trade routes) is a valid instance?
 
Are you positive that city is a valid CyCity? What about kTriggeredData.iCityId - where are you getting this value from? And ePlayer?

Insert this to check city:
Code:
if city.isNone(): print ("invalid CyCity")
 
I guess:

PHP:
random=gc.getGame().getSorenRandNum(routes,"")

produces an invalid value. Between the "" you have to place some text, on which the RNG will base it's result, and there's no text, so...

The string value is used for logging, it is not used as a seed. Many people put "". Having a meaningful unique string helps if you have a multiplayer out-of-sync problem. Then you want to make sure that all players are generating random numbers in the same order, and having "" for a log string makes this impossible. It is not that important, maybe, but it is easy enough to write a unique string for each call.
 
The problem in your code *may be* that "random" is the name of a predefined function. In general, most civ code and most python code uses a convention for naming variables. The convention is that the first letter is a type indicator. So, a variable where you want to store a random integer may be iRandom. A variable which contains a pointer to a city may be pCity. Python intentionally doesn't do type checking; some civ functions return integers and some return pointers. It is very easy to get an integer return from one function and pass it to another which requires a pointer. So it will prevent many coding errors if you religiously name variables with a "i" or "p" prefix.

If I am right, and you change the name of your variable to anything but "random", then your code will work. Using this naming convention would have (indirectly) prevented your problem.
 
I finally found the problem...
getTradeRoutes() gives you the number of potential routes, but they're not necessarily active. So, you get "empty" cities through getTradeCity(x) even if getTradeRoutes() is greater than 0. By checking first if the city is actually getting any gold from trade routes, city.getTradeYield(YieldTypes.YIELD_COMMERCE)>0, you can only then be sure that there's at least one active route. And just then tradecity.isNone() comes in handy to find a valid trading partner.

Thanks for all the ideas!
 
I think we all learned something then. :D
 
The string value is used for logging, it is not used as a seed. Many people put "". Having a meaningful unique string helps if you have a multiplayer out-of-sync problem. Then you want to make sure that all players are generating random numbers in the same order, and having "" for a log string makes this impossible. It is not that important, maybe, but it is easy enough to write a unique string for each call.
I wonder how many times this debugging feature has gotten actual use. Well, compared to the actual times modders have entered a value for this parameter it should be "almost never".

This and the constant gc.getGame() invocation makes the CyGame.getSorenRandNum() method a prime candidate for a wrapper function:
Code:
def getRandNum(iNum):
    return CyGame().getSorenRandNum(iNum, "getRandNum()")
Then you only have to call getRandNum(iNum) any time you need a random integer value.

But there perhaps already is one in some obscure module, somewhere...? (Then it would be a good idea to import that module - or just that function.)
 
I wonder how many times this debugging feature has gotten actual use. Well, compared to the actual times modders have entered a value for this parameter it should be "almost never".

It is helpful for diagnosing out-of-sync problems in multiplayer. Since many mods never bothered to write the correct code to support multiplayer, I can see why this does not seem useful to some people.
 
Yeah, but most people experiencing OOS would be mere players and not even aware of any diagnostics. And not all modders would be equipped to do this either. But you basically mean that there would be no OOS issues to talk about if the modders/developers would just take this stuff into consideration while creating their mods? (So the string value is actually meant to be used.)

I still think that unless you plan on doing any of this diagnostics business the second string argument is just a nuisance. And not all mods are designed for multiplayer games to begin with.

But I might be missing the point here: Perhaps there is another equally good (well not for diagnostic purposes) random number generator in the API? What about Python as such - it comes with its own random number generator, right?
 
Before you discuss too much about random numbers, you should understand how multiplayer works. One key requirement for multiplayer is that every player in the game must generate the same random number. Please see this tutorial. If you want to break multiplayer on purpose, use your own random number generator. If you want to not make multiplayer any harder, use getSorenRandNum. If you want to make multiplayer a little easier to debug, put a unique string in there too.
 
Yeah, that sound about right. I merely suggested that game/scenario designers aren't making good use of the debugging feature offered by Sören. Because then there wouldn't be all these OOS issues all over the place. If I'm understanding you correctly.

What about having the getRandNum() wrapper method set the string value to something that is actually useful:
Code:
game = CyGame()
...
def getRandNum(iNum):
    iGameTurn = game.getGameTurn()
    pPlayer = gc.getPlayer(game.getActivePlayer())
    name = pPlayer.getCivilizationShortDescription()
    debug = "getRandNum(): " + str(iGameTurn) + " " + name
    return game.getSorenRandNum(iNum, debug)
 
The problem is that that does nothing to tell you which line of code that called your getRandNum is the one that is the problem. By just directly using getSorenRandNum and giving each call a unique text value you can then tell where the problem actually happened in the code. When debugging the code, what the turn number is and which player was active is generally nowhere near as important as knowing which line of code you are talking about.

(Besides which, the active player is always the player who is on the computer that is running the code. The same code is run on every computer in parallel, which is why they have to stay in synch. When a turn is being processed in a multiplayer game, the active player is the one on the computer running the code so it is different for each human player and never indicates anybody but themselves. As far as I know there is no function you can call in Python to tell which of the players/civilizations is currently having his turn processed. I have had to resort to saving the info in onBeginPlayerTurn so that I could tell elsewhere.)
 
And even knowing which player's turn was being processed might be misleading. Say you add a random chance of getting 1:gold: every time a unit of yours wins a battle--on attack or defense. You are rolling the dice on defense since it's your bonus, but the random number would be attributed to the attacker.

No, understanding the point of that debug string is much better than trying to make it go away. ;) I'm usually the one to twist myself in knots trying to make an API streamlined, but this is a case of "more is better" because "less is useless".
 
Top Bottom