configparser and default sections

Continuing my series on the configparser, this tutorial looks at the default section in INI sytle files. We take a slight departure from our previous example of saving/loading  player data to explore how default sections can be used to create an in-game dialogue systems. We will build on the last tutorial by continuing to look at how data retrieved from the config parser can be manipulated.

The [DEFAULT] section

In a nutshell, options declared in the default section are propagated to other sections. Or to put it another way, options in a default section are available to all other sections. For example, if our INI style file looked like this:

[DEFAULT]
zPos: 0

[cube1]
xPos: 1
yPos: 2

[cube2]
xPos: 4
yPos: 8

[cube3]
xPos: 7
yPos: -5
zPos: 2

We could do something like this in python:

#assuming we've loaded our file and all that jazz
zPos = config.getint('cube1', 'zPos')

Here, zPos will equal 0 even though the section cube1 does not have an option called zPos. Default values can be overridden. So if we were to access cube3 we’d get a value of 2.

Avoiding repetition

This is a really useful feature when you’ve got a config file that contains sections that will have the same options. Like in the above example, each cube has a zPos and there’s no need to specify zPos for each cube, if more often than not, it’s going to be the same.

However, in the example from the previous tutorial, the addition of a default section would create problems. Look back at the section of code that controls the position of the smaller cubes. Can you see what might happen?

Because the python code in the previous tutorial works by looping over the cubes section, reading each of the options and converting the value into a list of 3 numbers it is expecting the option’s value to follow a particular format. The addition of a default section would mean that any options contained there would also be included in the cubes section (and therefore looped over by the python code), but would not follow the format that the python code expects. So when we write:

[DEFAULT]
zPos: 0

[player]
xpPos: -1.5
yPos: -1.5
score: 10

[cubes]
smallCube1: 0.0, 3.0, 0
smallCube2: 6.6, 7.0, 0
smallCube3: -8.3, 2.2, 0
smallCube4:
smallCube5: 7.4, -5.5, 0

What the configparser sees is:

[player]
xpPos: -1.5
yPos: -1.5
score: 10
zPos: 0

[cubes]
smallCube1: 0.0, 3.0, 0
smallCube2: 6.6, 7.0, 0
smallCube3: -8.3, 2.2, 0
smallCube4:
smallCube5: 7.4, -5.5, 0
zPos: 0

Which will generate a python error somewhere along the lines. So while default sections can be used to avoid repetition be mindful that options in them will apply to all sections and therefore need to be relevant to all sections in the file.

And that’s pretty much all there is to it.

Talking cubes

I racked my brain trying to think of a good use of the default section in the BGE. In the end I came up with the idea of using a config file to contain NPC speech in a ‘game’. Here the default section could be used for stock responses (hello, goodbye, etc.), while each character could have their unique responses as well. This also takes advantage of the main advantage of INI style files: ease of human editing – perfect for writing dialogue. Finally, this example builds on previous ones to create something more robust to handle missing sections, missing files and erroneous user input.

I’m not going to labour the setup of this .blend (see files below), have a play with it yourself. Use WASD to move around, press spacebar near a cube to talk to it and a number key to access a speech option. Before moving on to have a look at the code, have a play with the dialogue.cfg file. Try adding new responses and speech options. Notice how easy it is. Also, you’ll see that, unlike previous examples, it handles any option-value pair you add.

Breaking it down

First, lets look at the .cfg file:

[DEFAULT]
greet: Hello
mood: I'm fine, thank you
end: Goodbye

[cube1]
age: I'm 500 years old

[cube2]
greet: Greetings fellow traveller

[cube3]

[cube4]
greet: Go away!
mood: Get lost!
end: Good riddance!

Here, the default section is used to add the neutral responses for the cubes, then cubes with specific responses or responses that differ from the neutral ones can define their own.

There’s a lot more going on in the python code than the previous tutorials, so I’ll give an overview of what’s going on and look more closely at the configparser code related. First, we’re doing the usual BGE stuff (importing modules, getting game objects and the like). Next, comes the parseText() function. This takes a string and breaks it up to a specified line length without breaking words up. This means we can ensure the text remains on screen. Then comes the loadDialogue() function:

def loadDialogue(ident, file='dialogue.cfg'):        
    config = configparser.ConfigParser()
    config.optionxform = str 
    try:
        config.read_file(open(file))
    except IOError:
        print("file", file, "could not be found.")        
        return None        
    #generate text and dialogue options
    dialogue = []
    playerOptions = ""
    greeting = ""
    i = 1
    if config.has_section(ident):
        for option in config.options(ident):
            if option == 'greet':
                greeting = config.get(ident, option)
                continue
            dialogue.append([option, config.get(ident, option)])
            if option != 'end' and option != 'greet':
                playerOptions += str(i) + ") Ask about " + option + """\n"""    
                i+=1
    else:
        return None
    playerOptions += str(i) + ") Goodbye" 
    d = [None, "I don't know what you're saying"]
    dialogue += [d]*(9-len(dialogue))    
    return greeting, dialogue, playerOptions

It takes the identity of the cube we are talking to, this corresponds to the sections in the dialogue.cfg file. The first few lines should look familiar. We return None if the file is not found that way later down the line the game can handle missing dialogue accordingly. As per usual, we check that a section exists (line 41) before proceeding and this time we use to return from the function so the game can handle it (lines 53-4). Notice there is one cube that you can’t talk to.

The code then loops through the dialogue options for the non-player character (NPC) and separates out the various things we need. First, it finds the greeting and sets that aside for us. It then gathers all the things a player can ask and the NPC’s responses. This takes the format of a nested list: [[topic, reponse], [topic, response]…]. Since there are 9 number keys the list needs to contain 9 responses. So on lines 53-4 we fill the remainder of the list with stock responses. Finally, we build the options text based on what the player can ask. All going well, it should return a tuple in the following format: (String, List, String).

The rest of the code makes use of the returned data. First, we check that the dialogue as been loaded, and if not free the player out of conversation mode and display a default ‘go away’ response. If everything’s where it should be we display the greeting and make sure that it doesn’t get displayed again. The then display the player’s options across the bottom of the screen. Finally, the list of responses is used to marry them up to key presses and update the NPC’s speech accordingly. Each time a key is pressed we are checking that the topic isn’t ‘end’, and if it is we close the conversation and free the player to move around again.

Final remarks

That was quiet a long-winded look at default sections, but hopefully, the talking cubes example demonstrates that between default sections and error handling you can use the configparser to effectively handle a variety of situations. If you have any questions or comments leave them below. Next time, we’ll move to saving data with the configparser.

Tutorial Resources

Here’s the talking cubes.blend file
Here’s the dialogue.cfg file

You can find the previous tutorials here:
Introduction to the configparser
Using data retrieved with the configparser

Advertisements

~ by Jay on April 27, 2014.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

 
%d bloggers like this: