Python for Humans · Draft v0.1 · Oct 28, 2026 · Project · First recurring-project draft.
Text Adventure: Save Your Adventure
Optional project · After Files and the filesystem
- You should already know
- Explore the Castle; expected exceptions from Errors and debugging; file reading and writing from Files and the filesystem.
- You'll build
- An adventure with save and load commands and a small plain-text save file.
- You'll be able to
- Choose what state to store and reconstruct it without relying on old variables.
- You need
- This page for the runnable examples; your local Python setup for files that survive closing the browser.
Our castle remembers the torch as we walk between rooms. But end the program and the bag is empty again.
Let's save the two values that change as we play: the current room and the inventory. This optional installment takes about 20–30 minutes.
The examples run here in the browser. Browser files are temporary: they can be read during this run, but restarting Python or leaving the page loses them. For progress that survives closing the browser, save the final program as adventure.py and run it using your local setup from Keeping Your Work. Its save file stays in the folder where you run it.
Start with the Exploring Game
Run this complete Explore the Castle starter. You can move, collect a torch, and find the crown, but there is no save command yet.
def show_room(room, descriptions, exits): print(descriptions[room]) print('You can go to:') for destination in exits[room]: print(destination) descriptions = { 'hall': 'Two passages lead away from the castle hall.', 'dragon': 'A sleeping dragon guards a torch. Borrow it quietly!', 'treasury': 'Gold glitters in the torchlight. A crown waits here.' } exits = { 'hall': ['dragon', 'treasury'], 'dragon': ['hall'], 'treasury': ['hall'] } items = {'dragon': 'torch', 'treasury': 'crown'} room = 'hall' inventory = [] print('Welcome back to the Mysterious Castle!') while True: show_room(room, descriptions, exits) print('Your bag:', inventory) command = input('Where next? (room name, take, quit) ').lower() if command == 'quit': break elif command == 'take': if room in items: item = items[room] if item in inventory: print('You already have it.') else: inventory.append(item) print('You take the', item) if item == 'crown': print('You found the crown. You win!') break else: print('There is nothing to take here.') elif command in exits[room]: if command == 'treasury': if 'torch' in inventory: room = command else: print('Too dark! Find a torch first.') else: room = command else: print("You can't do that here.") print('Thanks for exploring!')
Decide What the File Means
Our first save format has one rule per line:
- The first line is the room identifier, such as
dragon. - Each remaining line is one inventory item, such as
torch.
The map and descriptions are part of the program, so we don't need to save them on every turn. Our identifiers and item names fit on single lines; this small format depends on that choice.
A saved game with a torch might contain:
dragon torch
No inventory? Then the file contains just the room line.
Write, Then Read
Use with open(filename, 'w', encoding='utf-8') as saved: to write, as in the files chapter. Writing with 'w' replaces the previous contents. Write the room first, then loop over inventory, adding '\n' after each value to start a new line.
When loading, saved.read() gives us one string. Its .splitlines() method gives us a list of lines without their line-break characters. The first item is our room; the rest become the inventory.
The loader below checks that the room and item names belong to our game. It raises ValueError for an empty or unrecognized save. That lets the caller keep its current state if the file is wrong.
Run this small round trip before changing the adventure. It writes its own file and immediately reads it, so it needs no file or variable from another example:
def save_game(room, inventory, filename): with open(filename, 'w', encoding='utf-8') as saved: saved.write(room + '\n') for item in inventory: saved.write(item + '\n') def load_game(filename, rooms): with open(filename, encoding='utf-8') as saved: lines = saved.read().splitlines() if len(lines) == 0: raise ValueError('The save file is empty.') if lines[0] in rooms: room = lines[0] else: raise ValueError('The saved room is unknown.') inventory = [] position = 1 while position < len(lines): item = lines[position] if item in ['torch', 'crown']: inventory.append(item) else: raise ValueError('The saved item is unknown.') position = position + 1 return {'room': room, 'inventory': inventory} rooms = {'hall': 'A hall.', 'dragon': 'A dragon.'} save_game('dragon', ['torch'], 'castle_example.txt') progress = load_game('castle_example.txt', rooms) print(progress['room']) print(progress['inventory'])
You should see dragon and ['torch']. Change the saved inventory to [], then try again. Change the room to 'hall' as well. Does the restored state match what you wrote?
load_game() returns a dictionary, keeping the room and inventory together. The caller looks them up by key; we don't need a new way to return several values.
Add Two Commands
In your adventure starter:
- Add
save_game()andload_game()above the world data. - Add a
savebranch that calls the writer with the current room and bag. - Add a
loadbranch that calls the reader, then updates both variables from its result. - Catch
FileNotFoundErrorandValueErroraround loading. Display a helpful message, then let the loop ask again.
Keep writing outside the exception handler for loading: saving and loading are different actions. The player should be able to try load before ever saving and still carry on playing.
Play, Save, and Return
Here's the complete result. It has both helpers and all the world data:
def show_room(room, descriptions, exits): print(descriptions[room]) print('You can go to:') for destination in exits[room]: print(destination) def save_game(room, inventory, filename): with open(filename, 'w', encoding='utf-8') as saved: saved.write(room + '\n') for item in inventory: saved.write(item + '\n') def load_game(filename, rooms): with open(filename, encoding='utf-8') as saved: lines = saved.read().splitlines() if len(lines) == 0: raise ValueError('The save file is empty.') if lines[0] in rooms: room = lines[0] else: raise ValueError('The saved room is unknown.') inventory = [] position = 1 while position < len(lines): item = lines[position] if item in ['torch', 'crown']: inventory.append(item) else: raise ValueError('The saved item is unknown.') position = position + 1 return {'room': room, 'inventory': inventory} descriptions = { 'hall': 'Two passages lead away from the castle hall.', 'dragon': 'A sleeping dragon guards a torch. Borrow it quietly!', 'treasury': 'Gold glitters in the torchlight. A crown waits here.' } exits = { 'hall': ['dragon', 'treasury'], 'dragon': ['hall'], 'treasury': ['hall'] } items = {'dragon': 'torch', 'treasury': 'crown'} room = 'hall' inventory = [] print('Welcome back to the Mysterious Castle!') while True: show_room(room, descriptions, exits) print('Your bag:', inventory) command = input('Where next? (room name, take, save, load, quit) ').lower() if command == 'quit': break elif command == 'save': save_game(room, inventory, 'castle_save.txt') print('Adventure saved.') elif command == 'load': try: progress = load_game('castle_save.txt', descriptions) room = progress['room'] inventory = progress['inventory'] print('Adventure loaded.') except FileNotFoundError: print('No saved adventure yet. Use save first.') except ValueError: print('That save file is not a valid castle adventure.') elif command == 'take': if room in items: item = items[room] if item in inventory: print('You already have it.') else: inventory.append(item) print('You take the', item) if item == 'crown': print('You found the crown. You win!') break else: print('There is nothing to take here.') elif command in exits[room]: if command == 'treasury': if 'torch' in inventory: room = command else: print('Too dark! Find a torch first.') else: room = command else: print("You can't do that here.") print('Thanks for exploring!')
Try dragon, take, save, hall, then load. You should return to the dragon room carrying the torch. Finish with hall, treasury, take.
In this browser box, keep those actions in one run. To check a fresh missing-file case locally, run the program in a new folder and type load. To check a bad save, replace that folder's castle_save.txt with an empty file, then load again. Neither failure should change your room or bag.
Keep Exploring
Choose a bonus:
- Add a second save slot with another filename. Show clearly which slot a command uses.
- After Modules and several files, try JSON for the save data. That chapter introduces
json; first check that a tiny room-and-inventory dictionary survives a write/read round trip, then adapt the helpers.
Our save file describes data, so we read it as data. There's no need to make Python execute its contents.
After Classes, Meet the Castle's Characters will give the dragon and a new ghost their own names, possessions, and conversation behavior.
Explain why loading replaces both the room and inventory, while the map can stay in the program.