Learning Goals
3 minBy the end of this lesson you can:
- Read a scores file with
open()/withand handle a missing file usingtry/except FileNotFoundError. - Write a sorted top-5 list of integers back to the same file so scores survive between game sessions.
- Call
load_scores()andsave_scores()inside a Pygame Zero game loop to persist the leaderboard.
Warm-Up · File I/O Refresher
5 minIn Level 2 you used open() and with to read files. Let's dust that off. What does this snippet print — and what happens on the second call to int()?
lines = ["42\n", "hello\n", "7\n"] for line in lines: print(int(line.strip()))
Show the answer
The first iteration prints 42, the second raises ValueError because "hello" cannot be converted to an integer. line.strip() removes the newline, but int() still refuses a word.
That is exactly why we must be careful when reading score files — one bad line breaks everything. Today we also guard against a missing file with try/except FileNotFoundError.
New Concept · Persist Data Between Sessions
12 minThink of a high-score file like a sticky note on the fridge. The game writes new scores onto it when it closes, and reads them back when it opens. Without the sticky note, the scores disappear the moment the light goes off.
Reading a score file safely
The file does not exist on the very first run. Wrap the read in a try/except so the game starts cleanly instead of crashing:
def load_scores(filename): try: with open(filename, "r") as f: scores = [int(line.strip()) for line in f if line.strip()] except FileNotFoundError: scores = [] return scores
Writing scores back — top 5 only
Sort the combined list in descending order and keep the first five entries:
def save_scores(filename, scores): top5 = sorted(scores, reverse=True)[:5] with open(filename, "w") as f: for score in top5: f.write(str(score) + "\n")
Why it matters
Every real game — from Candy Crush to Minecraft — saves progress to a file (or a server). Learning this pattern now means you can add persistence to any project: leaderboards, save states, settings, player names.
Worked Example · A Live Leaderboard
12 minPriya wants to track her top-5 scores in a game she built in PZ-30. The file scores.txt lives in the same folder as the game. Each run loads the old scores, adds the new one, and saves the updated top-5. Save this as leaderboard.py:
# leaderboard.py — persistent top-5 leaderboard import pgzrun WIDTH = 480 HEIGHT = 400 TITLE = "Leaderboard Demo" SCORES_FILE = "scores.txt" score = 0 high_scores = [] def load_scores(filename): try: with open(filename, "r") as f: result = [int(line.strip()) for line in f if line.strip()] except FileNotFoundError: result = [] return result def save_scores(filename, scores): top5 = sorted(scores, reverse=True)[:5] with open(filename, "w") as f: for s in top5: f.write(str(s) + "\n")
def draw(): screen.fill("black") screen.draw.text("Top 5 Scores", topleft=(20, 20), fontsize=32, color="gold") for i, s in enumerate(high_scores): screen.draw.text( f"{i + 1}. {s}", topleft=(20, 70 + i * 40), fontsize=28, color="white", ) screen.draw.text( f"Current: {score} (SPACE = +10, S = save)", topleft=(20, 340), fontsize=20, color="cyan", ) def on_key_down(key): global score, high_scores if key == keys.SPACE: score += 10 if key == keys.S: high_scores = load_scores(SCORES_FILE) high_scores.append(score) save_scores(SCORES_FILE, high_scores) high_scores = load_scores(SCORES_FILE) score = 0 high_scores = load_scores(SCORES_FILE) pgzrun.go()
scores.txt, with the current session score at the bottom.Press SPACE several times to build up a score, then S to save it. Close and reopen the game — your score is still there.
Try It Yourself
13 minEdit save_scores so only the top 3 scores are saved. Test by generating six scores and checking the file has exactly three lines.
# change one character in save_scores def save_scores(filename, scores): top3 = sorted(scores, reverse=True)[:3] with open(filename, "w") as f: for s in top3: f.write(str(s) + "\n")
In draw(), display the first entry in high_scores (the highest) in gold and the rest in white. Hint: check the loop index i.
for i, s in enumerate(high_scores): colour = "gold" if i == 0 else "white" screen.draw.text(f"{i + 1}. {s}", topleft=(20, 70 + i * 40), fontsize=28, color=colour)
Extend the file format to name,score per line (e.g. Priya,350). Update load_scores to return a list of (name, score) tuples, and save_scores to write them back. Display both on screen.
Mini-Challenge · Debug Hafiz's Save Function
8 minHafiz wrote a save function but his leaderboard always shows the same score after restarting the game. Find the bugs.
# hafiz_scores.py — buggy
def load_scores(filename):
try:
with open(filename, "r") as f:
scores = [line for line in f if line.strip()]
except FileNotFoundError:
scores = []
return scores
def save_scores(filename, scores):
top5 = sorted(scores)[:5]
with open(filename, "a") as f:
for s in top5:
f.write(str(s) + "\n")It works if…
the top-5 list shrinks over time rather than growing forever, and the best scores appear first
Show the fixed version
# hafiz_scores.py — fixed def load_scores(filename): try: with open(filename, "r") as f: # Bug 1: lines must be converted to int, not kept as strings scores = [int(line.strip()) for line in f if line.strip()] except FileNotFoundError: scores = [] return scores def save_scores(filename, scores): # Bug 2: sorted() without reverse=True puts the LOWEST first # Bug 3: open with "a" appends forever — use "w" to overwrite top5 = sorted(scores, reverse=True)[:5] with open(filename, "w") as f: for s in top5: f.write(str(s) + "\n")
Three bugs: strings not cast to int, missing reverse=True, and "a" (append) instead of "w" (write/overwrite).
Recap
3 minYou now save scores between game sessions with two small helper functions. load_scores wraps the read in try/except FileNotFoundError so the first run never crashes. save_scores sorts the combined list, slices the top 5, and overwrites the file with open("w"). Call them at game-over and at start-up, and the leaderboard is persistent.
Vocabulary Card
- FileNotFoundError
- The exception Python raises when you try to open a file that does not exist yet.
- open(filename, "w")
- Opens a file for writing and overwrites any existing content. Use
"r"to read,"a"to append. - sorted(list, reverse=True)
- Returns a new list sorted highest-first. Combine with
[:5]to keep only the top 5. - persistent data
- Data that survives after a program closes — stored in a file, database, or cloud service.
Homework
4 minTake a game you built earlier in this module (PZ-28, PZ-29, or PZ-30 are good choices). Add a load_scores and save_scores function so the leaderboard persists between runs. Save as mygame_hs.py and bring a screenshot showing the same scores after restarting the game.
Sample · mygame_hs.py
# mygame_hs.py — minimal game with persistent high scores import pgzrun WIDTH = 480 HEIGHT = 360 TITLE = "Score Keeper" SCORES_FILE = "mygame_scores.txt" score = 0 high_scores = [] def load_scores(filename): try: with open(filename, "r") as f: result = [int(line.strip()) for line in f if line.strip()] except FileNotFoundError: result = [] return result def save_scores(filename, scores): top5 = sorted(scores, reverse=True)[:5] with open(filename, "w") as f: for s in top5: f.write(str(s) + "\n") def draw(): screen.fill("darkblue") screen.draw.text(f"Score: {score}", center=(240, 60), fontsize=36, color="white") screen.draw.text("Top Scores:", topleft=(20, 120), fontsize=24, color="gold") for i, s in enumerate(high_scores): screen.draw.text(f"{i + 1}. {s}", topleft=(20, 155 + i * 30), fontsize=22, color="white") screen.draw.text("SPACE +10 | S save+reset", topleft=(20, 330), fontsize=16, color="cyan") def on_key_down(key): global score, high_scores if key == keys.SPACE: score += 10 if key == keys.S: high_scores = load_scores(SCORES_FILE) high_scores.append(score) save_scores(SCORES_FILE, high_scores) high_scores = load_scores(SCORES_FILE) score = 0 high_scores = load_scores(SCORES_FILE) pgzrun.go()
The key pattern: load at start-up, append + save at game-over, reload to refresh the display. Your chosen game and styling will differ.