I have recently started little Python project -- text-based adventure. Genre old as computers, this type of games is very easy to develop, and process is a rewarding learning and creative experience.
Modern languages allow programming process to be close to pain-free. This does not mean you can be lax in design and planning of the world, and underlying code. It does allow you to gradually populate your universe with objects, new ways players can interact with them, create quests and puzzles.
Here I would like to show first few steps of the process, how object-less void get filled with new elements.
The game design is Escape Room. Player's character finds themselves in a laboratory with a closed door. To win the game, they'll have to interact with objects, find way to open the door, and exit the lab. Each attempt to interact with the world is counted as a "turn". Player interact with the world by typing commands in english language. Game runs in Terminal.
We start with simple implementation, where player can either
exit the lab for the win:
def parseInput(s): if s == "look": return True, "You are in the middle of laboratory. There is a lot of equipment and door leading outside" elif s =="exit the lab": return False, "Congratulations, you've escaped the lab!" else: return True, "Sorry, I don't know what you mean." game = True turns = 0 while game: inp = raw_input(">") # here we ask user to type something turns += 1 game, result = parseInput(inp) print ("%d$\n%s" % (turns, result)) # this is called String Formatting print "Game over."
Now we can start adding some objects to the room. Let's say the key is sitting in the desk drawer.
Let's start adding some objects, in an object-oriented fashion. The
Object is a class that will envelope everything in this small world, including character themselves. For starters we want to be able to name objects, store stuff inside objects, and be able to see what's inside the objects.
class Object(): def __init__(self,description): self.contents =  self.description = description def add_item(self,item): self.contents.append(item) return True def remove_item(self,item): if item in self.contents: self.contents.remove(item) return True else: return False def inspect(self): return "\n".join(map(lambda x: x.description, self.contents))
At this stage we already learning about small magic of modern languages, namely
map: it allows us to construct list of items inside given object, by turning list of objects into list of their descriptions. We also used anonymous (lambda) function here, which is extremely useful practical tool.
# if items are: [red hat, blue apron, green sunglasses] # then map(lambda x: x.color, items) # equals to [red, blue, green]
Let's see what having
Object class allows us to do:
from Object import Object room = Object('Big laboratory room with one door') desk = Object('Desk with one drawer') drawer = Object('Drawer with some stuff inside') desk.add_item(drawer) key = Object('Key to the door') papers = Object('Bunch of useless papers') drawer.add_item(key) drawer.add_item(papers) player = Object('You, trying to escape this weird place') room.add_item(desk) room.add_item(player)
This code, which looks completely human-readable to me, creates room, creates few items in it, and puts them in correct containers. Now, if you want to see what's inside the room, you can simply call
room.inspect() and get list of items' description in the room:
if s == "look": room_description = "You are in the middle of laboratory. There is a lot of equipment and door leading outside.\nIn this room:\n" room_description += room.inspect() return True, room_description # >look # 1$ # You are in the middle of laboratory. There is a lot of equipment and door leading # outside. # In this room: # Desk with one drawer # You, trying to escape this weird place
Let's finish up our escape room by fortifying the door. It shouldn't be so easy to exit, so we will create new object and hook it to the world:
door = Door('Door leading to freedom', key) room.add_item(door) [...skipped...] elif s =="exit the lab": if door.opened: return False, "Congratulations, you've escaped the lab!" else: return True, "Door is closed"
Now, what the heck is this
Door object? Well, that's an opportunity to learn about inheritance in Python. The object look like that:
from Object import Object class Door(Object): def __init__(self,description,key): # Door is just another Object, so we initialize it as such Object.__init__(self,description) # but also Door has unique properties, such as key that unlocks this particular door self.key = key self.unlocked = False self.opened = False def open(self): if self.unlocked: if not self.opened: self.opened = True return "Door is now open" else: return "Door is already open" else: return "Door is locked" def unlock(self, who): if self.key not in who.contents: return False else: self.unlocked = True return True
Now, I will go have breakfast and stop this post on an observation. In this code, when someone is trying to unlock the door, the
Door itself checks whether that person has the right key. Is that how it supposed to be? In practice it will look like:
Or maybe the player should be checking whether they have the key?
There is a third option, adding some sort of
Interaction object which will check all necessary requirements for the action, such that we will have to write
This is an important general programming question: how to separate responsibility of the objects, and why one option is better than the other. I will try to explore this issue in Part Two, meanwhile here is GitHub link to current game described here
Short list of topics mentioned here:
- reading user's input
- creating class
Mapand anonymous functions
- class inheritance
- separation of responsibility