DEV Community

andrey a
andrey a

Posted on

Learning language by creating world of text-based adventure

logo

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 look or 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."
Enter fullscreen mode Exit fullscreen mode

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))
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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:

door.unlock(player)
Enter fullscreen mode Exit fullscreen mode

Or maybe the player should be checking whether they have the key?

player.unlock(door)
Enter fullscreen mode Exit fullscreen mode

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

world.unlock(what, who)
Enter fullscreen mode Exit fullscreen mode

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
  • Map and anonymous functions
  • class inheritance
  • separation of responsibility

Top comments (0)