A Text Adventure Game in Python
Build a text adventure game in Python: design rooms with dictionaries, move the player with input, track inventory and win conditions, and structure the game loop. Runnable code, a complete playable game and a quiz.
Key takeaways
- A text adventure describes a world in words and lets the player type commands to explore it
- Rooms are best stored as a dictionary, each with a description and a map of exits to other rooms
- A while loop is the game loop: show the room, read a command, update the state, repeat
- The player's position and inventory are just variables you read and change as the game runs
- Win and lose conditions are checks inside the loop that break out and end the game
Worlds made of words
Before games had graphics, they had words. You'd read a description β "You are in a damp cave. Passages lead north and east." β and type what you wanted to do: go north, take torch, look. These text adventures are still wonderful to build, because the entire game is logic you can read and change. And they teach the heart of game programming: a world, a loop, and state that changes over time.
In this lesson you'll build a complete, playable adventure. You'll need to be comfortable with dictionaries and while loops β those two ideas do almost all the heavy lifting.
The big idea: data plus a loop
Every text adventure has two parts:
- The world data β a description of every room and how they connect. This is just information, so we store it in a dictionary.
- The game loop β a
whileloop that, each turn, shows where you are, reads what you type, and updates the game.
Keeping the world (data) separate from the engine (loop) is the design secret. Add a room and the loop never changes.
Describing a room
Let's store each room as a dictionary holding a description and its exits. The exits are themselves a small dictionary mapping a direction to the name of the room it leads to:
hall = {
"description": "a grand entrance hall with a dusty chandelier",
"exits": {"north": "library", "east": "kitchen"},
}
Reading hall["exits"]["north"] gives "library" β the room you reach by going north. This nested structure is exactly what we need to move around.
The whole map
Now we collect every room into one big dictionary, keyed by room name:
rooms = {
"hall": {
"description": "a grand entrance hall with a dusty chandelier",
"exits": {"north": "library", "east": "kitchen"},
},
"library": {
"description": "a quiet library lined with old books",
"exits": {"south": "hall"},
"item": "key",
},
"kitchen": {
"description": "a cold kitchen; a locked door leads east",
"exits": {"west": "hall", "east": "treasure"},
},
"treasure": {
"description": "a glittering treasure vault β you win!",
"exits": {},
},
}
Notice the library has an extra "item": "key". We'll let the player pick that up. The treasure room has no exits β reaching it is the goal.
Tracking the player
The player's situation is held in plain variables: where they are, and what they're carrying.
current = "hall" # the room the player is in
inventory = [] # things the player has picked up
To move, we change current. To collect an item, we append to inventory. That's all "state" really is β variables that change as the game unfolds.
Reading and understanding commands
Each turn we read a command and tidy it up so the game is forgiving about capitals and spacing:
command = input("> ").strip().lower()
words = command.split() # "go north" -> ["go", "north"]
.strip().lower() turns " GO North " into "go north", and .split() breaks it into a list of words. Now we can check words[0] (the action) and words[1] (the direction or item).
Moving safely
When the player types go north, we must check that direction exists before moving β otherwise we'd crash:
exits = rooms[current]["exits"]
direction = words[1]
if direction in exits:
current = exits[direction] # move!
else:
print("You can't go that way.")
if direction in exits: is the key safety check. Only valid directions move the player; anything else gets a polite refusal.
Worked example: the complete game
Here's the whole thing β map, state, and game loop β in one runnable program. Read the comments to see how each piece connects.
rooms = {
"hall": {
"description": "a grand entrance hall with a dusty chandelier",
"exits": {"north": "library", "east": "kitchen"},
},
"library": {
"description": "a quiet library lined with old books",
"exits": {"south": "hall"},
"item": "key",
},
"kitchen": {
"description": "a cold kitchen; a locked door leads east",
"exits": {"west": "hall", "east": "treasure"},
},
"treasure": {
"description": "a glittering treasure vault",
"exits": {},
},
}
def play():
current = "hall"
inventory = []
print("=== The Locked Vault ===")
print("Commands: go <direction>, take, look, quit\n")
while True:
room = rooms[current]
print(f"\nYou are in {room['description']}.")
# Win check: reaching the treasure ends the game
if current == "treasure":
print("You found the treasure. You win!")
break
# Show what you can do
print("Exits:", ", ".join(room["exits"]) or "none")
if "item" in room and room["item"] not in inventory:
print(f"You see a {room['item']} here.")
command = input("> ").strip().lower()
words = command.split()
if not words:
continue
action = words[0]
if action == "quit":
print("Thanks for playing!")
break
elif action == "look":
continue # the loop will redraw the room
elif action == "take":
if "item" in room and room["item"] not in inventory:
inventory.append(room["item"])
print(f"You picked up the {room['item']}.")
else:
print("There's nothing to take.")
elif action == "go" and len(words) > 1:
direction = words[1]
exits = room["exits"]
if direction not in exits:
print("You can't go that way.")
# The kitchen's east door needs the key
elif current == "kitchen" and direction == "east" \
and "key" not in inventory:
print("The door is locked. You need a key.")
else:
current = exits[direction]
else:
print("I don't understand that. Try: go north, take, quit.")
play()
How the pieces work together:
- The map is pure data at the top. The engine below never hard-codes a room layout, so you can redesign the world freely.
- The loop redraws the room each turn, then checks the win condition (
current == "treasure") andbreaks if met. ", ".join(room["exits"])lists the available directions neatly;or "none"handles a room with no exits.- The take action moves an item from the room into
inventory, but only if it's there and not already held. - The go action has a twist: the kitchen's east door is locked until
"key"is in the inventory. The player must visit the library,takethe key, then return β a real puzzle! The backslash\just continues that longelifcondition onto the next line.
Play it: from the hall, go north to the library, take the key, go south, east to the kitchen, then east to win.
Try it yourself
Expand your adventure:
- Add a room. Give the library a hidden
"east"exit to a new"tower"room. You only edit theroomsdictionary β the engine needs no changes, which proves the design works. - Add a monster. Put a
"monster": Trueflag in a room and end the game (a loss) if the player enters it without a"sword"in their inventory. - Add an
inventorycommand that prints what the player is carrying. - Challenge: add randomness with the random module β for example, a 1-in-3 chance that a draught blows out the player's torch, requiring matches found elsewhere.
You've now built a real, stateful program with a world, a loop, and puzzles β the same architecture behind far bigger games. To strengthen the logic skills underneath it, revisit building a quiz app in Python.
Quick quiz
Test yourself and earn XP
What data structure works best for storing the game's rooms?
A dictionary lets you look up a room by name and read its description and exits instantly.
What is the 'game loop'?
The game loop runs every turn: display, read input, change state, then repeat until the game ends.
How is the player's current location usually tracked?
A variable like current = "hall" stores where the player is; you change it when they move.
If a room's exits are {'north': 'cave'}, what should happen when the player types 'go south'?
South is not a key in the exits dict, so the game should print a 'you cannot go that way' message instead of crashing.
What ends the game loop when the player wins?
Reaching a win condition uses break (or flips the loop flag) to leave the while loop and finish the game.
FAQ
Because the whole map lives in one dictionary, adding a room is just adding one more entry with its description and exits β you do not touch the game loop at all. That separation of data (the rooms) from logic (the loop) is the key design idea. As your world grows you could even move the rooms dictionary into its own file or load it from JSON, keeping the engine unchanged.
Normalise the input first: call .lower() so GO and go match, and .strip() to remove stray spaces. Then split the words with .split() so 'go north' becomes ['go', 'north']. You can accept shortcuts by checking the first word, or map synonyms (like 'grab' and 'take') to the same action. A little input cleaning makes the game feel much friendlier.
Keep exploring
More in Coding