1 The Problem
We want a text adventure: the player reads a scene, picks from choices, and moves to new scenes — some leading to victory, some to doom. It teaches modelling a world as a graph of connected rooms and writing an engine that walks that graph based on player choices.
2 How to Think About It
Think about how to represent the world, before any code:
3 The Build — explained part by part
Here is the complete adventure engine. Each part is explained below.
# A branching text adventure.
rooms = {
"start": {
"text": "You stand at a fork. Left is dark, right is bright.",
"choices": {"left": "cave", "right": "meadow"},
},
"cave": {
"text": "A dragon sleeps here. You grab the gold and flee!",
"choices": {}, # no choices = an ending
},
"meadow": {
"text": "A peaceful meadow. A path leads back.",
"choices": {"back": "start"},
},
}
def play(rooms, start="start"):
current = start
while True:
room = rooms[current]
print("\n" + room["text"])
if not room["choices"]:
print("THE END")
break
print("Options:", ", ".join(room["choices"]))
choice = input("> ").lower()
if choice in room["choices"]:
current = room["choices"][choice]
else:
print("You cannot go that way.")
if __name__ == "__main__":
play(rooms)
text) and choices mapping a word to the next room’s name. This is the game map."choices": {} — an empty choices dictionary marks an ending: there is nowhere to go, so the game stops.
room = rooms[current] — look up the room we are in by name.
if not room["choices"]: — if there are no choices, this is an ending: print THE END and break.
current = room["choices"][choice] — the heart of navigation: follow the chosen exit to the next room’s name, and loop. The engine never changes — only the data does.
KeyError.choices dictionary.4 Test & Prove Each Part
We test the navigation logic — that choices lead to the right rooms and endings are detected — without playing interactively.
ROOMS = {
"start": {"text": "fork", "choices": {"left": "cave", "right": "meadow"}},
"cave": {"text": "dragon", "choices": {}},
"meadow": {"text": "meadow", "choices": {"back": "start"}},
}
def next_room(rooms, current, choice):
return rooms[current]["choices"].get(choice, current)
def is_ending(rooms, current):
return not rooms[current]["choices"]
def test_valid_choice():
"""'left' from start leads to the cave."""
assert next_room(ROOMS, "start", "left") == "cave"
def test_ending():
"""The cave is an ending."""
assert is_ending(ROOMS, "cave") is True
def test_all_links_valid():
"""Every choice points to a real room."""
for room in ROOMS.values():
for destination in room["choices"].values():
assert destination in ROOMS
Run with pytest -v. The all-links-valid test is special: it checks the data is consistent — no choice leads to a room that does not exist. Validating your data this way catches a whole class of bugs.
5 The Interface
What it expects
You stand at a fork...
Options: left, right
> leftWhat it returns
A dragon sleeps here. You grab the gold and flee!
THE END6 Run It & Automate It
python3 adventure.pyRead each scene and type a choice to explore. Add your own rooms to the dictionary.
Jenkins runs the tests automatically — each line explained below.
You stand at a fork. Left is dark, right is bright.
Options: left, right
> left
A dragon sleeps here. You grab the gold and flee!
THE ENDKeyError when moving."choices": {}.// Jenkinsfile — automated tests on every change.
pipeline {
agent any
stages {
stage('Get the code') { steps { checkout scm } } // download the code
stage('Install') { steps { sh 'python3 -m venv venv && . venv/bin/activate && pip install pytest' } } // test tool
stage('Test') { steps { sh '. venv/bin/activate && pytest -v' } } // run every test
}
post {
success { echo 'All tests passed.' }
failure { echo 'A test failed — look above.' }
}
}
- Inventory. Let the player collect items that unlock paths. (Teaches: tracking state.)
- Load from a file. Store the map in JSON so writers can edit it. (Teaches: data-driven content.)
- Win and lose endings. Mark endings as good or bad and tally outcomes. (Teaches: richer room data.)