UEFI News and Commentary

Wednesday, July 25, 2012

Creating an Interactive Fiction Game with UEFI (Part 1)

Recently, I've been putting together a text-based game using Main as a starting point.  The game is an interactive fiction game, similar to Zork.  Essentially, every room and object is described, and the player simply types in commands such as "take book," "north," "unlock door" and so on.  Imagine it as a more flexible version of a choose your own adventure.  There are plenty of examples at ifdb.tads.org.  I am particularly fond of this type of game because it's like a mix of books and video games.  The creators have to put a lot of thought into what the player would be thinking of doing, and a lot of times, their descriptions of the world are so complete that they might as well be writing a novel.  Also, depending on how the creator decides to make the game, it can lean more towards the puzzle side or the story side.  It could be a one-room game, where your goal is simply to unlock the door, or it could be a fantasy story where you are some knight hunting a dragon.  It's an extremely flexible medium.

In the game I'm writing, you play as a little girl who is living in her uncle's house for the summer.  Her uncle has gone out for the day and locked her inside the house, but she wants to go outside and play.  So, while her uncle is away, she runs around the house trying to find a way outside.

Since UEFI contains the entire C lib, I am writing this game in C.  Unfortunately, I'm used to writing in object-oriented languages, so getting used to using structs was a challenge.  In this style of game, I used four different structs to create the basic world: Character, Object, Room, and Exit.  So in the following sections, I'll describe each struct and its purpose.

A Character is, in essence, the player.  It keeps track of what items are being held in the player's inventory and what room the player is currently in.  Let's walk through each element here.  First is a pointer to a Room.  That's the room that the player is currently "in."  It limits the Objects that the player can interact with to those in the Room, and also limits the Rooms a player can go to.  We will talk more about the Room and its limitations later.  Next is the description.  This is an optional part, but I like to put it here anyways.  It's a description of your character.  So if the player decides to enter "examine self" as a command, there is a description to print out, rather than having to say, "You don't see that here."  After that is an int value that stores the number of objects currently in the player's inventory.  It serves several purposes.  Not only does it provide an easy way to see if your character is carrying too much to pick up another object, it also makes it easier to insert a new object into the inventory, by simply using the number of objects as the index of the next object.  Finally is the actual array of Objects that is the inventory.  These objects move around with the Character so that the Character can interact with them and use them no matter which room they're in.

An Object is something the player can interact with, whether it be simply examining it, picking it up, or opening it.  They all have descriptions and BOOLEAN values for whether or not they can be picked up.  Now, the objects are a little strange.  I have two names for objects, name and checkName. The purpose of checkName is to compare the noun that was entered by the user to the object.  The way the scanner works, it concatenates all parts of the noun together, so if someone were to type in "examine candle holders," the noun that would be passed into the function would be "candleholders."  So the checkName is there to determine which Object the user was referring to.  name, on the other hand, is only there for movable objects, for listing purposes in the inventory.  So when the player decides to check what Objects they are carrying at the moment, it doesn't print "candleholders", but it will instead say "some candle holders."  The description is a more detailed description of the object.  It allows the player to "examine" things and get a better idea of the world the game takes place in.  After that come two booleans, movable and hidden.  Movable determines whether or not an object is static and can be picked up.  Sometimes, it is immovable simply because it would be impractical to pick up an object, like a bed.  Other times, you have an object that provides an additional detail to the world, but it is not needed to complete the game, like a doll.  Hidden determines whether an object is listed in the room description.  Any movable, not hidden objects will be listed at the end of the room description, like "You see a key here.  You see a broom here." If the object is hidden, it will not be listed.  For example, I may have a blanket on the bed, but I don't want to list that because it's unnecessary.  So I mark the object as hidden and it will not be printed.  Finally is a string used when you are unable to pick something up.  If you try and pick up an immovable object, this string will be printed out instead.  It could say something like, "That's too heavy to carry," or "You don't think you will need that."

A Room is a "location" that limits where your character can go and what objects it can interact with.  It contains a description, an array of Objects it contains, and an array of Exits.  First in the struct is the name.  Whenever a player enters a new room, the name is displayed.  It makes it easier on the player when they are making a map of the world which they explore.  Secondly is the description.  This is similar to the object description in that it provides a more detailed view of what the room looks like.  It is also important here to list major static objects (not ones that can be picked up, because those can leave the room) and other things that the player can interact with.  Also, be sure to list in what directions the exits are located.  Next is the number of objects located in the room.  This serves the same purpose as the corresponding value in the Character struct.  Same with the inventory.  Finally comes the array of Exits.  Depending on how your map is set up, you can have your basic four exits (north, south, east, west), or perhaps even ten (all eight compass points plus up and down).  It all depends on how you create your world.   These will keep track of which directions you can go from your current room.  Each position in the array corresponds to a direction (south, northeast, etc).  All positions in the array will always be filled, whether or not the player can go that direction.  I'll discuss Exits further in the next section. 

Exits keep track of which Rooms a Character can go to from a given Room, and whether or not the "door" is locked.  First in the struct is a boolean value for whether or not the exit is locked.  This will prevent the player from going through this Exit to the next Room.  The next value is a pointer to the Room this Exit leads to.  Once the player is able to go through this exit, they will be in that Room.  Sometimes, though, the exit doesn't lead to a room, because there is no "door" there.  In that case, this will be a NULL pointer.  Next is a string that is printed out when a player cannot go through this exit.  Whether it is locked or there is no Room to to to in that direction, this message will be printed.  It could be something like, "The door seems to be locked," or, "There's a wall in that direction."  Finally is the key.  If the door is locked and you want it to be able to be unlocked using an Object (whether it be an actual key, a pry bar, a duck, whatever), this will keep track of what can be used to unlock the door.  

So those are the basic building blocks of an interactive fiction world.  By putting all these together, you can make a nice little map that your player can explore and interact with.

Next time, I will discuss the game loop itself and its components

No comments: