Learning Goals
3 minBy the end of this lesson you can:
- Use a
locationstring variable to track where the player is in the world. - Write one function per scene (
scene_village(),scene_forest(),scene_cave()) that prints the scene's description and returns the player's next location. - Wrap everything in a main loop that calls the right scene based on
location, until the player reaches the end or typesquit.
Warm-Up
5 minTake a moment to sketch what we're building. Three rooms, four arrows:
village ──→ forest ──→ cave (end) ↑ │ └──────────┘ (back to village)
The player starts at "village". From the village they type forest to walk into the forest. From the forest they can type cave to push on, or back to return to the village. The cave is the end.
The way to write this is one function per room. Each function describes the room, asks the player what they want to do, and returns the name of the room they should go to next. A tiny outer loop calls the right function based on the current location.
Each scene is independent — different description, different choices. Bundling them in one giant if/elif chain quickly becomes unreadable. One function per scene lets each one stay short and focused.
New Concept · Scenes Return the Next Location
14 minA scene is a function that returns a place
def scene_village(): print("You're in the village square. Smoke rises from the chimneys.") print("Paths lead into the FOREST or you can STAY by the well.") choice = input("> ").strip().lower() if choice == "forest": return "forest" elif choice == "stay": return "village" else: print("You stand still, confused.") return "village"
The function:
- Describes the scene with prints.
- Asks for input.
- Decides what to do based on the answer.
- Returns the name of the next location — possibly the same one if the player chose to stay.
Always return a known location
Notice the else — if the player types nonsense, we return "village" too, so the loop calls scene_village() again and they re-read the description. Never return an unknown string — that would break the dispatcher below.
The main loop is a dispatcher
One tiny outer loop reads the current location and calls the matching scene:
location = "village" while True: if location == "village": location = scene_village() elif location == "forest": location = scene_forest() elif location == "cave": location = scene_cave() elif location == "end": print("THE END.") break elif location == "quit": print("You set down your pack. Goodbye.") break
This is a dispatcher pattern — the loop's only job is to look at a string and call the right function. To add a fifth scene, you write one more function and add one more elif branch.
Two ways the loop ends
- The player reaches an ending scene like
"end"— handled by a specialelifwith abreak. - The player asks to quit — any scene can return
"quit"and the loop ends gracefully.
String comparison is case-sensitive — clean before comparing
From PY-L1-23, .strip().lower() on every input. Without it, "Forest" wouldn't equal "forest" and the player would get sent down the else branch by accident.
Scenes return strings. The main loop maps strings to scenes. The world is just a list of named places connected by which strings each scene can return.
Worked Example · The Three-Room World
14 minThe full file
Save as adventure.py:
Code
# adventure.py — Part 1: three scenes, branching map def scene_village(): print() print("== VILLAGE SQUARE ==") print("Smoke curls from chimneys. A path leads east into the forest.") print("You can also STAY by the well, or QUIT and go home.") choice = input("> ").strip().lower() if choice == "forest": return "forest" elif choice == "stay": print("You linger. The villagers nod at you.") return "village" elif choice == "quit": return "quit" else: print("You don't understand. Try 'forest', 'stay', or 'quit'.") return "village" def scene_forest(): print() print("== FOREST PATH ==") print("Tall trees whisper. The path forks — onward to a CAVE, or BACK to the village.") choice = input("> ").strip().lower() if choice == "cave": return "cave" elif choice == "back": return "village" elif choice == "quit": return "quit" else: print("The wind rustles. Try 'cave', 'back', or 'quit'.") return "forest" def scene_cave(): print() print("== CAVE MOUTH ==") print("Cold air drifts from the darkness. You step inside and discover an old chest.") print("Inside is a single gold coin. You take it and walk home a little richer.") return "end" # main program — the dispatcher print("=" * 40) print(" A Short Adventure") print("=" * 40) location = "village" while True: if location == "village": location = scene_village() elif location == "forest": location = scene_forest() elif location == "cave": location = scene_cave() elif location == "end": print() print("=" * 40) print("THE END.") print("=" * 40) break elif location == "quit": print() print("You set down your pack. Goodbye.") break else: print("(The world flickers. Something went wrong.)") break
Sample run · a quick playthrough
========================================
A Short Adventure
========================================
== VILLAGE SQUARE ==
Smoke curls from chimneys. A path leads east into the forest.
You can also STAY by the well, or QUIT and go home.
> forest
== FOREST PATH ==
Tall trees whisper. The path forks — onward to a CAVE, or BACK to the village.
> back
== VILLAGE SQUARE ==
Smoke curls from chimneys. A path leads east into the forest.
You can also STAY by the well, or QUIT and go home.
> forest
== FOREST PATH ==
Tall trees whisper. The path forks — onward to a CAVE, or BACK to the village.
> cave
== CAVE MOUTH ==
Cold air drifts from the darkness. You step inside and discover an old chest.
Inside is a single gold coin. You take it and walk home a little richer.
========================================
THE END.
========================================Things to notice
- Each scene function is self-contained. To add a new room, write a new function and add one
elifbranch in the dispatcher. Nothing else needs to change. - Returning
"end"from a scene tells the dispatcher to stop. Nosys.exit, no global flag — just a string the loop recognises. - Nested
ifs? Not really. Each scene is one flatif/elif/else. The "nesting" is purely conceptual — the dispatcher dispatches, and each scene branches on its own input.
Next lesson we add a list-based inventory — pick up the coin from the cave, drop it later, check the bag. Part 3 adds a boss fight with HP and a while battle loop. The scenes and dispatcher from today don't change — we just add features around them.
Try It Yourself
14 minThree tasks. Build a tiny world, then expand it.
Write scene_home() and scene_school(). Each prints a description and asks for input. Home → typing school returns "school". School → typing home returns "home". Either scene → typing quit returns "quit". Anything else → return the same scene. Then wire them up with a dispatcher loop.
Hint
def scene_home(): print("You're at home. Type 'school' to walk there, 'quit' to stop.") c = input("> ").strip().lower() if c == "school": return "school" if c == "quit": return "quit" return "home" def scene_school(): print("You're at school. Type 'home' to walk back, 'quit' to stop.") c = input("> ").strip().lower() if c == "home": return "home" if c == "quit": return "quit" return "school" location = "home" while True: if location == "home": location = scene_home() elif location == "school": location = scene_school() elif location == "quit": print("Goodbye."); break
Notice the compact one-line ifs. Both work, but stack them vertically (no else) only when each branch returns — otherwise readers get confused.
Recreate the full village → forest → cave game. Play it three times: once straight through, once with a back-track, once with bad inputs. Make sure bad inputs keep you in the same scene and never crash.
Hint
Watch out for the else branch — it should return the current location. scene_village() returns "village" on bad input. scene_forest() returns "forest" on bad input. Mixing them up is the most common bug.
Add a scene_river() reachable from the village (e.g. type river). From the river the player can swim across (which ends the game with a different ending — return "end"), or back to the village. Don't forget the dispatcher elif.
Hint
def scene_river(): print() print("== RIVERBANK ==") print("A fast river. SWIM across, or go BACK to the village.") c = input("> ").strip().lower() if c == "swim": print("You cross. On the far bank you find a new village...") return "end" if c == "back": return "village" return "river" # in scene_village(), add: # elif choice == "river": return "river" # in the dispatcher, add: # elif location == "river": location = scene_river()
Three little edits — the village gets a new branch, a new scene function is added, and the dispatcher learns to call it. The rest of the world doesn't need to know.
Mini-Challenge · Mei's Disappearing Player
8 minMei's game crashes whenever the player types a wrong command. Find the bug.
# mei_adventure.py — buggy
def scene_village():
print("Village. Type 'forest'.")
c = input("> ").strip().lower()
if c == "forest":
return "forest"
elif c == "quit":
return "quit"
# missing else → returns None for any other input
def scene_forest():
print("Forest. Type 'back'.")
c = input("> ").strip().lower()
if c == "back":
return "village"
location = "village"
while True:
if location == "village":
location = scene_village()
elif location == "forest":
location = scene_forest()
elif location == "quit":
break
else:
print("World error.")
break- Bug 1.
scene_villagehas noelse, so when the player types anything other thanforestorquit, the function falls off the end and returnsNone. The dispatcher then haslocation == None, which matches noelifand lands in theelse: break. - Bug 2. Same problem in
scene_forest— every option exceptbackreturnsNone.
Show the fix
def scene_village(): print("Village. Type 'forest'.") c = input("> ").strip().lower() if c == "forest": return "forest" elif c == "quit": return "quit" else: print("Try 'forest' or 'quit'.") return "village" # ← stay put def scene_forest(): print("Forest. Type 'back' or 'quit'.") c = input("> ").strip().lower() if c == "back": return "village" elif c == "quit": return "quit" else: return "forest"
A scene function must always return a valid location — including when the input was nonsense. Otherwise Python silently returns None and the dispatcher gets a bogus value. Make the else branch mandatory in every scene.
Recap
3 minA text adventure is a state machine in disguise. The state is a single string — location — that says where the player is. Each location has a function that describes the scene, takes input, and returns the next location. A tiny main loop dispatches by reading location and calling the matching scene. To add a new place, write one more function and one more elif — nothing else changes. Always clean the user's input with .strip().lower() and always return a valid location, even on bad input. Next lesson we add an inventory list so the player can pick up items along the way.
Vocabulary Card
- scene function
- A function that describes one location, takes input, and returns the name of the next location.
- dispatcher
- A loop or chain of
elifs that picks which function to call based on a value (here,location). - state variable
- A variable that tracks the program's current condition. For us:
location. - terminal scene
- A scene that returns a sentinel value like
"end"instead of a real location, telling the dispatcher to stop. - sentinel value
- A special value used as a signal — like
"end","quit", orNone. Has no real-world meaning, just tells the loop something.
Homework
4 minTake adventure.py from class and grow your world.
- Add at least two new scenes — keep them short, two or three lines of description plus the choices.
- Make sure your world has at least one loop in its map (a way to walk in a circle), so the player can wander without ending.
- Add at least one extra ending — a different scene that returns
"end"with its own farewell. The dispatcher already handles"end"— no changes needed there. - Every scene must clean input and return a valid location even on bad input. Re-read the Mini-Challenge above if you forget.
Bring adventure.py next class — PY-L1-40 adds an inventory list.
Sample · adventure.py (five scenes)
# adventure.py — Part 1 with five scenes and two endings def scene_village(): print("\n== VILLAGE ==\nPaths to FOREST, MARKET, or RIVER. STAY or QUIT.") c = input("> ").strip().lower() if c in ("forest", "market", "river"): return c if c == "stay": return "village" if c == "quit": return "quit" print("Try forest / market / river / stay / quit.") return "village" def scene_forest(): print("\n== FOREST ==\nA shadowy CAVE ahead, or BACK to village.") c = input("> ").strip().lower() if c == "cave": return "cave" if c == "back": return "village" if c == "quit": return "quit" return "forest" def scene_market(): print("\n== MARKET ==\nThe stalls are quiet today. BACK only.") c = input("> ").strip().lower() if c == "back": return "village" if c == "quit": return "quit" return "market" def scene_river(): print("\n== RIVERBANK ==\nSWIM across (the end) or BACK.") c = input("> ").strip().lower() if c == "swim": print("You reach the far bank, a new life ahead.") return "end" if c == "back": return "village" if c == "quit": return "quit" return "river" def scene_cave(): print("\n== CAVE ==\nYou find a coin and walk home rich.") return "end" # dispatcher location = "village" while True: if location == "village": location = scene_village() elif location == "forest": location = scene_forest() elif location == "market": location = scene_market() elif location == "river": location = scene_river() elif location == "cave": location = scene_cave() elif location == "end": print("\nTHE END.") break elif location == "quit": print("\nGoodbye.") break
Two slick simplifications worth noticing. if c in ("forest", "market", "river"): return c handles three branches in one line — the tuple is checked for membership, and the input itself is the location to return. And scene_river shows how to give a scene two exits with different endings — different prints, both eventually returning "end".