Python Loops and Counter ...

Shqype

Shqyptar
Joined
Nov 15, 2005
Messages
2,468
Location
New York + Shqypni
For a scenario I'm creating I need to be able to create a unit every x amount of turns. As far as I can tell the best way to do this would be to use a loop with a counter (by my own programming experience with Java).

What is the easiest way to use loops? And are there any tricks to using a counter that would better help me out? This kind of information could be used for purposes other than adding a new unit every x amount of turns. Perhaps someone can create something like this and put it in the mod components section for easy access?

Also, I remember someone creating a sort of "cleaned up" version of the Python EventManager to make things easy for modders. If anyone knows where it is, I'de appreciate being directed to the thread.

Thanks :)
 
Shqype said:
For a scenario I'm creating I need to be able to create a unit every x amount of turns. As far as I can tell the best way to do this would be to use a loop with a counter (by my own programming experience with Java).

What is the easiest way to use loops? And are there any tricks to using a counter that would better help me out? This kind of information could be used for purposes other than adding a new unit every x amount of turns. Perhaps someone can create something like this and put it in the mod components section for easy access?

Your best bet would be to use something like SD-Toolkit:
http://forums.civfanatics.com/showthread.php?t=146130

Shqype said:
Also, I remember someone creating a sort of "cleaned up" version of the Python EventManager to make things easy for modders. If anyone knows where it is, I'de appreciate being directed to the thread.

Thanks :)

Here is the link for the new EventManager:
http://forums.civfanatics.com/showthread.php?goto=newpost&t=157141
 
Thanks for your reply, TheLopez, but it seems that neither of them are exactly what I was looking for:

Right now I do not see how the SD Toolkit would help me (perhaps this is a result of my lack of knowledge), but I don't yet have much use for it. Dr Elmer Jiggle's Python Utility Library looks like it could be more useful, but I could have sworn there was another thread by a different that has what I'm looking for ...
 
Shqype said:
For a scenario I'm creating I need to be able to create a unit every x amount of turns. As far as I can tell the best way to do this would be to use a loop with a counter (by my own programming experience with Java).

I don't know why you need a loop. All you need to do is query the current turn and use an algorithm to see if it is a turn you wish to build a unit on (assuming you intend to start counting form the same turn every game). OnBeginGameTurn even provides you the current turn as a parameter.

If the start of your loop is based on some event (building built, etc.). Then you'll want to establish an appropriate repository for the turn the initial startup criterion was met. Even in this case it is simply a matter of retrieving this start turn and then performing the same sort of algorithmic test as above. No loop necessary :).
 
Thanks Belizan, after I created this thread while in class I thought to myself "if the function is called every turn then I don't need a loop."

I could just add 1 to a counter initialized at 0 until it reaches x, then just set it back to 0, no?

When a certain condition is met, a unit gets created. Then the counter variable gets +1. If x+1 is reached, then create the unit again, and reset the counter to 0.

Would it work like this?
 
You could make your own counter, but the game keeps track of turns for you (hense implicitly giving you a global counter for turns). If it is really only *turns* which you wish to track, you should simply record your start turn, and algorithmically determine your event points.

For instance, if you started building units on Turn Y and you build them every X turns then your code would look something like this...

Code:
if ((iGameTurn - Y) % X == 0):
    # Spawn Unit

Ironically, the more difficult question is how/where to store your start turn offset (or counter if it turns out you really need one).
 
Thanks alot for this "if ((iGameTurn - Y) % X == 0):" Belizan, I would have done it a more difficult way. This is easy and simple :)

Isn't there a method having to do with improvements built? That is what I would like to trigger the "counter." Looking at Kael's python code from his Fall from Heaven mod, I see that he cleverly used improvements to do some interesting things:

Code:
def onImprovementBuilt(self, argsList):
		'Improvement Built'
		iImprovement, iX, iY = argsList
		pPlot = CyMap().plot(iX,iY)
		pPlayer = gc.getPlayer(pPlot.getOwner())
# ...... My additions (Animal Den based on Kael's idea)
	if ((iImprovement == gc.getInfoTypeForString('IMPROVEMENT_ANIMAL_DEN') and ((iGameTurn - Y) % X == 0)):
		newUnit = pPlayer.initUnit(gc.getInfoTypeForString('UNITCLASS_BEAR'), pPlot.getX(), pPlot.getY(), UnitAITypes.NO_UNITAI)

Correct me if I'm wrong, but this is what the above code does:

If the improvement "Animal Den" is built then it gives a new unit, "bear," to the player on the plot that the improvement was built. The latter part of the if condition will just amount to Y-Y so it of course will equal 0. The problems with the above code are as follows:

-It only occurs once per each Animal Den, since an improvement can only be built once on the same plot. Thus, there is no need for the "counter" since this method won't be called again until another such improvement is built, and it will be on a different plot unrelated to this one. I need a continous flow of units every few turns, not a one-time deal.
-The Y variable is a single variable that cannot keep track of the multiple improvements that would create these units. I would need to have a different Y value for each improvement plot.

I think the above should therefore be changed to:
Code:
if (iImprovement == gc.getInfoTypeForString('IMPROVEMENT_ANIMAL_DEN'):
		newUnit = pPlayer.initUnit(gc.getInfoTypeForString('UNITCLASS_BEAR'), pPlot.getX(), pPlot.getY(), UnitAITypes.NO_UNITAI)
		#initialize improvement plot-related Y variable

Then, OnBeginPlayerTurn can call a separate method which cycles through the different Animal Den plots and uses the equation to see if the correct amount of turns has elapsed before another unit is created.

Obviously, my next question is how I can accomplish this, and how I can create "improvement plot-related Y variables."
 
Yes you will want to break it up into two parts, an initialization period and a periodic check. Given that not all improvements are owned by a player, and ownership can shift during the course of a turn, you might want to use OnBeginGameTurn instead.

Now, the system I use in my Monster lairs and Ruins mod is to store this sort of information in the ScriptData of the plot (because each plot can only have one improvement--roads are features). What the ScriptData "services" let you do is store an arbitrary stream of bytes associated with an object within the game engine (meaning it is persisted, even across game saves). This is cool. Now, you can just set the script data to the turn your improvement was built, if you like. Personally, I'm not a Python programmer historically, and I haven't spent much effort in really learning the capabilities of the language or the libraries which have been built for it to date. I have, however, spent some time reading over the works of modders more knowledgable about Python then I ;). There is an object serializer for Python called Pickle (because you pickle the objects). A serializer is basically an object which takes a Python object (any object) and converts it into a stream of bytes which can be saved and later restored to reinstantiate that object (like a snapshot). Sort of like one of those kids books with the 3d castles inside. It flattens up the object so it can be stored. Python provides some built-in collections, one of which is the Dictionary, which lets you associate arbitrary keys with arbitrary python objects. So here's the cool part. A Dictionary is an object. So you use Pickle to serialize it and store in the ScriptData of the plot, and now you can persist as much information as you like in it.

Not sure I explained that well, so let me find some code to cut and paste.

This is written for my fuel mod, but the same code works for plots as for units--

Code:
def initUnitDictionary(pUnit):
    unitDict = {}
    pUnit.setScriptData(cPickle.dumps(unitDict))
    
def getUnitDictionary(pUnit):
    unitDict = cPickle.loads(pUnit.getScriptData())
    return unitDict
    
def setUnitDictionary(pUnit, unitDict):
    pUnit.setScriptData(cPickle.dumps(unitDict))

def getFuel(pUnit):
    unitDict = getUnitDictionary(pUnit)
    return unitDict["fuel"]
    
def refuel(pUnit):
    unitDict = getUnitDictionary(pUnit)
    unitDict["fuel"] = getMaxFuel(pUnit)
    setUnitDictionary(pUnit, unitDict)

So, in onImprovementBuilt, you initialize the plot with the Den to have a plotDictionary. Then onBeginGameTurn you walk all the plots on the map, check each for an improvement, check the improvement to make sure it's a Den, then retrieve it's turn created, and evaluate your equation for determining if you want to spawn a new animal. Although, I think you want to introduce some randomness, IMO.

Also, if you set a den to be a goody, the map generators will spread them around for you. You can set the goody to be bad if you want the AI to not want to explore them.

Anyhow, this should be more then enough information to get you going.
 
Randomness would be nice, maybe getting a unit from the improvement every 3-8 turns.

Now, what if I wanted to restrict this to a single player? Let's say for example I had a "Beastmaster" civilization, and "Animal Dens" were useless to everyone else except this civilization. What changes would need to be made?

Edit- Stupid question.
 
So by what you're saying it should be something like this:
onImprovementBuilt:
Code:
if (iImprovement == gc.getInfoTypeForString('IMPROVEMENT_ANIMAL_DEN'):
		newUnit = pPlayer.initUnit(gc.getInfoTypeForString('UNITCLASS_BEAR'), pPlot.getX(), pPlot.getY(), UnitAITypes.NO_UNITAI)
		def initPlotDictionary(pPlot):
  		plotDict = {}
    		pPlot.setScriptData(cPickle.dumps(plotDict))

onBeginGameTurn:
Here is where I'm more confused ... do I start like this?
Code:
for i in range (pPlot.getNumPlots()):
		pPlot = pPlayer.getPlot(i)

if pPlot.getImprovementType() == gc.getInfoTypeForString('IMPROVEMENT_ANIMAL_DEN'):
	def getPlotDictionary(pPlot):
    	plotDict = cPickle.loads(pPlot.getScriptData())
    	return plotDict

Now I'm not quite sure how to finish ...
 
Well, you could do that in a vast number of ways. The Beastmaster could be a UU of a given civilization and the only unit capable of building the Den. The Den could require a technology that only that civilization had access to. You could hard code the civ's id into your code by hand. You could make a leader trait called "Lord of Beasts" and check for it in your improvement code. At least I assume you can, I haven't played with programmatic access of leader traits yet 8). I'm sure there are many other ways to do it besides these. It all depends on how you feel like modeling it.
 
Yea, making the beastmaster a UU of the civilization is the smartest thing to do. I should have thought more about it before asking you.

Perhaps you can look over the code I put together and tell me what I'm doing wrong. :)
 
Yes :blush:
 
Code:
for i in range (pPlot.getNumPlots()):
		pPlot = pPlayer.getPlot(i)

if pPlot.getImprovementType() == gc.getInfoTypeForString('IMPROVEMENT_ANIMAL_DEN'):
	def getPlotDictionary(pPlot):
    	plotDict = cPickle.loads(pPlot.getScriptData())
    	return plotDict
From here, I'm not sure how to proceed. I understand the code as follows:
The first part cycles through the plots. If a plot has an improvement called "Animal Den" then the plot dictionary (which was created in onImprovementBuilt) is loaded and returned.

What to make of this dictionary (and how to use it next) I'm not sure. I also notice I have nothing in here for recording the turn that the plot was created (onImprovementBuilt).

Using your code, I tried to alter it to make it meaningful to the situation:
Code:
def initImprovementDictionary(iImprovement):
    ImprovementDict = {}
    iImprovement.setScriptData(cPickle.dumps(ImprovementDict))
Wouldn't it be better to record the turn in the same dictionary as the plots? Or can pickling only handle 1 object?
 
Ok, I need to go to lunch, so I don't have time to post a real response at the moment. I can see why your code is not working, though :). You are misusing a great number of things 8). To start with, I would suggest you walk through the Python Tutorials in the Modder's guide to Civ4 thread, stickied in this forum. Most of your confusion will likely be cleared up as a result of doing that. My other apriori comment is that set/getScriptData() is a method defined on some of the data classes provided by Firaxis in their engine. It is not defined on things like integers or enumerations :). If you don't know what I mean by that, just go through the Python tutorials for now, and I'll post a more lengthy explanation a bit later on.
 
Right now I am not familiar with dictionaries and need to start some experimentation before I begin with them. But, I will try to keep it simple and try to get the code working with only 1 improvement. This is what I've come up with:

onImprovementBuilt:
Code:
def onImprovementBuilt(self, argsList):
		'Improvement Built'
		iImprovement, iX, iY = argsList
		pPlot = CyMap().plot(iX,iY)
		pPlayer = gc.getPlayer(pPlot.getOwner())
# ...... My additions (Animal Den based on Kael's idea)
	if (iImprovement == gc.getInfoTypeForString('IMPROVEMENT_ANIMAL_DEN')):
		newUnit = pPlayer.initUnit(gc.getInfoTypeForString('UNITCLASS_BEAR'), pPlot.getX(), pPlot.getY(), UnitAITypes.NO_UNITAI)
		iImprovementTurn = gc.getGameTurn()

onBeginGameTurn:
Code:
for i in range (pPlot.getNumPlots()):
		pPlot = pPlayer.getPlot(i)

if (iImprovement == gc.getInfoTypeForString('IMPROVEMENT_ANIMAL_DEN') and ((iGameTurn - iImprovementTurn) % 5 == 0)):
	newUnit = pPlayer.initUnit(gc.getInfoTypeForString('UNITCLASS_BEAR'), pPlot.getX(), pPlot.getY(), UnitAITypes.NO_UNITAI)

In theory it should work, I'll test it out later.
 
Well, it doesn't work...

First and foremost, I combined 2 mod components: ActionButtonUI by talchas and Custom Event Manager by Impaler. There must be a conflict since upon loading the game I get an error:
Failed to load python module CvEventInterface.
If someone can explain to me what the error is and why it happens (so I could learn not to make the same mistakes in the future) it would allow me to proceed.
 
Back
Top Bottom