Build a Text Adventure Game

· 9 min read · Updated March 25, 2026 · beginner
python beginner game control-flow dicts input

Text adventure games were popular before graphics existed. You read descriptions on a terminal, type commands, and explore a world made entirely of words. Games like Zork built enormous puzzles out of nothing but text and imagination.

This guide walks through building a small but complete text adventure in Python. You will use dictionaries to model rooms and items, a while loop to run the game, and string methods to understand player commands.

What You Will Build

A cave exploration game with three interconnected rooms. The player starts at the cave entrance, can move north to a main hall, and north again to a treasure room. Along the way they pick up items and trigger a win condition when they carry the right item into the treasure chamber.

The core mechanics are:

  • Display the current room description
  • Move between rooms with go <direction> commands
  • Pick up and drop items
  • Check inventory
  • Reach a win condition

You need Python 3.10 or later if you want to use the match statement. Otherwise, if/elif chains work on any Python 3 version.

Setting Up the World

The entire game world lives in a single dictionary. Each room is a key-value pair where the value is another dictionary containing the room name, description, available exits, and any items lying on the ground.

rooms = {
    "cave_entrance": {
        "name": "Cave Entrance",
        "description": "You stand at the mouth of a dark cave. Cold air drifts from the darkness ahead.",
        "exits": {"north": "main_hall"},
        "items": ["torch"],
    },
    "main_hall": {
        "name": "Main Hall",
        "description": "A vast chamber stretches before you. Ancient pillars are carved with unreadable script.",
        "exits": {"south": "cave_entrance", "north": "treasure_room"},
        "items": ["key"],
    },
    "treasure_room": {
        "name": "Treasure Room",
        "description": "Gold glitters in the torchlight. A stone altar dominates the center of the room.",
        "exits": {"south": "main_hall"},
        "items": [],
    },
}

player = {
    "current_room": "cave_entrance",
    "inventory": [],
}

rooms is a dict of dicts. player is a dict that tracks where the player is and what they carry. Both structures are flat — no classes, no nesting beyond what you see here.

To show the player where they are, write a function that reads the current room from player and prints its details:

def show_room():
    room = rooms[player["current_room"]]
    print(f"\n{room['name']}")
    print(room["description"])
    if room["items"]:
        print(f"You see: {', '.join(room['items'])}")
    exits = ", ".join(room["exits"].keys())
    print(f"Exits: {exits}")

Run show_room() and you get a complete description of the player’s surroundings, including visible items and available exits.

The Game Loop

Every text adventure needs a loop that runs until the player quits or wins. A while True loop with a playing flag is the standard approach:

playing = True

while playing:
    show_room()
    command = input("\n> ").strip().lower()

The input() call pauses the program and waits for the player to type something. strip() removes any leading or trailing whitespace, so " go north " still works. lower() converts the input to lowercase so "Go North", "GO NORTH", and "go north" are all treated the same way.

Parsing Commands

The simplest way to handle commands is with if/elif chains:

    if command == "quit" or command == "q":
        playing = False
    elif command.startswith("go "):
        direction = command.split(" ", 1)[1]
        # handle movement
    elif command.startswith("take "):
        item = command.split(" ", 1)[1]
        # handle pickup

startswith("go ") guards against IndexError when the player types just "go" with no direction. split(" ", 1) splits on the first space only, so "go north south" gives you just "north" as the direction.

If you use Python 3.10 or later, the match statement is cleaner. Each case pattern describes the command structure visually:

    parts = command.split()
    verb = parts[0] if parts else ""
    noun = parts[1] if len(parts) > 1 else ""

    match [verb, noun]:
        case ["go", direction]:
            # handle movement
        case ["take", item]:
            # handle pickup
        case _:
            print("I don't understand that.")

The _ case acts as a catch-all for anything that does not match an earlier pattern.

Moving Between Rooms

Movement checks whether the requested direction exists in the current room’s exits dictionary:

        case ["go", direction]:
            room = rooms[player["current_room"]]
            if direction in room["exits"]:
                player["current_room"] = room["exits"][direction]
                print(f"You walk {direction}.\n")
            else:
                print("You can't go that way.")

If the direction is a valid exit, player["current_room"] gets updated to the target room key. On the next iteration of the loop, show_room() displays the new room’s description.

Direction shortcuts make the game more pleasant to play. A shorthand like n for north requires only a small lookup dictionary:

direction_map = {"n": "north", "s": "south", "e": "east", "w": "west"}

direction = direction_map.get(direction, direction)

Now "go n" and "go north" both work.

Picking Up and Dropping Items

Items live in two places: the room’s items list and the player’s inventory list. The take command moves an item from the room to the player:

        case ["take", item]:
            room = rooms[player["current_room"]]
            if item in room["items"]:
                room["items"].remove(item)
                player["inventory"].append(item)
                print(f"You pick up the {item}.")
            else:
                print("That isn't here.")

The "key" in room["items"] check prevents a crash if the player tries to take something that does not exist.

Dropping an item reverses the process:

        case ["drop", item]:
            if item in player["inventory"]:
                player["inventory"].remove(item)
                rooms[player["current_room"]]["items"].append(item)
                print(f"You drop the {item}.")
            else:
                print("You aren't carrying that.")

Both functions modify two data structures — the room’s list and the player’s list — so the checks are necessary to keep the game state consistent.

Inventory and Looking Around

The inventory command lists what the player carries:

        case ["inventory"] | ["i"]:
            if player["inventory"]:
                print(f"You are carrying: {', '.join(player['inventory'])}")
            else:
                print("You are empty-handed.")

The pipe | in case ["inventory"] | ["i"] lets a single block handle both the full word and the shorthand.

A bare look command or pressing Enter with no input re-displays the current room:

        case ["look"] | []:
            show_room()

The empty list [] catches the case where the player presses Enter without typing anything.

Win Condition

Place the win check after movement has occurred. It fires when the player enters the treasure room while carrying the key:

                if player["current_room"] == "treasure_room":
                    if "key" in player["inventory"]:
                        print("\nYou place the key in the altar. Gold light floods the chamber.")
                        print("Congratulations — you have won!")
                        playing = False
                    else:
                        print("\nThe treasure chest is locked. You need a key.")

The playing = False line breaks the loop cleanly without needing sys.exit().

The Complete Game

Putting everything together, here is a fully playable text adventure:

rooms = {
    "cave_entrance": {
        "name": "Cave Entrance",
        "description": "You stand at the mouth of a dark cave. Cold air drifts from the darkness ahead.",
        "exits": {"north": "main_hall"},
        "items": ["torch"],
    },
    "main_hall": {
        "name": "Main Hall",
        "description": "A vast chamber stretches before you. Ancient pillars are carved with unreadable script.",
        "exits": {"south": "cave_entrance", "north": "treasure_room"},
        "items": ["key"],
    },
    "treasure_room": {
        "name": "Treasure Room",
        "description": "Gold glitters in the torchlight. A stone altar dominates the center of the room.",
        "exits": {"south": "main_hall"},
        "items": [],
    },
}

player = {"current_room": "cave_entrance", "inventory": []}
direction_map = {"n": "north", "s": "south", "e": "east", "w": "west"}


def show_room():
    room = rooms[player["current_room"]]
    print(f"\n{room['name']}")
    print(room["description"])
    if room["items"]:
        print(f"You see: {', '.join(room['items'])}")
    exits = ", ".join(room["exits"].keys())
    print(f"Exits: {exits}")


playing = True
show_room()

while playing:
    command = input("\n> ").strip().lower()
    parts = command.split()
    verb = parts[0] if parts else ""
    noun = parts[1] if len(parts) > 1 else ""

    if verb == "go" and noun in direction_map:
        noun = direction_map[noun]

    match [verb, noun]:
        case ["go", direction]:
            room = rooms[player["current_room"]]
            if direction in room["exits"]:
                player["current_room"] = room["exits"][direction]
                show_room()
                if player["current_room"] == "treasure_room":
                    if "key" in player["inventory"]:
                        print("\nYou place the key in the altar. Gold light floods the chamber.")
                        print("Congratulations — you have won!")
                        playing = False
                    else:
                        print("\nThe treasure chest is locked. You need a key.")
            else:
                print("You can't go that way.")
        case ["take", item]:
            room = rooms[player["current_room"]]
            if item in room["items"]:
                room["items"].remove(item)
                player["inventory"].append(item)
                print(f"You pick up the {item}.")
            else:
                print("That isn't here.")
        case ["drop", item]:
            if item in player["inventory"]:
                player["inventory"].remove(item)
                rooms[player["current_room"]]["items"].append(item)
                print(f"You drop the {item}.")
            else:
                print("You aren't carrying that.")
        case ["look"] | []:
            show_room()
        case ["inventory"] | ["i"]:
            if player["inventory"]:
                print(f"You are carrying: {', '.join(player['inventory'])}")
            else:
                print("You are empty-handed.")
        case ["help"]:
            print("Commands: go <direction>, take <item>, drop <item>, look, inventory, help, quit")
        case ["quit"] | ["q"]:
            print("Goodbye!")
            playing = False
        case _:
            print("I don't understand that. Type 'help' for commands.")

print("Thanks for playing!")

Run this with python game.py and you have a working three-room adventure. Try go north, take key, go north to reach the treasure room with the key and win.

Common Mistakes

Putting input() inside a print() call — print("> ", input()) — combines the prompt and the input on the same line, which makes the output harder to read. Use separate lines instead:

    command = input("\n> ").strip().lower()

Forgetting to strip() input means commands with extra spaces silently fail. A player who types " go north " expects it to work, and with strip() it does.

Not checking whether an item exists before moving it causes crashes or duplicate items. Always verify "item in list" before list.remove(item) or list.append(item).

Extending the Game

Once the basic loop works, several additions fit within beginner scope:

Add more rooms by expanding the rooms dictionary. Remember to set the exits for each new room so the player can navigate.

Add a locked door that requires a specific item to open. Store "door_locked": True in a room and check the player’s inventory before allowing movement through that exit.

Add a simple save system using the json module to write rooms and player to a file. Load them back with json.load() when the game starts.

Refactor to use classes when you are ready to handle object-oriented design. A Room class, an Item class, and a Game class can make larger games easier to manage — but that belongs in an intermediate guide.

Conclusion

You built a working text adventure using Python data structures you already know. Dictionaries modeled the world, while drove the game loop, input() collected player commands, and string methods parsed those commands into actions.

The core pattern — data structure plus loop plus input — applies far beyond games. Every interactive program follows this same shape.

Try building a two-room version first. Get movement working. Then add one item, then the win condition. Small steps, each one testable. That is how professional games are built too.

See Also