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). .. function:: room.exit(direction) Get the Room that is linked in direction (eg. ``north``). Returns ``None`` if there is no room in that direction. .. function:: 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