Rooms

Many adventure games - but not all - have a concept of “rooms”. A player can explore rooms with some standard movement commands, perhaps finding interesting items that they can use or characters they can speak to. Note that despite the name, a room doesn’t have to be a room of a house. You could use rooms to describe any concept of location, in order to tell your story:

  • Drifting in space
  • On top of a hill
  • Underneath the floorboards
  • Nowhere

Adventurelib provides a helper object called Room, that can be used within your program. You don’t have to use this object in order to create the impression of rooms though. You can do it with creative use of @when functions.

Creating a room

Rooms are created by passing a description. Rich descriptions that convey a story to the user are very important to make your text adventure immersive, so try to write at least a couple of sentences.

from adventurelib import *

space = Room("""
You are drifting in space. It feels very cold.

A slate-blue spaceship sits completely silently to your left,
its airlock open and waiting.
""")

spaceship = Room("""
The bridge if the spaceship is shiny and white, with thousands
of small, red, blinking lights.
""")

Next you’ll want the ability to move between rooms. adventurelib doesn’t track what room the player is in; this is your responsibility!:

# current_room will be a global variable. Let's start out in
# space, so assign the 'space' room from above.
current_room = space


@when('enter airlock')
def enter_spaceship():
    # To set a global variable from within a function you have
    # to include the 'global' keyword, to avoid creating a
    # local variable instead.
    global current_room

    # Got to check if this action can be done here
    if current_room is not space:
        print('There is no airlock here.')
        return

    current_room = spaceship

    # You should include some narrative for every action to
    # ensure the transition doesn't feel abrupt.
    print(
        "You heave yourself into the airlock and slam your " +
        "hand on the button to close the outer door."
    )

    # Show the room description to indicate we have arrived.
    print(current_room)

Storing attributes on rooms

Part of the reason for rooms is to have different objects or contexts for the story. Some actions could only be possible in some rooms. You can assign arbitrary attribute names to an object in order to track the state of a room or what actions can be performed there. You can also set attributes on the Room object, which apply for all rooms:

Room.can_scream = True  # The default for all rooms
space.can_scream = False  # Set a value for a specific room.

@when('scream')
def scream():
    if current_room.can_scream:
        print(
            "You unleash a piercing shriek that " +
            "reverberates around you."
        )
    else:
        print(
            "You try to yell but there's no sound " +
            "in the vacuum of space."
        )

If you access an attribute that doesn’t exist on a room, an AttributeError will be raised, so ensure that you either set an attribute on every single room or set a default value on Room.

Directions and exits

Many text adventure games let players explore a system of rooms freely, using common commands such as north, south, east and west.

Room objects support these compass point directions by default. If you assign a room as the north attribute of another room, then you can traverse this relationship.

space.north = spaceship

Then one could access the room to the north of the current room using normal attribute access:

current_room.north

The key feature of the directions system is that these references are bi-directional. adventurelib knows that north is the opposite of south, so these relationships automatically hold:

>>> space.north is spaceship
True
>>> spaceship.south is space
True

Exits

Rooms have a couple of methods that allow you to query what exits they have.

These can be useful when writing commands that use the room layout (such as moving or looking in a direction).

room.exit(direction)

Get the Room that is linked in direction (eg. north). Returns None if there is no room in that direction.

room.exits()

Get a list of direction names where a direction is set.

Moving between rooms

To follow the links you’ve defined you could define separate north, south, east and west handlers - but the code would be mostly the same, and this is annoying to type and make changes to.

Instead, we can define one function and use several different @when lines to define the directions we will go. Each one will pass a direction in which to go.:

@when('north', direction='north')
@when('south', direction='south')
@when('east', direction='east')
@when('west', direction='west')
def go(direction):
    global current_room
    room = current_room.exit(direction)
    if room:
        current_room = room
        print(f'You go {direction}.')
        look()

Then in game:

> north
You go north.
There is a polar bear here.

> south
You go south.
It is a bright, sunny day.

These can be some of the most heavily used command, so you could also provide alias commands n, s, e and w as a convenience:

@when('north', direction='north')
@when('south', direction='south')
@when('east', direction='east')
@when('west', direction='west')
@when('n', direction='north')
@when('s', direction='south')
@when('e', direction='east')
@when('w', direction='west')
def go(direction):
    ...

Adding more directions

While north, south, east and west are built into adventurelib, you don’t have to use them. You can also register new directions, so long as you give an opposite. You would typically do this at the top of the file, before you define any rooms:

Room.add_direction('up', 'down')
Room.add_direction('enter', 'exit')

tent = Room(...)
camp = Room(...)
river = Room(...)
camp.enter = tent
camp.down = river