Chain-linking functions

You should never need to use the globals() function. If class B is a subclass of class A (what you are calling subordinate), it has access to the fields and functions of B via self.
You mean that class A can call on any function belonging to class B with using "self."? I never got this to work, but I have managed to work around it by adding a function that returns the other class instead of "self". And I seem only to be able to share variables between classes by using the "global" command.

But I did manage to spawn a city using functions from three different classes on one single line :king::
Code:
Spawn(iCiv).trigger().turn(iTurn).city().cityPlot(iX, iY).foundCity()
The Spawn class ended up being first, since any kind of spawn needs to have a owner, and I figured that the checks for human players and dead Civs would be the most basic ones. My code looks like this:
Spoiler :
Code:
class Spawn:

        def __init__(self, iPlayer):
                global iCiv
                iCiv = iPlayer
                global player
                player = gc.getPlayer(iCiv)
                self.bAlive = player.isAlive()
                global bTrigger
                bTrigger = self.bAlive
                

        def trigger(self):
                return Trigger()


class Trigger:

        def __init__(self):
                self.gameTurn = gc.getGame().getGameTurn()
                        

        def isTrigger(self):
                return bTrigger


        def turn(self, iTurn):
                if self.isTrigger():
                        if iTurn != self.gameTurn:
                                bTrigger = False
                return self


        def city(self):
                return City()


class City:

        def cityPlot(self, iX, iY):
                self.iX = iX
                self.iY = iY
                return self


        def foundCity(self):
                if Trigger().isTrigger():
                        player.found(self.iX, self.iY)
I'm sure you can point out better ways of doing this... :rolleyes: I also tried creating a hierarchy of classes with indentation (as I've seen done in PyHelpers) but that didn't do anything useful for me.

What I meant when asking for an example is a concrete example from a scenario:
Your examples were all good, but here goes (from my Russia scenario):
Spoiler :
  • On turn 201 a Fort improvement appears on tile (68, 56).
  • If the present city on tile (69, 52) is razed, then the new size 1 city "Kiev" appears on turn 207 with one Archer unit. The city belongs to the Russians but will only spawn if they are still alive.
  • On turn 209 a Town improvement appears on tile (68, 56).
  • On turn 211 the size 1 city "Novgorod" appears on tile (68, 56) along with one Archer unit. It belongs to the Russians if they are not controlled by the human player (and alive), otherwise it belongs to the Independents.
  • On turn 235 a Town improvement appears on tile (72, 55).
  • On turn 236 the size 1 city "Moskva" appears on tile (72, 55) along with one Longbowman unit. The city belongs to the Independents and has Christianity preset.
  • Between the turns 206 and 240 a Barbarian Horse Archer unit appears every three turns on a random tile inside the area defined by tile (69, 49) and tile (77, 50). The unit has the AI setting of "UNITAI_PILLAGE".
The unit and city spawns above also have checks for the target tiles. Some are forced and some not.

But since you're a programmer, first and foremost, you can find examples of functions available with my existing code here and here. This new setup (discussed in this thread) will be able to do all that and more, as the modularity gives more options for the scenario maker. So I'll be hacking the existing functions to bit and pieces so that the code can then be rearranged by the modder by stacking functions like I did above.

edit: As it turns out, I did a poor job of testing the code posted above. It will trigger on any turn, so I have rewritten the code and am currently passing on the bTrigger value from the Spawn class to the Trigger class and finally to the City class. So I guess I can't use a global bTrigger either... Is there another way of doing this?
 
I'm working on this further, and right now I'm trying to get this setup to work:
Code:
spawn = CustomUtils.Trigger()
spawn.[COLOR="Red"]turn(iTurn).player(iCiv).checkPlayer(bHuman, bDead).spawnCity(iCiv)[/COLOR][COLOR="SeaGreen"].cityPlot(iX, iY).checkCityPlot(bDomestic, bForeign, bForce).foundCity(iPopulation).garrison(bConscript)[/COLOR][COLOR="DarkOrange"].makeUnit(iNumUnits)[/COLOR]
This would involve 3 different classes:Trigger, City and Unit. Any thought on this?
 
There is a significant difference between class B subclassing class A versus composing or relating to it. You can usually pick the relationship based on how you describe it.

Subclassing typically involves "is a" relationships. A Car is a Vehicle. A Square is a Shape. Composition (a system being made up of multiple classes) occurs with "has a" relationships. A Car has an Engine and four Wheels. A Circle has a center (Point) and radius (simple integer).

BTW, this is all in that book, only better written. Hint hint. :D

So to allow your Trigger class to access values in the Spawn class you can give a Spawn reference to it.

Code:
class Spawn:
    def __init__(self):
        self.bTrigger = True
    def trigger(self):
        return Trigger([B]self[/B])

class Trigger:
    def __init__(self, [B]spawn[/B]):
        [B]self.spawn = spawn[/B]
    def isTrigger(self):
        return [B]self.spawn.bTrigger[/B]

I think you're on the right track, but you need to think about how to handle this Trigger class as those lines of code execute. What happens when a trigger is found to be inactive this turn? Do all the other objects after it that detail what would have happened if the trigger had been activated? That's a waste of execution time.

One way to bail out of an execution path is to use an exception. Exceptions are covered in that book so I won't try to explain them here.
 
That makes a whole lot of sense and I will meditate over your example. I can't thank you enough for teaching me this stuff! :goodjob:

And, yeah, that book looks like a very good place to start. Too bad I only heard of it now, while I'm in the middle of this work in progress. I will try to find the appropriate chapters and give them my full attention though, but reading through the whole thing might have to wait. (Was it 280 pages of computer science? :eek:) There is just too many things happening right now and I can't spare the time or attention. Unfortunately.

Now, if I only knew where to find the relevant information in that book...

By the way, this is what the entire library of functions related to city and unit spawns looks like as of right now:
Spoiler :
Code:
Trigger()
    .year(iYear)
    .turn(iTurn)
    .yearInterval(iYear1, iYear2)
    .turnInterval(iTurn1, iTurn2)
    .randomTurn(iInterval)
    .intervalTurn(iInterval, iModulo)
    .player(iCiv)
    .checkplayer(bHuman, bDead)
    .isTrigger()
    .spawnCity()
    .spawnUnit(iUnitType)

City(iCiv)
    .cityPlot(iX, iY)
    .checkCityPlot(bDomestic, bForeign, bForce)
    .setCityName(name)
    .setCulture(iCityCulture, iPlotCulture)
    .setReligions(lReligions, bErase)
    .setBuildings(lBuildings)
    .foundCity(iPopulation)
    .garrison(bConscript)

Unit(iCiv, iUnitType)
    .plot(iX, iY)
    .area(tTL, tBR, bLand, bDomestic, bForeign)
    .checkPlot(bDomestic, bForeign, bAdjacent)
    .setUnitLevel(iLevel, iXP)
    .setPromotions(lPromotions)
    .setAI(AIType)
    .makeUnit(iNumUnits)
These functions would be the ones I would have to cover in any sort of documentation. Trigger().isTrigger() is a function allowing one to use the Trigger class functions in an if statement, as it returns a boolean. Trigger().SpawnCity() and Trigger().SpawnUnit() are "carry-over functions" that supply the arguments required by the City and Unit classes. City().garrison() is another one of those, as it will supply the iUnitType value. (It can either be the standard defensive unit of the era - or the unit that would be available for drafting in the city in question - hence the boolean.)

The whole system will have some default settings also, so that one can skip functions without rendering the incomplete line nonfunctional. Like the default player would be the Barbarians, or the default unit type would be whatever is available to that team in terms of a defensive unit. So a very basic line would produce defensive barbarian units, then:
Code:
spawn = CustomUtils.Trigger()
spawn.randomTurn(iInterval).area(tTL, tBR, bLand, bDomestic, bForeign).makeUnit(iNumUnits)
Maybe there would also be default settings for tTL and tBR making the whole map available for random spawns without using the area() function? (The default settings for bLand would then be True, as you'd wanna spawn land units as default, and False for both bDomestic and bForeign, as the units don't have to spawn within domestic culture, but cannot be within foreign culture either.) This would take out yet another function...
Code:
spawn.randomTurn(iInterval).makeUnit(iNumUnits)
That's what I call easy-to-use unit spawning! :king:
 
Looking good. That book may be 280 pages printed, but I bet you could skim the stuff you already knew and chew on the meaty stuff in a weekend or two. Imagine all the time you'll save yourself over your lifetime by learning how to use those powerful tools now. It may seem like you have limited time to do stuff now, but you'll be much more effective and efficient if you learn the right way early. As one who had to learn a lot of this stuff by trial-and-error because there simply wasn't this much information available at the time, take my word that it's time well spent.

One helpful thing with Python is the ability to provide default values for parameters so that the caller can name their parameters without having to provide values for each one. This minimizes typing and more importantly makes it easier to read the code and see what it should do.

In one of BUG's helper modules (PlayerUtil) I have a function players() that returns a list of all players matching any of four criteria. For each parameter None means do not filter, True means it must be that type, False means it must not be that type.

Code:
def players(human=None, alive=None, minor=None, barbarian=None):
    ...

To get all non-human, non-barbarian players you can use this:

Code:
players(False, None, None, False)

or this:

Code:
players(human=False, barbarian=False)

The first form has less typing, but you'll never remember the order while the second form is completely explicit. If you read that code 6 months later it's obvious what is happening. The beauty is that you leave it up to the modder to pick whichever they prefer--both work with the same function def.
 
Yeah, I totally understand the value of learning the basics of something before trying to do it myself. So I'll be sure to check that book out. (In fact, I'm browsing through it right now.) If I only had this text a couple of months ago, I could have easily saved some 100 hours of head scratching...

So thank you again! I'll be sure to pass this tip on to anyone struggling with Python I encounter from now on. :goodjob:

As for your example, I think I get the point of what you describe, but it does seem a bit alien to me. Had I read that in a non Civ context I wouldn't have thought twice about it, since it clearly doesn't apply to what I'm doing. But I guess I would be wrong...

Why is there no "self" argument in the players() function? And how can you feed it only two arguments when it requires four?

The answer to the second question would be that you've already set all the arguments by default. so that you don't need to use any if you just wanna use the defaults. If I manage to wrap my head around this I believe that it could be infinitely useful!

Speaking of the self argument - can this:
Code:
def player(self, iCiv):
        self.iCiv = iCiv
be replaced with
Code:
def player(self, self.iCiv):
? (I can see why not, and I haven't dared to try it!)

I have another question also that I probably should try to find in the textbook: I've used argsList as an argument many times, but I haven't fully figured out how I can have it contain any number of arguments and still have it work in functions. An example:

I tried to make a spawn function that could both be used for setting plot coordinates (two values) or to define an area (two times two values). I never did manage to make it easy to use though, so I settled for two different functions instead. (Or you could just enter the same tile coordinates twice, but its not the elegant solution I was looking for.)

What am I missing here? (The book again? Sure... :rolleyes:)
 
If I only had this text a couple of months ago, I could have easily saved some 100 hours of head scratching...

So imagine yourself saying this again in a couple months . . . or taking the time to read the book now. Hehe, okay, I'll stop pushing the book now. :p

Why is there no "self" argument in the players() function?

Because the players() function is a module-level function. It does not belong to a class. There are three types of functions in Python. First, they are all functions and are only special due to their context.

  • Module-level (global) functions. These are plain-old functions from most every other procedural language (C, Perl, PHP, Pascal, Basic, Tcl, etc). They take a normal set of parameters that are not constrained in any special way (e.g. no magical self parameter). Examples include "def multiply(x, y): return x * y"
  • Function-level functions. These are functions that are defined inside another function. Their parameters are also not constrained. They are special because they have access to the local variables of the function that contains them. These can be tricky, and you'll rarely use them. I mention them only for completeness.
  • Class-level functions (aka methods). These are defined inside a class block and receive the class instance as their first parameter (typically called self).
As you've seen, when you do this

Code:
class Car:
    def __init__(self, name):
        self.name = name
    def getName(self):
        return self.name

honda = Car("honda")
print honda.getName()

inside __init__() and getName() "self" is a reference to "car"--the Car instance named "honda". Notice that the "self" parameter is provided by Python automatically. You could call the above getName() function like this:

Code:
print Car.getName(honda)

Here "Car.getName()" access the function without a class instance as a normal function. No value for "self" is passed automatically, and so you must pass "honda" yourself. Obviously this is not what you should normally do since it takes more typing and has other ramifications that I won't describe here. But it illustrates that all these functions are similar--it's the context that makes them special.

Module-level functions don't belong to a class, so they don't take the self parameter and don't receive one either. My players() function is one of these.

And how can you feed it only two arguments when it requires four?

The parameters have default values, making them optional. If the caller doesn't provide a value, they take on the defaults you give in the definition. Any optional parameters can be passed by name in a caller, allowing the caller to provide them in any order they wish. Only optional parameters can be handled this way.

Speaking of the self argument - can this:
Code:
def player(self, iCiv):
        self.iCiv = iCiv
be replaced with
Code:
def player(self, self.iCiv):
?

No, each parameter name must be precisely that: a simple name. "self.iCiv" is not a name--it is an attribute lookup (the iCiv attribute of the self reference).

I've used argsList as an argument many times, but I haven't fully figured out how I can have it contain any number of arguments and still have it work in functions.

You can pass a list or tuple as an argument value. A reference to any object--a list, tuple, set, dict, class instance, int, string, function, etc--is a single value. That it points to an object that contains multiple values doesn't matter. The reference that you pass is what matters, and it's singular.

You can do cool tricks to allow a function to receive different sets of parameters, but honestly at this stage you're better off using two functions with different names.

Here's an example to show how it works, just to whet your appetite. Yes, this is covered in the book, but it's definitely a more advanced feature of Python.

Code:
def multiply(*args):      # all arguments are packed into the args variable (a list)
    result = 1
    for value in args:    # loops over each value in the list
        result *= value
    return result

Since args is just a simple list you can check how many values it has using len(args):

Code:
if len(args) == 2:
    # do 1 x,y point code
elif len(args) == 4:
    # do 2 x,y points code
 
So imagine yourself saying this again in a couple months . . . or taking the time to read the book now. Hehe, okay, I'll stop pushing the book now. :p
I hear 'ya, and you'll soon be getting questions regarding the contents of that book instead... :p

Because the players() function is a module-level function. It does not belong to a class.
Yeah, I figured that much, but thanks for explaining the context. But what is the benefit of not having a class for functions? Is having a class even necessary in many cases, if you're actually not using the class instance (with self) for anything? (I've seen lots of examples of this in code.) All it does is make it necessary to abbreviate the path to those functions, instead of just calling on the module name, right?

For example, I collect all my scenario making functions in a file called Custom.py and have all of my functions belong to the class Custom. In order to use those functions inside another file/module I would need to go Custom.Custom().function(). (Which I abbreviated to "custom = Custom.Custom()".) But if I don't have a class at all it would be sufficient to just call on Custom.function(), right? (Which is pretty much exactly what I achieved with the abbreviation.)

inside __init__() and getName() "self" is a reference to "car"--the Car instance named "honda". Notice that the "self" parameter is provided by Python automatically. You could call the above getName() function like this:

Code:
print Car.getName(honda)

Here "Car.getName()" access the function without a class instance as a normal function. No value for "self" is passed automatically, and so you must pass "honda" yourself.
This really doesn't make sense to me, but I guess a certain book would enlighten me then? :rolleyes: What do you mean by the self parameter being provided automatically - and not? I don't think I'm seeing this in your examples.

How can you feed an argument ("honda") to getName() when it only takes "self"? But I guess that is the reason why you don't have to provide the "name" argument to Car(), but in my limited experience there would already have be a error message complaining about the function receiving too few arguments...

The parameters have default values, making them optional. If the caller doesn't provide a value, they take on the defaults you give in the definition. Any optional parameters can be passed by name in a caller, allowing the caller to provide them in any order they wish. Only optional parameters can be handled this way.
Yeah, its nothing short of fantastic! :king:

Could I get away with this?
Code:
def foundCity(self, tCoords=(-1, -1), iCiv=iBarbarians, name=None, iCityCulture=0, lReligions=[], lBuildings=[], iPopulation=1):
So you could use this function to spawn barbarian cities without any arguments, then? (If all you wanna do is spawn barbarian cities on random map tiles - as the "-1" coordinates would trigger a random map search for suitable land tiles.) And if you wanna set one of the arguments you go "iPopulations = 2" or whatever. This would pretty much make most of the functions I was planning on including in the stacking redundant! :eek:

You can do cool tricks to allow a function to receive different sets of parameters, but honestly at this stage you're better off using two functions with different names.
Yeah, I gathered this much, but as I said: it was another thing altogether to make use of it.

Here's an example to show how it works, just to whet your appetite. Yes, this is covered in the book, but it's definitely a more advanced feature of Python.
I believe I see what's going on here (the function multiplies all the values in args and returns the result), but I don't see what good the asterisk is doing in the definition line. :confused:

Also, I've always looped things inside lists with:
Code:
for loop in range(len(list)):
        value = list[loop]
Your method seems superior! :D

Since args is just a simple list you can check how many values it has using len(args):
My problem was that I couldn't use len to determine how many sets of coordinates were in argsList, because the amount of entries was always 2. Like:
Code:
(x, y) or ((x, y), (x, y))
The whole point was not to have four values inside the same parenthesis, but rather to allow for two sets of parenthesis - optionally. As I said, len always returned 2 (as it should). So I tried something along the lines of len(argsList[0]) and such, but that didn't work out either. Maybe I was trying to get flexibility where there really is none?
 
But what is the benefit of not having a class for functions?

You pretty much covered it. The question is really why have a class if one doesn't make sense? Sometimes people will group related functions into a class definition, but that's precisely what a module (.py file) is for. If there is no logical class, don't force one on the situation.

Which I abbreviated to "custom = Custom.Custom()".

Right, but if no one will access anything in Custom (the module), but Custom (the class) doesn't have any data members (only functions), then just remove the class and put the functions in the module. I think what you might be missing is what a class really provides: data encapsulation.

For example, let's say you wanted to create your own List class. Sure, you can do things to a list like get its length, append values, cut it in half, test if it's empty, etc. But that's only half the story. The really important bits are the data a class stores in each instance created from it.

The List class itself--the actual class definition--doesn't hold any data. It is merely the idea of a list. All it does is define functions. But when you instantiate (create) a list, a spot in memory for its data values is created and its __init__() function is executed with self pointing to that memory. "self" is the class instance (object)--not the class itself.

Code:
class List:
  def __init__(self):
    self.size = 0
    self.values = []
  def getSize(self):
    return self.size
  def add(self, item)
    self.values.append(item)
  def isEmpty(self):
    return self.getSize() == 0

All these "self"s refer to whatever List instance you happen to call the functions on.

Code:
foo = List()
foo.add("hello")
foo.add("world")

bar = List()
print bar.isEmpty()
> True

Inside the function calls on the first three lines, "self" points to the same object that "foo" points to. In the next two lines "self" refers to the object pointed to by "bar". You can also access the data values using foo and bar directly:

Code:
print foo.size
> 2
print foo.values[1]
> world
print bar.size
> 0

Think of a class instance as a container of values. The class defines functions that operate on those values through the passed-in "self" value. When I say that the value for "self" is provided automatically when calling a function through a reference, I mean that you don't place the value that goes into the "self" variable inside the ()s of the function. Instead, it goes in front of the function call.

Code:
foo.add("hello")

"foo" points to a List instance. When this function executes its first parameter--"self"--gets assigned the same reference that foo has automatically by Python. The second parameter--value--gets assigned the string "hello", even though it looks like it's the first value being passed to add(). When you call the function through the class instead of through the foo instance you must pass in a value for self:

Code:
[B][COLOR="Red"]List[/COLOR][/B].add([B][COLOR="Red"]foo[/COLOR][/B], "hello")

Do you see the difference? Here you can see that foo is assigned to "self" inside the function.

Could I get away with this?
Code:
def foundCity(self, tCoords=(-1, -1), iCiv=iBarbarians, name=None, iCityCulture=0, lReligions=[], lBuildings=[], iPopulation=1):
So you could use this function to spawn barbarian cities without any arguments, then?

Yes, that would work, but then that function will be very complicated. It's better to have short functions that do specific things. Twenty lines of code for a single function is bordering on too long in my book. Sure, sometimes it's impractical to split apart a complex function, but it's much harder to understand a function later if it's very long. I target 5-10 lines of code per function for a lot of my coding. I don't count the function declaration itself (parameters) nor its documentation.

But to be clear, you understand the concept correctly.

This would pretty much make most of the functions I was planning on including in the stacking redundant! :eek:

As I just wrote, I think a nice API of small functions will be better for people to understand. They can learn bits and pieces as they need them, and it'll be easier for you to write and add to later.

But I don't see what good the asterisk is doing in the definition line. :confused:

It collects all of the parameters you pass to the function into the list. Let's say you wrote a similar multiply() function for two arguments:

Code:
def multiply(x, y):
  return x * y

but you now what to allow 2 or 3 arguments:

Code:
def multiply(x, y, z=1):
  return x * y * z

Okay, 2-4 arguments:

Code:
def multiply(x, y, z=1, a=1):
  return x * y * z * a

You can see where this is going. Sure, you can leave off the * and make the caller put the arguments into a second set of parentheses:

Code:
def multiply(args):
  for ...

print multiply((2, 3, 4))

But that looks ugly. There will be cases where you want to allow one argument here, and that forces the caller to make a tuple when one doesn't already exist:

Code:
print multiply( (2,) )

Blech! By using the * it wraps all the arguments in a tuple for you--even if there's only one. Also you can have fixed parameters before it too. In any case, it's not commonly used. I mentioned it only because it can be handy when building highly-flexible APIs.

As for looping over lists, you can use the same loop construct over tuples (immutable lists), lists, sets, and dictionaries. Python makes dealing with standard collections very easy.

My problem was that I couldn't use len to determine how many sets of coordinates were in argsList, because the amount of entries was always 2. Like:
Code:
(x, y) or ((x, y), (x, y))

Ah, in this case you'd need to test the type of the elements in the tuple. You know you're already getting a tuple, but it might contain two integers or two more tuples.

Code:
def do(args):
  if isinstance(args, tuple):
    # two coordinate pairs
  else:
    # single coordinate pair

I probably have the args backwards in isinstance(). I always need to look them up when using it. :lol: But what it does is check the type of a variable's value against one or more classes (types).
 
This is pure gold, all of it! The good thing is I now have my own gold mine! :goodjob: (Not you, obviously, but the damned book. So I'll try not to do any code this week but to read up on this stuff instead.)

I kinda get what you're telling me and I suppose it will all be clear enough to me eventually. I think I'll just have to tinker with it some more.

Are you putting in errors in your code examples to test me, or would I be wrong of me to correct you? :p Because I'm pretty sure that line 6 should read:
Code:
    return len(self.size)
Either that or you know something vital I'm completely missing.

One thing I'm having a hard times swallowing is that you call on a class which takes the self argument without any parenthesis or anything. Like:
Code:
print List.getSize(value)
instead of
Code:
print List().getSize(value)
But I guess that would be the whole point, then?

What if the init definition requires additional arguments? Can you skip that also and just feed the argument to the function, in some way?


You pretty much covered it. The question is really why have a class if one doesn't make sense? Sometimes people will group related functions into a class definition, but that's precisely what a module (.py file) is for. If there is no logical class, don't force one on the situation.

(...)

Right, but if no one will access anything in Custom (the module), but Custom (the class) doesn't have any data members (only functions), then just remove the class and put the functions in the module. I think what you might be missing is what a class really provides: data encapsulation.
So there wouldn't be any of that self stuff then either, would there?

I can think of another reason to put functions into classes, and that is for importing them to other modules, right? So if I have several classes in my CustomUtils.py file (or the CustomUtils module) but only wanna import the SpawnCity class for easy use in another module (like the Scenario module), I would use:
Code:
from CustomUtils import SpawnCity
And perhaps even:
Code:
from CustomUtils import SpawnCity as city
That would make it worthwhile to have a class even if it didn't store any data in the class instance. Or would that not be a good enough reason?

Yes, that would work, but then that function will be very complicated. It's better to have short functions that do specific things. Twenty lines of code for a single function is bordering on too long in my book. Sure, sometimes it's impractical to split apart a complex function, but it's much harder to understand a function later if it's very long. I target 5-10 lines of code per function for a lot of my coding. I don't count the function declaration itself (parameters) nor its documentation.
Ok, this proves I'm on the right track then. Because this would be an example of one of the previous functions I made prior to all this modular business:
Code:
        def createHostileUnits(self, iCiv, bHuman, tCoords, iUnitType, iNumUnits, iLevel, bPreset):
                if (self.checkPlayer(iCiv, bHuman)):
                        tCoords = self.plotSearch(iCiv, tCoords, True, False, True)
                        if (tCoords):
                                iLevel, iXP, lPromotions = self.unitLevel(iCiv, iUnitType, iLevel, bPreset, False, True)
                                self.makeUnit(iCiv, tCoords, iUnitType, iNumUnits, iLevel, iXP, lPromotions, UnitAITypes.UNITAI_ATTACK)
So almost every line makes a call to another function and the whole thing is only 6 lines long, yet it offers many features and does complex checks and sets new variables based upon the arguments given. I guess this is what you meant, then?

As I just wrote, I think a nice API of small functions will be better for people to understand. They can learn bits and pieces as they need them, and it'll be easier for you to write and add to later.
Ok, so I would replace this approach:
Code:
custom = CustomUtils.Custom()
def checkGameTurn(self, iGameTurn):
        if iGameTurn = 101:
                custom.createHostileUnits(iCiv, bHuman, tCoords, iUnitType, iNumUnits, iLevel, bPreset)
With the API setup:
Code:
spawn = CustomUtils.Trigger()
spawn.turn(101).player(iCiv).checkPlayer(True, False).spawnUnit(iUnitType).plot(iX, iY).checkPlot(False, False, True).setUnitLevel(iLevel, iXP).setAI(UnitAITypes.UNITAI_ATTACK).makeUnit(iNumUnits)
This would require much more typing but would also offer a couple extra settings and the option to skip one or two. Some of those boolean settings for the checks could also be set by default, just like in your players() function. That way you'd only have to type in the function and no arguments in 9 out of 10 cases, and in 99 out of 100 cases it would be sufficient to only set one of them to a non-default setting.

But the API route could also be used in this way:
Code:
trigger = CustomUtils.Trigger()
spawnUnit = CustomUtils.Unit
if trigger.turn(iTurn).player(iCiv).checkPlayer(True, False).isTrigger:
        spawnUnit(iCiv, iUnitType).plot(iX, iY).checkPlot(False, False, True).setUnitLevel(iLevel, iXP).setAI(UnitAITypes.UNITAI_ATTACK).makeUnit(iNumUnits)
In this example there would be no real point in having a separate if statement for the trigger functions, but the scenario maker could add another check on that line, or perhaps only use the "trigger" to do something completely different (than a spawn). Like a pop-up message or flip some cities or something.

Ah, in this case you'd need to test the type of the elements in the tuple. You know you're already getting a tuple, but it might contain two integers or two more tuples.
Thank you again! I'll take another look at the isinstance command once I try this approach again.
 
Are you putting in errors in your code examples to test me, or would I be wrong of me to correct you? :p Because I'm pretty sure that line 6 should read:
Code:
    return len(self.size)

Haha, not intentionally. The above code, however, is correct. self.size is an integer that keeps track of the length of the list instead of calling len() on the list each time it's needed. I forgot to increment it inside add(), though.

Code:
def add(self, value):
  [B]self.size += 1[/B]
  self.values.append(value)

A List class is a simple example given that Python has its own List class already (called list).

getSize() is a function belonging to the List class. If you call it using a List instance such as foo and bar in my previous post, Python passes the reference as the self argument.

Code:
foo.add("hello")

But you can call the add function through the class instead. I can't think of any other way to explain it. When you type List() you are asking Python to create an instance of the List class and call List.__init__(). When you write List.add() you are skipping those steps and calling the function directly as if it were a module-level function. But it needs a List instance for self, and so you must pass foo above in the list of arguments.

Don't worry too much if you don't understand this. The book will clarify the difference between a class and an instance of that class. Using the Car metaphor: the class Car is the idea of a car. It describes how cars work and what you typically find on a car. Cars have an engine, tires, a steering wheel, doors, and a license plate. But Car is just an idea; I cannot give you Car. You cannot drive Car.

But the red Dodge Viper with license plate A583FHS is a specific Car (an instance of the Car class).

Code:
class Car:
  def __init__(self, make, model, year, plate):
    self.make = make
    ...

viper = Car("Dodge", "Viper", 1993, "A583FHS")
print viper.getNumDoors()
> 2
print viper.getMaxSpeed()
> 124 mph
print viper.isManual()
> True

print Car.getPlate()
> ERROR! Missing self argument
print Car().getLicensePlate()
> ERROR! __init__() requires 5 arguments, none passed
print Car.getLicensePlate(viper)
> "A583FHS"

One thing I'm having a hard times swallowing is that you call on a class which takes the self argument without any parenthesis or anything. Like:
Code:
print List.getSize(value)
instead of
Code:
print List().getSize(value)

I don't think I wrote that above. You need to pass getSize() a List instance somehow, either by calling getSize() from on instance or by passing an instance directly:

Code:
print List.getSize(foo)

getSize() doesn't take a "value" argument; it only needs an instance for self. add() takes a value:

Code:
List.add(foo, 5)

Here foo becomes self and 5 becomes value.

What if the init definition requires additional arguments? Can you skip that also and just feed the argument to the function, in some way?

I'm not quite sure I understand your question. __init__() is tricky because you won't have an instance to pass to it without using <class>() to instantiate it:

Code:
foo = List()    # creates instance of List and calls __init__() on it
List.__init__(foo)     # calls __init__() *again*, sure, but pointless

There's no way to split apart the first line so that you get an uninitialized instance of List, but you shouldn't need to.

So there wouldn't be any of that self stuff then either, would there?

Correct. If you have module-level functions they will not have self as an argument. There's no class to require an instance, and there's no self to store values.

I can think of another reason to put functions into classes, and that is for importing them to other modules, right?

That's not necessary. You can do all those same imports:

Code:
# Custom

def multiply(x, y):
  return x * y

...

# OtherModule

import Custom
print [B]Custom.multiply[/B](5, 2)

import multiply from Custom
print [B]multiply[/B](3, 4)

import multiply from Custom as times
print [B]times[/B](3, 4)

Ok, this proves I'm on the right track then. Because this would be an example of one of the previous functions I made prior to all this modular business:

I agree, that's a nice function. When you see one function calling several other functions to do small chunks of work it's called delegation--like when someone asks others to perform parts of a task. That also makes it possible for other functions to reuse those same bits for themselves.

You might want to check out some of the BUG code for examples. I wrote most everything in the Python/BUG subfolder from scratch, so those modules server as better examples. The stuff that starts with "Bug" (for example "BugOptions") are framework code and will be much more complex; don't start with those. ;) But things like PlayerUtil and TradeUtil (maybe) are better examples.
 
Ok, all good stuff! I'll be sure to revisit your posts as I sort this stuff out. :king:

I'm reading the Good Book right now, and it sure shows some of this stuff (like class instances) in an... interesting light. You mentioned the exceptions passage in the book, by the way, but it doesn't really list any useful exceptions for exiting my stacked functions (once bTrigger = False). The book does refer to the documentation on Python.org but could you just point me in the right direction here? The search function on the site isn't all that helpful but if I knew the exact thing I'm looking for I'm sure I could at least give this a try on my own. (Pride and all, you understand.)

I think you managed to confuse me by creating a List class when Python already has one. :crazyeye:

Regarding the import options available to me I realize that I can import all the __main__ functions to another module. I was rather thinking that if you have a large number of functions you could sort them in different classes and only import those classes you wanna use in another module. But I guess there would be no point in limiting one self to only one class of functions? :rolleyes: (Is there a reason not to import everything into every single module, by the way?)

As I said, I'm doing my homework now and have read pretty much everything up to page 158 (skipping some stuff not really relevant to Civ modding), so it is starting to get really interesting. :king:

I'll download BUG also once I get the chance, to see what you're being up to. You can pretty much expect some more stupid questions as I expect your code to be every bit as complicated as that confusing stuff I've seen in PyHelpers. ;) But then again, you are a professional programmer. (I guess you'd qualify for the title "Code Monkey" then? :lol:)
 
You mentioned the exceptions passage in the book, by the way, but it doesn't really list any useful exceptions for exiting my stacked functions (once bTrigger = False).

Using exceptions would be more work for the modders using your API, too. But if you went that route, you'd just create your own exception:

Code:
class TriggerFailed: pass

...

try:
  Trigger()...
except TriggerFailed:
  # some trigger wasn't satisfied, move along...
  pass

I don't suggest this as the way to go. The triggers you're creating sound to me to be less procedural and more like configuring events. I'd go the route I described earlier where the modder uses the Trigger classes to define all the triggers in their mod when it loads. This can be done once during initialization of the game. Then your code would check all the appropriate triggers during BTS events such as BeginGameTurn and BeginPlayerTurn.

So the modder uses the classes to say "when the game turn hits 250, if Russia if they are alive and AI, spawn Novgorod for them at x, y with 2 Archers" which creates an object that you store in your list of "things to check each turn."

Then onBeginGameTurn would loop over all of the triggers created by the modder, checking each one to see if it's ready. This starts to give you an idea of what exactly a trigger is.

A Trigger is a set of conditions and a list of actions. When every condition evaluate to True, the trigger is fired (its actions are executed). Triggers may be repeatable; otherwise they expire as soon as they are fired.

Code:
class Trigger:
  def __init__(self, repeating):
    self.repeating = repeating
    self.conditions = []
    set.actions = []
  def addCondition(self, condition):
    "Adds the given condition to this Trigger and returns itself for chaining."
    self.conditions.append(condition)
    return self
  def addAction(self, action):
    "Adds the given action to this Trigger and returns itself for chaining."
    self.actions.append(action)
    return self
  def isReady(self, context):
    "Returns True if every condition of this Trigger is ready."
    for c in self.conditions:
      if not c.isReady(context):
        return False
    return True
  def fire(self):
    "Returns True if this Trigger is complete and should be removed."
    for a in self.actions:
      a.fire()
    return not self.repeating

class Condition:
  def __init__(self):
    pass # nothing to do
  def isReady(self, context):
    return True # by default, empty conditions are always true

I added the "context" parameter to isReady() because each event will bring with it some context: the game turn (any xxxTurn), the player (Begin/EndPlayerTurn), etc. You can't easily declare each of these parameters because the isReady() function signature must be generic.

By turning the context into a class as opposed to using a simple dictionary of keys and values, you can have the context store things it has looked up already to be used by multiple triggers. As you can see above and below, one context will be created and passed to every Condition of every Trigger.

Here are some Context classes demonstrating the use of subclasses. Each successive Context class adds more information to its parent class.

Code:
class Context:
  def __init__(self):
    pass

class GameTurnContext(Context):
  def __init__(self, iGameTurn):
    super(GameTurnContext, self).__init__()
    self.iGameTurn = iGameTurn
  def getGameTurn(self):
    return self.iGameTurn

class PlayerTurnContext(GameTurnContext):
  def __init__(self, iGameTurn, ePlayer):
    super(PlayerTurnContext, self).__init__(iGameTurn)
    self.ePlayer = ePlayer
    self.player = None
    self.eCivilization = None
  def getPlayerId(self):
    return self.ePlayer
  def getPlayer(self):
    if self.player is None:
      self.player = gc.getPlayer(self.ePlayer)
    return self.player
  def getCivizliationType(self):
    if self.eCivilization is None:
      self.eCivilization = self.getPlayer().getCivilizationType()
    return self.eCivilization

You would define functions on the Trigger class that create a specific Condition subclass and add it to the Trigger, just as you've already planned. This still allows an experienced modder to define their own Condition subclasses and use them with the Trigger class without modifying the Trigger class directly by simply calling trigger.addCondition(MyCoolCondition(...)).

Code:
class Trigger:
  ...
  def forPlayer(self, ePlayer):
    return self.addCondition([B]IsPlayerCondition[/B](ePlayer))
  def forCivilizationself, eCivilization):
    return self.addCondition([B]IsCivilizationCondition[/B](eCivilization))

class [B]IsPlayerCondition[/B](Condition):
  def __init__(self, ePlayer):
    super(IsPlayerCondition, self).__init__() # call Condition.__init__()
    self.ePlayer = ePlayer
  def isReady(self, context):
    return self.ePlayer == context.getPlayer()

class [B]IsCivilizationCondition[/B](Condition):
  def __init__(self, eCivlization):
    super(IsCivilizationCondition, self).__init__() # call Condition.__init__()
    self.eCivlization = eCivlization
  def isReady(self, context):
    return self.eCivlization == context.getCivilizationType()

Just as with functions, by keeping the conditions very simple they can be just a few lines of code: __init__() stores configuration parameters and isReady() checks them against the context.

I think you managed to confuse me by creating a List class when Python already has one. :crazyeye:

It's not unusual to wrap an existing data structure inside a class, but you'll typically be adding some value to the data structure beneath it--not merely putting on a pretty face. :lol:

I was rather thinking that if you have a large number of functions you could sort them in different classes and only import those classes you wanna use in another module.

If the functions are unrelated enough to put into separate classes, why not just put them into separate modules?

Is there a reason not to import everything into every single module, by the way?

The technical answer is that it clutters up the namespace, degrading performance. The design answer is that it makes the module harder to understand. If every module imports every other module you can't tell what a module is supposed to do.

Code:
# WebBrowser.py

import io.file
import io.net
import io.mouse
import io.keyboard
import gui.window
import gui.button
... okay, you can get a feel what this module is going to do ...
import io.joystick
import graphics.3d.opengl
import graphics.3d.directx
import io.smartcard
import io.midi
import math.vector
import math.linearalgebra
import math.calculus
import physics.em.fieldanalysis
... WTH? ...

I'll download BUG also once I get the chance

Wait, you don't use BUG already? Hmm, I'm not sure I can continue helping an infidel. :trouble:

I kinda went crazy with the above code, so don't worry if you feel a little overwhelmed. The book will cover the technical details of everything I did up there, but the code probably won't be clear even after reading the book. I've had a few years to hone my software design skills, but you seem to be picking this up easily enough. :goodjob:
 
So, I could just use pass to exit my "stack", then?
Wait, you don't use BUG already? Hmm, I'm not sure I can continue helping an infidel. :trouble:
Yeah, since I'm trying to mod I didn't wanna confuse myself by using another interface also. Plus, I wanted to be sure that any error or unintended result was my own doing and not any kind of compatibility issue. (I'm basing my own scenarios and whatnot on RFC, by the way.) Once I start playing the game again (if I'm ever "done" modding...) I'll use it for sure. :king:

I kinda went crazy with the above code, so don't worry if you feel a little overwhelmed. The book will cover the technical details of everything I did up there, but the code probably won't be clear even after reading the book. I've had a few years to hone my software design skills, but you seem to be picking this up easily enough. :goodjob:
Heh, I'm struggling here, be sure of that. But since you're giving me nuggets all over the place, I'll try and sort this stuff out.

The book, by the way, starts describing classes as a way of storing data - not a way of defining functions. That's actually an interesting approach and once I get used to the idea I'm sure this all will be that much clearer. (I had to skip ahead to see if it even covers "self", as that didn't show up immediately. It turns out it does - it was actually comforting to see functions and classes that I could recognize... :lol:)
 
So, I could just use pass to exit my "stack", then?

No, pass simply does nothing. You can use it in places where you must have at least one statement (e.g. inside an if() or for() or function definition) but don't have anything interesting to do. The only way to alter the flow of execution outside of a function from within the function itself is to raise an exception.

Yeah, since I'm trying to mod I didn't wanna confuse myself by using another interface also.

I was kidding, and I understand this motivation. When you're ready, BUG has a lot of utility functions that will make modding easier. Plus it has a more strict initialization path that you can plug into.

The book, by the way, starts describing classes as a way of storing data - not a way of defining functions.

This is what I was getting at when I said the impetus for classes originally was data encapsulation. But data is nothing in a computer without some to manipulate it. The true power of classes is data merged with functions in a complete package. The data can be hidden behind the functions to provide a concise API that manages the data. This gives security that the data will remain valid as long as the functions are correct.
 
The only way to alter the flow of execution outside of a function from within the function itself is to raise an exception.
Aha, but you recommend another route altogether. Ok, got it. :king:
When you're ready, BUG has a lot of utility functions that will make modding easier. Plus it has a more strict initialization path that you can plug into.
I definitely need to take a look at this, then. So I pretty much would have to merge whatever I'm doing with BUG so that all those utils can be called upon in my scenario or scenario making tools or what ever? What do you mean "a more strict initialization path" by the way? Please, enlighten me! :king:

This is what I was getting at when I said the impetus for classes originally was data encapsulation. But data is nothing in a computer without some to manipulate it. The true power of classes is data merged with functions in a complete package. The data can be hidden behind the functions to provide a concise API that manages the data. This gives security that the data will remain valid as long as the functions are correct.
Ok, this makes sense and the book is gearing up to this, or so I believe.

You mentioned "BeginGameTurn" and "BeginPlayerTurn", by the way, and I believe I actually understand what you are talking about. :goodjob: Thus far I've been modding the RFC scenario and the way Rhye has done his "historical" events (and other events in the scenario) is by including a checkGameTurn() function in most of his modules. These are then called upon each turn from the custom event handler.

So my initial plan was to supply a template of a "Scenario" module with import commands, some variables and the checkGameTurn() function belonging to the Scenario class (following Rhye's example). Then I realized that there really wasn't any point in having a function nor a class (as that would just be confusing or at the very least require some kind of explanation). There would also be none of the indentation crap that Python illiterate folk seem to have a hard time getting. :rolleyes: (Since no if statements are required to trigger any events there really would be no need to use blocks of code.)

Now I'm all of a sudden thinking that a class definition might not be such a bad idea after all, since it can be used to store values shared by the function calls. I'll have to learn some more before I can formulate an actual example for when this would be useful, but maybe you could see some reason for including a class? (All constants could of course be defined in __main__ but there might be other values floating around?) :confused:

Any way, this is all just so much fun but all the learning required is really getting in the way of the creativity right now. But since I've failed miserably with good ideas before, due to lack of knowledge, I understand how this is gonna make it all so much more exiting in the end! :king: I think this scenario maker idea of mine could just be really good in the end, if I just have the stamina to keep at it. :p
 
You can certainly have all the triggers tested and fired from a single event each turn. I was thinking, though, that you might want to have triggers that fire if an AI loses a key city. Also, by "each turn" that means right after the barbarians take their turn. If that's fine, then cool. But if you want new unit spawns to happen immediately before an AI takes their turn, you'll need to trigger from BeginPlayerTurn.

You could have one of the functions on the Trigger class determine which event it should be stuck into. For example, Trigger.cityLost("London") would look up who owns London and attach itself to the onCityAcquired event trigger list.

You will learn faster by practicing as you read, so don't forget to take time to have fun and explore what you're learning. :)
 
You can certainly have all the triggers tested and fired from a single event each turn. I was thinking, though, that you might want to have triggers that fire if an AI loses a key city. Also, by "each turn" that means right after the barbarians take their turn. If that's fine, then cool. But if you want new unit spawns to happen immediately before an AI takes their turn, you'll need to trigger from BeginPlayerTurn.

You could have one of the functions on the Trigger class determine which event it should be stuck into. For example, Trigger.cityLost("London") would look up who owns London and attach itself to the onCityAcquired event trigger list.
I see your point and such a setup would be optimal. Right now I don't see how I could manage that, though. In my world, the Event Manager runs the show and I would just connect a module to one of the events, then trigger the scenario events from this module each turn. But I realize this would be very limiting. Where would you connect a hypothetical Scenario.py - to several events at once?

I tried to follow your example from a previous post about "conditions" and whatnot, but it was all a bit overwhelming. :p Don't get me wrong, I will want to move in this direction eventually. Right now I'm busy studying the book and I might have the time to make some prototypes this coming weekend. Lets just take it from there. :D

I've reached chapter 17 (Linked Lists) and am gonna take a breather while I revisit chapters 15 (Sets of Objects) and 16 (Inheritance). I actually copied the example program (Old Maid Hand Card Game) into one single script so that I have a chance to follow along the text. This is actually exactly the stuff I need to learn!

Browsing forward in the book I'm kinda getting the feeling its moving into trivia territory, but maybe I'm wrong? Do I need to concern myself with stuff like Stacks, Queues and Trees at this point? I haven't read those chapters yet so I'm not even sure what those subjects are concerning...

One thing I realized today (I do most of my contemplating during work hours) is that using the CivIV Python API is utilizing Object-Oriented Programming. :D I've already used the term "instance" to describe one of the classes (like CyPlayer) but I now also realize that the actual player instance really is an "object". And that the functions listed in the API really are "methods". Eureka, then! :goodjob:

This stuff is really starting to make sense, but I'm not quite there yet.
 
Aha, you mean that the Scenario module creates some sort of index of all the triggers present, and assigns each to one of the events? And every time one of these events trigger the Scenario module the script will search the index to see if there are relevant scenario events present. Thats actually pretty obvious, when you think about it! :D

Spontaneously I'm thinking that 1. the init definition for Trigger() will take "event" as an argument, or that 2. the beginGameTurn event will be set to default inside Trigger() instance. Any added methods would then change the default setting. Well, something like that.
 
In my world, the Event Manager runs the show and I would just connect a module to one of the events, then trigger the scenario events from this module each turn.

The event manager has many event types: begin/end player turn, begin/end game turn, city acquired, unit killed, player changed state religion, etc. You can have each event check all triggers, but with a little effort you can make that run faster. Turn times are already slow, and checking a list of triggers every time anything happens in the game will slow it down more.

But it's best to start simple and build from there. So have one list of triggers and check it for each interesting event type. You can make it smarter later.

I tried to follow your example from a previous post about "conditions" and whatnot, but it was all a bit overwhelming.

Objects relate to each other. For example, Civ's CyCity has an "owner" attribute that is a CyPlayer. And a CyTeam contains one or more CyPlayers. Conditions are the checks that must be True for the trigger to fire. "The player is English", "it's between turn 100 and 105," "Germany is still alive," etc. Each Trigger holds a list of one or more Conditions that it checks to see if it should fire yet or not.

Actions are the things that the trigger does when it fires. "Spawn a city called Duseldorf at plot 23,82," "spawn 3 archers in London," "give 500 gold to Spain," etc. Each Trigger has a list of one or more Actions that it performs when it fires.

So Trigger can be said to be composed of a list of Conditions and a list of Actions. In other words, a Trigger is defined by its Conditions and Actions; they are inseparable. Just as a MotorVehicle has an Engine and a list of Wheels.

Do I need to concern myself with stuff like Stacks, Queues and Trees at this point?

Eventually, yes. For now you can skip them or just skim them (five minutes each chapter) to get a feel for what they will cover. Then in the future when you need one, maybe you'll remember what you skimmed and can look it up. They are just different ways of storing data.

Stacks and queues are extremely common in computer science at the system level. For your mod you probably won't need them. You'll definitely be using lists ([] in Python).

One thing I realized today (I do most of my contemplating during work hours) is that using the CivIV Python API is utilizing Object-Oriented Programming. :D

Absolutely! The whole list on the left is a list of classes, and when you click on a class you see its methods on the right. :goodjob:

Aha, you mean that the Scenario module creates some sort of index of all the triggers present, and assigns each to one of the events? . . . 1. the init definition for Trigger() will take "event" as an argument, or that 2. the beginGameTurn event will be set to default inside Trigger() instance. Any added methods would then change the default setting. Well, something like that.

That's a good start. I was thinking maybe the functions could infer that information, but I think it's going to require the modder to explicitly say when to check the trigger.
 
Back
Top Bottom