← Teacher guide

Save the game

Students write and read a file, save a dictionary with json, catch FileNotFoundError for a first run, then add save and load commands to their text adventure and test that a save survives a page reload.

Deeper Python in the console · Errors and the adventure · about 50 minutes · Open the student lesson

Classroom resources

Load these into Waypoints Prepare: choose Load URL and paste the copied address, or download the file and import it. In Prepare, the worksheet's Teacher edition shows the answers; the student copy hides them.

Students will be able to

Timing

Predict and Run 10 min · Investigate 10 min · Milestone 1 10 min · Milestone 2 10 min · Make 10 min

Before class

Read the unit primer first. This lesson runs in the Python playground (real Python in the browser, Pyodide), not the turtle playground: it has real files. The first load takes a few seconds; before teaching it, open the site's Check your school page once on a student device at school. A student's files are kept in their browser for this course and listed in the Files tab, where they can be read and deleted; they survive closing the page, but not clearing the browser's data, and they do not follow a student to another device. open(name, "w") empties the file first (the Predict and the bug task); open(name) reads. with closes the file when its block ends. json.dump(data, f) writes a dictionary or list as JSON text, and json.load(f) gives the same dictionary back. Opening a missing file causes FileNotFoundError, caught exactly like the errors in Lesson 4. The game saves here, bag and things (everything that changes during play): saving only here and bag would let a player take the key twice.

Running the lesson

  1. Predict: write kitchen then garden on two sticky notes; the second "w" throws the first note away.
  2. Run: have students reload the page between runs. The point is that the file outlives the program.
  3. Investigate: open the Files tab and compare me.json with the dictionary in the code.
  4. Milestone 1: ask what changes during play. Everything that changes must be saved.
  5. Milestone 2: test with a reload, not just a second command. Pair students: one saves, the other reloads and loads.

The hard part (on purpose)

Milestone 2 is the hard part: putting the loaded values back into here, bag and things. When students are stuck, ask them to say aloud what game holds after json.load, before giving any code.

When students get stuck

What you will seeA question to askThe fix
FileNotFoundError when they load.Did you save first, in this course's playground?Save first, or catch FileNotFoundError and say there is no saved game.
The save file is empty, or json.load says the file is not valid JSON.Which open() has "w" in it?Read with open("game.json"), not "w": "w" empties the file. Delete the broken file in the Files tab and save again.
After load, the key is back in the kitchen as well as in the bag.What else changes during play, apart from here and bag?Save and load things too.
load says Loaded. but nothing changed.Did you set here, bag and things from game, or only read the file?here = game["here"], and the same for bag and things.

Answers to the checks

  1. Predict: What does it print?
    garden. Opening a file with "w" empties it first, so the second write replaces kitchen. Reading it back gives only garden.
  2. The first run: The very first time it runs, why does the except part run?
    There is no me.json yet, so open() causes a FileNotFoundError
    Opening a file that is not there causes a FileNotFoundError. except FileNotFoundError: is the first-time path: ask the name and start counting.
  3. Inside the file: What does me.json hold after Ada's second run?
    {"name": "Ada", "visits": 2}
    json.dump writes the dictionary as JSON text, which looks almost exactly like a Python dictionary. The last lines write it again after every run, with the new count.
  4. Reading it back: After me = json.load(f), what is me?
    A dictionary, so me["name"] works
    json.load turns the JSON text back into the dictionary that was saved, so you use it exactly as before: me["name"], me["visits"].
  5. Spot the bug: This should welcome Ada back, but it stops with an error, and afterwards me.json is empty. Click the line that is wrong.
    line 2
    "w" empties the file straight away, ready to write: it cannot read. To read, open it without "w": open("me.json").
  6. Where this leads: A friend opens your game on their own computer. Will your saved game be there?
    No: the file is on the device that saved it (here, in your browser)
    A file lives where it was written. To share a save, the file itself has to be copied or uploaded, which is what cloud saves do.

Solutions

One possible solution for each task. Other correct programs are fine.

Milestone 1: save

import json

rooms = {
    "hall": "A long hall with a creaky floor.",
    "kitchen": "A kitchen that smells of toast.",
    "garden": "A garden full of tall grass. A locked shed stands to the east.",
    "shed": "A dusty shed. Something glints in the corner.",
}
exits = {
    "hall": {"north": "kitchen", "east": "garden"},
    "kitchen": {"south": "hall"},
    "garden": {"west": "hall", "east": "shed"},
    "shed": {"west": "garden"},
}
things = {
    "hall": ["lamp"],
    "kitchen": ["key"],
    "garden": [],
    "shed": ["crown"],
}
bag = []
here = "hall"
print(rooms[here])
while True:
    command = input("> ").strip().lower()
    words = command.split()
    if command == "quit":
        break
    elif command == "look":
        print(rooms[here])
        print("Exits: " + ", ".join(exits[here]))
        if len(things[here]) > 0:
            print("You see: " + ", ".join(things[here]))
    elif command == "bag":
        print("You carry: " + ", ".join(bag))
    elif len(words) == 2 and words[0] == "go":
        if words[1] in exits[here]:
            if exits[here][words[1]] == "shed" and "key" not in bag:
                print("The shed is locked.")
            else:
                here = exits[here][words[1]]
                print(rooms[here])
        else:
            print("You can't go that way.")
    elif len(words) == 2 and words[0] == "take":
        if words[1] in things[here]:
            things[here].remove(words[1])
            bag.append(words[1])
            print("You take the " + words[1] + ".")
        else:
            print("There is no " + words[1] + " here.")
    elif len(words) == 2 and words[0] == "drop":
        if words[1] in bag:
            bag.remove(words[1])
            things[here].append(words[1])
            print("You drop the " + words[1] + ".")
        else:
            print("You don't have a " + words[1] + ".")
    elif command == "save":
        game = {"here": here, "bag": bag, "things": things}
        with open("game.json", "w") as f:
            json.dump(game, f)
        print("Saved.")
    else:
        print("Try: look, go north, take lamp, drop lamp, bag, save, load or quit")
    if "crown" in bag:
        print("You found the crown. You win!")
        break

Milestone 2: load

import json

rooms = {
    "hall": "A long hall with a creaky floor.",
    "kitchen": "A kitchen that smells of toast.",
    "garden": "A garden full of tall grass. A locked shed stands to the east.",
    "shed": "A dusty shed. Something glints in the corner.",
}
exits = {
    "hall": {"north": "kitchen", "east": "garden"},
    "kitchen": {"south": "hall"},
    "garden": {"west": "hall", "east": "shed"},
    "shed": {"west": "garden"},
}
things = {
    "hall": ["lamp"],
    "kitchen": ["key"],
    "garden": [],
    "shed": ["crown"],
}
bag = []
here = "hall"
print(rooms[here])
while True:
    command = input("> ").strip().lower()
    words = command.split()
    if command == "quit":
        break
    elif command == "look":
        print(rooms[here])
        print("Exits: " + ", ".join(exits[here]))
        if len(things[here]) > 0:
            print("You see: " + ", ".join(things[here]))
    elif command == "bag":
        print("You carry: " + ", ".join(bag))
    elif len(words) == 2 and words[0] == "go":
        if words[1] in exits[here]:
            if exits[here][words[1]] == "shed" and "key" not in bag:
                print("The shed is locked.")
            else:
                here = exits[here][words[1]]
                print(rooms[here])
        else:
            print("You can't go that way.")
    elif len(words) == 2 and words[0] == "take":
        if words[1] in things[here]:
            things[here].remove(words[1])
            bag.append(words[1])
            print("You take the " + words[1] + ".")
        else:
            print("There is no " + words[1] + " here.")
    elif len(words) == 2 and words[0] == "drop":
        if words[1] in bag:
            bag.remove(words[1])
            things[here].append(words[1])
            print("You drop the " + words[1] + ".")
        else:
            print("You don't have a " + words[1] + ".")
    elif command == "save":
        game = {"here": here, "bag": bag, "things": things}
        with open("game.json", "w") as f:
            json.dump(game, f)
        print("Saved.")
    elif command == "load":
        try:
            with open("game.json") as f:
                game = json.load(f)
            here = game["here"]
            bag = game["bag"]
            things = game["things"]
            print("Loaded.")
            print(rooms[here])
        except FileNotFoundError:
            print("There is no saved game yet.")
    else:
        print("Try: look, go north, take lamp, drop lamp, bag, save, load or quit")
    if "crown" in bag:
        print("You found the crown. You win!")
        break

carry on where you left off › Offer to load

Open task: any program that does what the step asks.

carry on where you left off › Your own touch

Open task: any program that does what the step asks.

Stretch and support

Stretch: Keep a best score in a second file (fewest moves to win), and only replace it when a new game beats it.

Support: Give students the save branch written out and ask them to write load by mirroring it, with json.load instead of json.dump.

Exit ticket

Why does the game save things as well as here and bag?

things changes during play (taking moves an item out of a room), so without it a loaded game would put the item back in the room as well as in the bag.

What each step is for

Where it leads

Data detective reads data files (CSV and JSON) the same way. Web apps later store what users type in a file, then in a database.