Python (civ4 scripting language) tutorial thread

Blackbird_SR-71 said:
We need a moderator to sticky this thread. and where is everyone?

If there is enough interest, it'll remain up higher by people posting. Once the Civ4 forums come online, we'll probably have Python/programming tutorial threads.


To comment on the XML section --

If you have ever looked at HTML, or looked at datasets in the .NET programming language, then you know XML! Well, sort of. XML is like HTML in a database form. For example (a simplified version):

Code:
<forums>
	<civfanatics>
		<users>
			<name>Thunderfall</name>
			<posts>7000</posts>
			<status>Administrator</status>
		</users>
		<users>
			<name>Padma</name>
			<posts>7000</posts>
			<status>Moderator</status>
		<users>
	<civfanatics>
</forms>

There's a table called "Civfanatics" that has a list of users, with the columns "name", "posts", "status". The table would look something like this:

Code:
Name		Post	Status
Thunderfall	7000	Administrator
Padma		7000	Moderator
 
XML has no boundaries. It has no tags. You make them up. Also XML is much more stricter. You can't do this anymore in XML unlike HTML:

Code:
[B][I]Wrong[/B][/I]

This is the correct way in XML:

Code:
[B][I]Correct[/I][/B]

So basically you only have to make the text format correctly but other than that XML doesn't have tags but you have to instead define them just like Chieftess did.

Think of HTML and XML like this:

HTML displays information.
XML describes information.

EDIT: Darn can't show the HTML and XML code. how do show them without making them actually code.
 
Code:
<b>test</b>
<i>test</i>
<u>test</u>
Code:
<xml> 
<test>123</test>
<test2/>
</xml>

Seems to work here.

If you're trying to do something like code=html, or code=xml, this forum doesn't have that feature.
 
I used the code tags, and I used notepad to type it manually. ;)
 
Well, you guys have pretty much figured out the basics of XML on your own. I'll write an official lesson once I have more details on what we need to know. I've been trying to get the attention of the powers that be to tell me this. If you come across one of those mysterious and alusive characters from Firaxis, tell them to drop me a word ;).

Expect some of the final lessons in the python series in the next couple of weeks. Next lesson is loading and making modules, so you can import code from other files. After that is file I/O, especially the pickle() function, which lets you save instances of a class. After that is exception and error handling, which just about finishes everything you might need to know.

Beyond that, is XML, then getting python to work with XML. If there is a gap between those, we might do some work making IRC scripts and/or bots. In a month or so I might be able to show you the beginnings of the multiplayer text adventure game based on this.
 
Lesson 09 - 'Borrowing' other people's stuff (with modules).

Introduction

Last lesson we covered the killer topic of Classes. As you can remember, classes are neat combinations of variables and functions in a nice, neat package. Programming lingo calls this feature encapsulation, but reguardless of what it is called, it's a really cool feature for keeping things together so the code can be used in many instances in lots of places. Of course, you've got to ask, "how do I get my classes to many places?". The answer is to put them into a module, to be imported into other programs.

Module? What's a Module?

A module is a python file that (generally) has only defenitions of variables, functions, and classes. For example, a module might look like this:
Code:
### moduletest.py
### EXAMPLE PYTHON MODULE
# Define some variables:
numberone = 1
ageofqueen = 78

# define some functions
def printhello():
    print "hello"
    
def timesfour(input):
    print input * 4
    
# define a class
class Piano:
    def __init__(self):
        self.type = raw_input("What type of piano? ")
	self.height = raw_input("What height (in feet)? ")
	self.price = raw_input("How much did it cost? ")
	self.age = raw_input("How old is it (in years)? ")
	
    def printdetails(self):
        print "This piano is a/an " + self.height + " foot",
	print self.type, "piano, " + self.age, "years old and costing " + self.price + " dollars."
Save the above as moduletest.py

So what do we do with a module? We import bits of it (or all of it) into other programs.

To import all the variables, functions and classes from moduletest.py into another program you are writing, we use the import operator. For example, to import moduletest.py into your main program, you would have this:
Code:
### mainprogam.py
### IMPORTS ANOTHER MODULE
import moduletest
This assumes that the module is in the same directory as mainprogram.py, or is a default module that comes with python. You leave out the '.py' at the end of the file - it is ignored. You normally put all import statements at the beginning of the python file, but technically it is not required. In order to use the items in the module in your main program, you use the following:
Code:
### mainprogram.py continued
### USING AN IMPORTED MODULE

# Use the form modulename.itemname
# Examples:
print moduletest.ageofqueen

cfcpiano = moduletest.Piano()

cfcpiano.printdetails()

More module thingummyjigs (in lack of a better title)

Ever wish you could get rid of the modulename. part that you have to put before every item you use from a module? No? Never? Well, I'll teach it you anyway.

To do this, you use the from operator. You use it in the form of from modulename import itemname. Here is an example:
Code:
### directimport.py
### IMPORT ITEMS DIRECTLY INTO YOUR PROGRAM

# import them
from moduletest import ageofqueen
from moduletest import printhello

# now try using them
print ageofqueen
printhello()
What is the point of this? Well, maybe you could use it to make your code a little more readable. If we get into heaps of modules inside modules, it could remove that extra layer of crypticness.

If you wanted to, you could import everything from a module is this way by putting from modulename import *. Of course, this can be troublesome if there are objects in your program with the same name as some items in the module. With large modules, this can easily happen, and can cause many a headache. A better way to do this would be to import a module in the normal way (without the from operator) and then assign items to a local name:
Code:
### mainprogram.py continued
### ASSIGNING ITEMS TO A LOCAL NAME

# Assigning to a local name
timesfour = moduletest.timesfour

# Using the local name
print timesfour(565)
This way, you can remove some crypticness, AND have all of the items from a certain module.

Conclusion

That's it! A very simple lesson, but now you can organise your programs very neatly. In fact, now it is increadibly easy to make progams that can grow in complexity without ending up with one cryptic file that is full of bugs.

Modules are great for importing code. Next lesson, we learn about file input and output, and the saving of information inside classes, to be retrieved later. Will be great! But until then...

Thanks to all,
Gingerbread Man
 
Well, there is lesson 9. Lesson 10 will be up within 48 hours, and lesson 11, probably the last straight-out python lesson needed, in 96 hours or less. (If you haven't guessed, I'm on a bit of a writing binge.)

I'd post this (VERY brief) lesson on the website now, but it is past midnight. Good morning CFC!
 
Lesson 10 - File I/O

Introduction

Last lesson we learnt how to load external code into our program. Without any introduction (like what I usually have), let's delve into file input and output with normal text files, and later the saving and restoring of instances of classes. (Say what??? All will be clear later...)

Opening a File

To open a text file you use, well, the open() function. Seems sensible. You pass certain parameters to open() to tell it in which way the file should be opened - 'r' for read only, 'w' for writing only (if there is an old file, it will be written over), 'a' for appending (adding things on to the end of the file) and 'r+' for both reading and writing. But less talk, lets open a file for reading (you can do this in your python idle mode - remember that you dont have to type in the '>>>' bit). We will then print out what we read inside the file:
Code:
### openfile.py
### opens a file

# Think of a normal text file to open. Remember the path to that file.
>>> openfile = open('pathtofile', 'r')
>>> openfile.read()

# you will see the file, completely unformatted, here.

That was interesting. You'll notice a lot of '\n' symbols. These represent newlines (where you pressed enter to start a new line). The text is completely unformatted, but if you were to pass the output of openfile.read() to print (by typing >>> print openfile.read()) it would be nicely formatted.

Seek and You Shall Find

Did you try typing in '>>> print openfile.read()'? Did it fail? It likely did, and reason is because the 'cursor' has changed it's place. Cursor? What cursor? Well, a cursor that you really cannot see, but still a cursor. This invisible cursor tells the read function (and many other I/O functions) where to start from. To set where the cursor is, you use the seek() function. Here is the form for seek:
Code:
seek([u]offset[/u], [u]whence[/u])
Whence is optional, and determines where to seek from. If whence is 0, the bytes/letters are counted from the beginning. If it is 1, the bytes are counted from the current cursor position. If it is 2, then the bytes are counted from the end of the file. If nothing is put there, 0 is assumed.
offset decribes how far from whence that the cursor moves. for example:
  • openfile.seek(45,0) would move the cursor to 45 bytes/letters after the beginning of the file.
  • openfile.seek(10,1) would move the cursor to 10 bytes/letters after the current cursor position.
  • openfile.seek(-77,2) would move the cursor to 77 bytes/letters before the end of the file (notice the - before the 77)
Try it out now. Use openfile.seek() to go to any spot in the file and then try typing 'print openfile.read()'. It will print from the spot you seeked to. But realise that openfile.read() moves the cursor to the end of the file - you will have to seek again.

Other I/0 functions

There are many other functions that help you with dealing with files. They have many uses that empower you to do more, and make the things you can do easier. Let's have a look at tell(), readline(), readlines(), write() and close().

tell() returns where the cursor is in the file. It has no parameters, just type it in (like what the example below will show). This is infinitely useful, for knowing what you are refering to, where it is, and simple control of the cursor. To use it, type fileobjectname.tell() - where fileobjectname is the name of the file object you created when you opened the file (in 'openfile = open('pathtofile', 'r')' the file object name is openfile).

readline() reads from where the cursor is till the end of the line. Remember that the end of the line isn't the edge of your screen - the line ends when you press enter to create a new line. This is useful for things like reading a log of events, or going through something progressively to process it. There are no parameters you have to pass to readline(), though you can optionally tell it the maximum number of bytes/letters to read by putting a number in the brackets. Use it with fileobjectname.readline().

readlines()] is much like readline(), however readlines() reads all the lines from the cursor onwards, and returns a list, with each list element holding a line of code. Use it with fileobjectname.readlines(). For example, if you had the text file:
Code:
Line 1

Line 3
Line 4

Line 6
then the returned list from readlines() would be:
Code:
List Element    Content
0               'Line 1'
1               ''
2               'Line 3'
3               'Line 4'
4               ''
5               'Line 6'
(List Element is just a fancy name for an item in a list.)

The write() function, writes to the file. How did you guess??? It writes from where the cursor is, and overwrites text in front of it - like in MS Word, where you press 'insert' and it writes over the top of old text. Put a string between the brackets e.g. fileobjectname.write('this is a string').

close, you may figure, closes the file so that you can no longer read or write to it until you reopen in again. Simple enough. To use, you would write 'fileobjectname.close()'. Simple!

In Python idle mode, open up a test file (or create a new one...) and play around with these functions. You can do some simple (and very inconvenient) text editing.

Mmm, Pickles

Pickles, in Python, are to be eaten. Their flavour is just to good to let programmers leave them in the fridge.

Ok, just joking there. Pickles, in Python, are objects saved to a file. An object in this case could be a variables, instance of a class, or a list, dictionary, or tuple. Other things can also be pickled, but with limits. The object can then be restored, or unpickled, later on. In other words, you are 'saving' your objects.

So how do we pickle? With the dump() function, which is inside the pickle module - so at the beginning of your program you will have to write 'import pickle'. Simple enough? Then open an empty file, and use pickle.dump() to drop the object into that file. Let's try that:
Code:
### pickletest.py
### PICKLE AN OBJECT

# import the pickle module
import pickle

# lets create something to be pickled
# How about a list?
picklelist = ['one',2,'three','four',5,'can you count?']

# now create a file
# replace filename with the file you want to create
file = open('filename', 'w')

# now let's pickle picklelist
pickle.dump(picklelist,file)

# close the file, and your pickling is complete
file.close()
The form you use is 'pickle.load(object_to_pickle, file_object)' where:
  • object_to_pickle is the object you want to pickle (i.e. save it to file)
  • file_object is the file object you want to write to (in this case, the file object is 'file')
After you close the file, open it in notepad and look at what you see. Along with some other gibblygook, you will see bits of the list we created.

Now to re-open, or unpickle, your file. to use this, we would use pickle.load():
Code:
### unpickletest.py
### unpickle file

# import the pickle module
import pickle

# now open a file for reading
# replace filename with the path to the file you created in pickletest.py
unpicklefile = open('filename', 'r')

# now load the list that we pickled into a new object
unpickledlist = pickle.load(unpicklefile)

# close the file, just for safety
unpicklefile.close()

# Try out using the list
for item in unpickledlist:
    print item
Nifty, eh?

Of course, the limitation above is that we can only put in one object to a file. We could get around this by putting lots of picklable objects in a list or dictionary, and then pickling that list or dictionary. This is the quickest and easiest way, but you can do some pretty advanced stuff if you have some pretty andvanced knowledge of pickle.

Which we won't cover.

Conclusion

Which ends this lesson.

Thanks to all,
Gingerbread Man
 
Hey! Very excited about learning this!

I cant believe there isnt more publicity about this thread, considering how important it is. I will try and catch up to where you guys are on my own (using your AMAZING tutorials!) and then get into it from there.

THANKS AGAIN for helping us illiterates!
 
Thanks for the responses, the100thballoon and Aeon221! While I've generally laid back on any official classes, I like it when people respond to my lessons and let me know that people actually read them. Indeed, let me know your thoughts on my tutorials, so that they can get more accurate and easier to understand.

By the way, the last two lessons aren't on the website yet. You'll have to look at them on this thread. Otherwise, the website has updated and clarified lessons which are much better than my originals here.
 
Got through all the lessons! Very happy! This is the same thing they used to script events in Rise of Nations, so I feel familiar with it... kinda like a distant cousin twice removed who you met once before but didnt really like... even if he was really helpful, and got you that nice keg...

anyway, as if this wasnt obvious... :bump:

hit me! next lesson! come on, I'm feeling a blackjack! ;p
 
I did the first two lessons with no problem!
I know a LOT of HTML, but i'm not a proffessional. I did the first two lessons with no prob. :cool:
This seems like HTML, but totally more fun!
I tried to get into computer programming language before, but it was too boring. :(
But this is WAY cooler! :cool: :cool:
 
Lesson 11 - Exception Handling - dealing with the dumb

Introduction

If you haven't seen them before, you're not trying hard enough. What are they? Errors. Exceptions. Problems. Know what I'm talking about? I got it with this program:
Code:
def menu(list, question):
    for entry in list:
        print 1 + list.index(entry),
        print ") " + entry

    return input(question) - 1

answer = menu(['A','B','C','D','E','F','H','I'],'Which letter is your favourite? ')

print 'You picked answer ' + (answer + 1)
This is just an example of the menu program we made earlier. Appears perfectly fine to me. At least until when I first tried it. Run the program, and what happens?

Bugs - Human Errors

The most common problems with your code are of your own doing. Sad, but true. What do we see when we try to run our crippled program?
Code:
[color=red]Traceback (most recent call last):
  File "/home/steven/errortest.py", line 8, in -toplevel-
    answer = menu(['A','B','C','D','E','F','H','I'],'Which letter is your favourite? ')
  File "/home/steven/errortest.py", line 6, in menu
    return raw_input(question) - 1
TypeError: unsupported operand type(s) for -: 'str' and 'int'[/color]

Say what? What python is trying to tell you (but struggling to find a good word for it) is that you can't join a string of letters and a number into one string of text. Let's go through the error message and have a look at how it tells us that:
  • File "/home/steven/errortest.py", line 8, in -toplevel- tells us a couple of things. File "/home/steven/errortest.py" tells us which file the error occured in. This is useful if you use lots of modules that refer to each other. line 8, in -toplevel- tells us that it is in line # 8 of the file, and in the top level (that is, no indentation).
  • answer = menu(['A','B','C','D','E','F','H','I'],'Which letter is your favourite? ') duplicates the code where the error is.
  • Since this line calls a function, the next two lines describe where in the function the error occured.
  • TypeError: unsupported operand type(s) for -: 'str' and 'int' tells you the error. In this case, it is a 'TypeError', where you tried to subtract incompatible variables.
There are muliple file and code listings for a single error, because the error occured with the interaction of two lines of code (e.g. when using a function, the error occured on the line where the function was called, AND the line in the function where things went wrong).

Now that we know what the problem is, how do we fix it. Well, the error message has isolated where the problem is, so we'll only concentrate on that bit of code.
Code:
answer = menu(['A','B','C','D','E','F','H','I'],'Which letter is your favourite? ')
This is a call to a function. The error occured in the function in the following line
Code:
return raw_input(question) - 1
raw_input always returns a string, hence our problem. Let's change it to input(), which, when you type in a number, it returns a number:
Code:
return input(question) - 1
Bug fixed!

Exceptions - Limitations of the code

OK, the program works when you do something normal. But what if you try something weird? Type in a letter (lets say, 'm') instead of a number? Whoops!
Code:
[color=red]Traceback (most recent call last):
  File "/home/steven/errortest.py", line 8, in -toplevel-
    answer = menu(['A','B','C','D','E','F','H','I'],'Which letter is your favourite? ')
  File "/home/steven/errortest.py", line 6, in menu
    return input(question) - 1
  File "<string>", line 0, in -toplevel-
NameError: name 'g' is not defined[/color]
What is this telling us? There are two code listings - one in line 8, and the other in line 6. What this is telling us is that when we called the menu function in line 8, an error occured in line 6 (where we take away 1). This makes sense if you know what the input() function does - I did a bit of reading and testing, and realised that if you type in a letter or word, it will assume that you are mentioning a variable! so in line 6, we are trying to take 1 away from the variable 'm', which doesn't exist.

Have no clue on how to fix this? One of the best and easiest ways is to use the try and except operators.

Here is an example of try being used in a program:
Code:
try:
    world.dictLocales[world.characterLocation].localeObjects[input[1]].action[input[0]](world.dictObjects[input[1]],world)
except:
    print world.errormsg
[code]
This is an example of a really messy bit of code that I was trying to fix. First, the code under [i]try:[/i] is run. If there is an error, the compiler jumps to the [i]except[/i] section and prints [u]world.errormsg[/u]. The program doesn't stop right there and crash, it runs the code under [i]except:[/i] then continues on.

Lets try that where the error occured in our code (line 6). The menu function now is:
[code]def menu(list, question):
    for entry in list:
        print 1 + list.index(entry),
        print ") " + entry

    try:
        return input(question) - 1
    except:
        print "Enter a correct number"
Now try entering a letter when you're asked for a number and see what happens. Dang. We fixed one problem, but now it has caused another problem furthur down the track. This happens all the time (Sometimes you end up going around in circles, because your code is an absolute mess). Let's have a look at the error:
Code:
[color=red]Traceback (most recent call last):
  File "/home/steven/errortest.py", line 12, in -toplevel-
    print 'You picked answer', (answer + 1)
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'[/color]
What has happened this time is that the menu function has returned no value - it only printed an error message. When, at the end of the program, we try to print the returned value plus 1, what is the returned value? There is no returned value? So what is 1 + ... well, we have no clue what we are adding 1 to!

We could just return any old number, but that would be lying. What we really should to is rewrite the program to cope with this exception. With what? try and except!
Code:
# from when we finish defining the function
answer = menu(['A','B','C','D','E','F','H','I'],'Which letter is your favourite? ')
try:
    print 'You picked answer', (answer + 1)
    # you can put stuff after a comma in the 'print' statement,
    # and it will continue as if you had typed in 'print' again
except:
    print '\nincorrect answer.'
    # the '\n' is for formatting reasons. Try without it and see.
Problem solved again.

More Than One Error

What happens if there are multiple errors on the one section of code. Each needs to be dealt with in a different way. For example, have a look at this program:
Code:
print 'Subtraction program, v0.0.1 (beta)'
a = input('Enter a number to subtract from > ')
b = input('Enter the number to subtract > ')
print a - b
Ok, you enter your two numbers and it works. Enter a letter, and it gives you a 'NameError'. Lets rewrite the code to deal with a 'NameError' only. We'll put the program in a loop, so it restarts if an error occurs (using continue, which starts the loop from the top again, and break, which leaves the loop):
Code:
print 'Subtraction program, v0.0.2 (beta)'
loop = 1
while loop == 1:
    try:
        a = input('Enter a number to subtract from > ')
        b = input('Enter the number to subtract > ')
    except NameError:
        print "\nYou cannot subtract a letter"
	continue
    print a - b
    try:
        loop = input('Press 1 to try again > ')
    except NameError:
        loop = 0
Here, we restarted the loop if you typed in something wrong. In line 12 we assumed you wanted to quit the program if you didn't press 1, so we quit the program.

But there are still problems. If we leave something blank, or type in an unusual character like ! or ;, the program gives us a 'SyntaxError'. Lets deal with this. When we are asking for the numbers to subtract, we will give a different error message. When we ask to press 1, we will again assume the user wants to quit.
Code:
print 'Subtraction program, v0.0.3 (beta)'
loop = 1
while loop == 1:
    try:
        a = input('Enter a number to subtract from > ')
        b = input('Enter the number to subtract > ')
    except NameError:
        print "\nYou cannot subtract a letter"
	continue
    except SyntaxError:
        print "\nPlease enter a number only."
	continue
    print a - b
    try:
        loop = input('Press 1 to try again > ')
    except (NameError,SyntaxError):
        loop = 0
As you can see, you can have multiple excepts, each dealing with a different problem. You can also have one except to deal with multiple exceptions, by putting them inside parentheses and seperating them with commas.

Now we have a program that is very difficult, to crash by an end user. As a final challenge, see if you can crash it. There is one way I have thought of - if you read the chapter on Human Error carefully, you might know what it is.

Conclusion

There you go! The final lesson on python! Finally we are finished. That is, unless you want to also know XML. First we will deal with a quick lesson on HTML, then we will move on to XML - after that, who know's where?

Thanks to all,
Gingerbread Man
 
I'm redesigning the webite! Just for you guys, I have set up a preview of the new look-and-feel. I will also change the main page to accomodate more than just the python tutorial - the upcoming xml tutorial and some other pages will be added.

Please give me feedback on the new look, whether is is easier to read, and if there are any faults for it when running in your browser. It does not[/u] use frames - purely tables. I have tested (and confirmed the working order of) the web page in the following browsers:
Under Linux:
  • Mozilla Firefox v1.0 Preview
  • Links2 text mode (Yes, as in a old DOS-style web browser)
  • Links2 graphical mode
  • Lynx (hardcore text-mode browser, no frames, no tables!)
Under Windows:
  • Mozilla Firefox v1.0 Preview
  • Internet Explorer 6.0
I've also confirmed the site at resolutions down to 800x600 (640x480 is a maybe)
 
Top Bottom