Editor

Text Adventure Game in Python

Intermediate · Games · about 90 minutes to build · 146 lines

You wake up in a locked house and type commands such as go north and take lamp to explore the rooms and find a way out.

The program

Press Run and answer its questions in the box that appears under the code.

text_adventure_game.py
"""Text adventure: explore an old house, find the right items and get out."""
START_ROOM = "hall"
SHORTCUTS = {"n": "north", "s": "south", "e": "east", "w": "west", "u": "up", "d": "down"}
# What the player reads when picking each item up.
ITEM_TEXT = {
"note": "It says: 'Spare key in the cellar. Take a light, the bulb is gone.'",
"lamp": "An oil lamp, still burning. It will light up a dark room.",
"key": "A heavy iron key with a tag: FRONT DOOR.",
}
HELP = """Commands:
go <direction> (or n, s, e, w, up, down)
take <item> inventory (or i) look help quit"""
def new_world():
"""Return a fresh map of the rooms, so every game starts the same way."""
return {
"hall": {
"description": "a dusty hall. The front door is to the south.",
"exits": {"north": "library", "east": "kitchen", "south": "outside"},
"locked": {"south": "key"}, # this exit needs the key
"items": [],
},
"library": {
"description": "a library with tall shelves of old books.",
"exits": {"south": "hall"},
"items": ["note"],
},
"kitchen": {
"description": "a cold kitchen. Stone steps lead down.",
"exits": {"west": "hall", "down": "cellar"},
"items": ["lamp"],
},
"cellar": {
"description": "a damp cellar full of empty jars.",
"exits": {"up": "kitchen"},
"items": ["key"],
"dark": True,
},
"outside": {
"description": "the garden, under an open sky.",
"exits": {},
"items": [],
},
}
def can_see(room, inventory):
"""A dark room can only be seen with the lamp."""
return not room.get("dark") or "lamp" in inventory
def describe(room, inventory):
"""Print where the player is, what is here and the ways out."""
if can_see(room, inventory):
print(f"You are in {room['description']}")
if room["items"]:
print(f"You see: {', '.join(room['items'])}")
else:
print("It is too dark to see anything here.")
if room["exits"]:
print(f"Exits: {', '.join(room['exits'])}")
def go(world, current, direction, inventory):
"""Return the room the player ends up in after trying to move."""
room = world[current]
direction = SHORTCUTS.get(direction, direction)
if not direction:
print("Go where? For example: go north")
return current
if direction not in room["exits"]:
print("You can't go that way.")
return current
needed = room.get("locked", {}).get(direction)
if needed and needed not in inventory:
print("The door is locked.")
return current
if needed:
print(f"You unlock the door with the {needed}.")
describe(world[room["exits"][direction]], inventory)
return room["exits"][direction]
def take(room, item, inventory):
"""Move an item from the room into the inventory, if the player can see it."""
if not item:
print("Take what? For example: take lamp")
elif not can_see(room, inventory):
print("You feel around in the dark but find nothing.")
elif item in room["items"]:
room["items"].remove(item)
inventory.append(item)
print(f"You take the {item}. {ITEM_TEXT[item]}")
else:
print(f"There is no {item} here.")
def play_game():
"""Play until the player gets outside or quits; return the moves taken."""
world = new_world()
current = START_ROOM
inventory = []
moves = 0
describe(world[current], inventory)
while current != "outside":
# "take the lamp" and "take lamp" mean the same, so drop "the".
words = [w for w in input("\n> ").lower().split() if w != "the"]
if not words:
continue
verb, noun = words[0], " ".join(words[1:])
moves += 1
if verb in SHORTCUTS or verb in SHORTCUTS.values():
current = go(world, current, verb, inventory)
elif verb == "go":
current = go(world, current, noun, inventory)
elif verb == "take":
take(world[current], noun, inventory)
elif verb in ("inventory", "i"):
print(f"You carry: {', '.join(inventory) or 'nothing'}")
elif verb == "look":
describe(world[current], inventory)
elif verb == "help":
print(HELP)
elif verb == "quit":
return None
else:
print("I don't understand that. Type help for the commands.")
return moves
def main():
"""Run the adventure and report how many moves it took."""
print("=== The Locked House ===")
print("You wake up inside an old house. Find a way out. Type help for the commands.\n")
moves = play_game()
if moves is None:
print("You give up and sit down. Maybe next time.")
else:
print(f"\nYou escaped in {moves} moves. Well done.")
if __name__ == "__main__":
main()

Open it in the full editor to save it, share it and keep building.

Sample run

What the program printed, and what was typed (in yellow).

=== The Locked House ===
You wake up inside an old house. Find a way out. Type help for the commands.

You are in a dusty hall. The front door is to the south.
Exits: north, east, south

> go south
The door is locked.

> n
You are in a library with tall shelves of old books.
You see: note
Exits: south

> take note
You take the note. It says: 'Spare key in the cellar. Take a light, the bulb is gone.'

> s
You are in a dusty hall. The front door is to the south.
Exits: north, east, south

> e
You are in a cold kitchen. Stone steps lead down.
You see: lamp
Exits: west, down

> down
It is too dark to see anything here.
Exits: up

> take key
You feel around in the dark but find nothing.

> up
You are in a cold kitchen. Stone steps lead down.
You see: lamp
Exits: west, down

> take lamp
You take the lamp. An oil lamp, still burning. It will light up a dark room.

> dance
I don't understand that. Type help for the commands.

> d
You are in a damp cellar full of empty jars.
You see: key
Exits: up

> take key
You take the key. A heavy iron key with a tag: FRONT DOOR.

> u
You are in a cold kitchen. Stone steps lead down.
Exits: west, down

> w
You are in a dusty hall. The front door is to the south.
Exits: north, east, south

> s
You unlock the door with the key.
You are in the garden, under an open sky.

You escaped in 15 moves. Well done.

How it is built

1. The map as data

Each room is a dictionary with a description, its exits, its items and optional flags such as dark and locked. A function builds the map, so every new game starts with the items back in their rooms.

def new_world():
    """Return a fresh map of the rooms, so every game starts the same way."""
    return {
        "hall": {
            "description": "a dusty hall. The front door is to the south.",
            "exits": {"north": "library", "east": "kitchen", "south": "outside"},
            "locked": {"south": "key"},  # this exit needs the key
            "items": [],
        },
        "library": {
            "description": "a library with tall shelves of old books.",
            "exits": {"south": "hall"},
            "items": ["note"],
        },
        "kitchen": {
            "description": "a cold kitchen. Stone steps lead down.",
            "exits": {"west": "hall", "down": "cellar"},
            "items": ["lamp"],
        },
        "cellar": {
            "description": "a damp cellar full of empty jars.",
            "exits": {"up": "kitchen"},
            "items": ["key"],
            "dark": True,
        },
        "outside": {
            "description": "the garden, under an open sky.",
            "exits": {},
            "items": [],
        },
    }

2. Describing a room

can_see returns False in a dark room unless the lamp is in the inventory, and describe then hides what is there. That one rule is the puzzle: the key lies in the dark cellar.

def describe(room, inventory):
    """Print where the player is, what is here and the ways out."""
    if can_see(room, inventory):
        print(f"You are in {room['description']}")
        if room["items"]:
            print(f"You see: {', '.join(room['items'])}")
    else:
        print("It is too dark to see anything here.")
    if room["exits"]:
        print(f"Exits: {', '.join(room['exits'])}")

3. Moving between rooms

SHORTCUTS turns n into north. A locked exit names the item it needs, and the player gets through only with that item in the inventory.

def go(world, current, direction, inventory):
    """Return the room the player ends up in after trying to move."""
    room = world[current]
    direction = SHORTCUTS.get(direction, direction)
    if not direction:
        print("Go where? For example: go north")
        return current
    if direction not in room["exits"]:
        print("You can't go that way.")
        return current
    needed = room.get("locked", {}).get(direction)
    if needed and needed not in inventory:
        print("The door is locked.")
        return current
    if needed:
        print(f"You unlock the door with the {needed}.")
    describe(world[room["exits"][direction]], inventory)
    return room["exits"][direction]

4. Picking things up

Taking an item removes it from the room's list and appends it to the inventory list, then prints what the player reads on it.

def take(room, item, inventory):
    """Move an item from the room into the inventory, if the player can see it."""
    if not item:
        print("Take what? For example: take lamp")
    elif not can_see(room, inventory):
        print("You feel around in the dark but find nothing.")
    elif item in room["items"]:
        room["items"].remove(item)
        inventory.append(item)
        print(f"You take the {item}. {ITEM_TEXT[item]}")
    else:
        print(f"There is no {item} here.")

5. Reading commands

Each command is split into words: the first is the verb and the rest is the noun, so take lamp gives verb = "take" and noun = "lamp". The loop runs until the player reaches the garden or types quit.

def play_game():
    """Play until the player gets outside or quits; return the moves taken."""
    world = new_world()
    current = START_ROOM
    inventory = []
    moves = 0
    describe(world[current], inventory)

    while current != "outside":
        # "take the lamp" and "take lamp" mean the same, so drop "the".
        words = [w for w in input("\n> ").lower().split() if w != "the"]
        if not words:
            continue
        verb, noun = words[0], " ".join(words[1:])
        moves += 1
        if verb in SHORTCUTS or verb in SHORTCUTS.values():
            current = go(world, current, verb, inventory)
        elif verb == "go":
            current = go(world, current, noun, inventory)
        elif verb == "take":
            take(world[current], noun, inventory)
        elif verb in ("inventory", "i"):
            print(f"You carry: {', '.join(inventory) or 'nothing'}")
        elif verb == "look":
            describe(world[current], inventory)
        elif verb == "help":
            print(HELP)
        elif verb == "quit":
            return None
        else:
            print("I don't understand that. Type help for the commands.")
    return moves

Make it bigger

  • Add a drop command that moves an item from the inventory back into the room.
  • Put a second puzzle in the library, such as a book that has to be read to learn a code.
  • Add a save command that writes the current room and inventory to a file.
  • Give the lamp a limited amount of oil, so it goes out after 20 moves.

What it practises