UMPSA STEM LAB · Step 8 of 10
Make your game remember the high score between sessions using file I/O, try/except error handling, f-strings, and a dictionary to store player scores.
Variables live in RAM. When Python exits, they're gone. To make the high score persist, we write it to a file on disk that survives between runs.
This is one of the most common patterns in software: RAM is fast but volatile (disappears on quit), disk is slow but persistent. Every app you use does this: a game saves to a save file, a browser saves bookmarks, a word processor saves documents. The pattern is always: read state from disk at start → modify in RAM → write back to disk on change.
Python's open() function opens a file. The with statement ensures the file is properly closed even if an error occurs.
score = 42
with open("highscore.txt", "w") as f:
f.write(str(score))
# File now contains the text "42"
# "w" mode: creates file if not exists, overwrites if it does
with open("highscore.txt", "r") as f:
content = f.read() # read all text as a string
high = int(content) # convert "42" → 42
# "r" mode: read-only (file must exist)
print(f"High score: {high}") # f-string!
| Mode | Meaning | File missing? |
|---|---|---|
"r" | Read only | ❌ Error! |
"w" | Write (overwrite) | ✅ Creates it |
"a" | Append | ✅ Creates it |
"r+" | Read + write | ❌ Error! |
Press each button in order to see exactly what Python does when it writes to, and then reads from, a file. Notice how RAM is cleared between runs — but the file persists.
What happens the first time you run the game? The highscore file doesn't exist yet. Reading it would crash the program. try/except handles this gracefully.
Professional software assumes things will go wrong: files won't exist, networks will time out, users will enter bad data. try/except is Python's way of making code robust — it tries the risky operation and provides a fallback when it fails. This is called defensive programming, and it's what separates hobby code from production code.
with open("highscore.txt", "r") as f:
best = int(f.read()) # might fail!
except (FileNotFoundError, ValueError):
best = 0 # first run, or corrupted file
FileNotFoundError — file doesn't exist (first run). ValueError — file contains invalid text (corrupted). Both are caught and handled with a sensible default of 0.
f-strings embed expressions directly inside strings. They're the cleanest way to combine text and variables in Python 3.6+.
score = 42; player = "Ahmad"; best = 100
# Old way (clunky)
msg = "Player " + player + " scored " + str(score)
# f-string (clean)
msg = f"Player {player} scored {score}"
# f-strings can evaluate expressions!
msg = f"Score: {score} Best: {best} New best: {score > best}"
# → "Score: 42 Best: 100 New best: False"
msg = f"You scored {score/best*100:.1f}% of the high score"
# → "You scored 42.0% of the high score"
# {value:.1f} = format as float with 1 decimal place
Store scores for multiple players using a dictionary: player name → top score.
import json # for saving/loading dictionaries as text
# Load leaderboard from file
def load_scores(filename="scores.json"):
try:
with open(filename, "r") as f:
return json.load(f) # JSON → Python dict
except (FileNotFoundError, json.JSONDecodeError):
return {} # empty dict on first run
# Save leaderboard to file
def save_scores(scores, filename="scores.json"):
with open(filename, "w") as f:
json.dump(scores, f, indent=2) # Python dict → JSON
# Usage:
scores = load_scores()
player = "Ahmad"
if player not in scores or score > scores[player]:
scores[player] = score # new high score!
save_scores(scores)
# scores.json on disk looks like:
# {
# "Ahmad": 42,
# "Nurul": 87,
# "Zafri": 15
# }
JSON (JavaScript Object Notation) is a text format that looks almost identical to Python dictionaries and lists. It's the universal language of data exchange — REST APIs, config files, and databases all use it. Python's json module converts between Python objects and JSON text.
import json # add to imports at top
# ── New functions (add above the while loop) ───────────────────
SCORE_FILE = "highscore.txt"
def load_high_score():
"""Load high score from file. Returns 0 if file doesn't exist."""
try:
with open(SCORE_FILE, "r") as f:
return int(f.read().strip())
except (FileNotFoundError, ValueError):
return 0
def save_high_score(score):
"""Save high score to file if it's a new record."""
current_best = load_high_score()
if score > current_best:
with open(SCORE_FILE, "w") as f:
f.write(str(score))
return True # new record!
return False
# ── Startup ────────────────────────────────────────────────────
high_score = load_high_score() # load before the loop
# ── In draw_game() — add high score to display ─────────────────
screen.blit(font.render(
f"Score: {score} Best: {high_score}", True, WHITE), (8, 6))
# ── After the game loop ends ───────────────────────────────────
is_new_record = save_high_score(score)
if is_new_record:
print(f"🎉 New high score: {score}!")
import pygame, random
pygame.init()
GRID_COLS=20; GRID_ROWS=15; CELL_SIZE=30; SNAKE_SPEED=8
WHITE=(255,255,255); GREEN=(0,200,80); DKGREEN=(0,160,50); RED=(220,50,50); BG=(30,30,30)
WINDOW_W=GRID_COLS*CELL_SIZE; WINDOW_H=GRID_ROWS*CELL_SIZE
MOVES={"RIGHT":(1,0),"LEFT":(-1,0),"UP":(0,-1),"DOWN":(0,1)}
SCORE_FILE="highscore.txt"
def load_high_score():
try:
with open(SCORE_FILE,"r") as f: return int(f.read().strip())
except(FileNotFoundError,ValueError): return 0
def save_high_score(score):
best=load_high_score()
if score>best:
with open(SCORE_FILE,"w") as f: f.write(str(score))
return True
return False
def draw_game(screen,snake,food,score,high,font):
screen.fill(BG)
for i,(cx,cy) in enumerate(snake):
pygame.draw.rect(screen,DKGREEN if i==0 else GREEN,
pygame.Rect(cx*CELL_SIZE,cy*CELL_SIZE,CELL_SIZE-2,CELL_SIZE-2),border_radius=4)
fx,fy=food
pygame.draw.circle(screen,RED,(fx*CELL_SIZE+CELL_SIZE//2,fy*CELL_SIZE+CELL_SIZE//2),CELL_SIZE//3)
screen.blit(font.render(f"Score:{score} Best:{high}",True,WHITE),(8,6))
pygame.display.flip()
def update_game(snake,direction,food,score):
hx,hy=snake[0]; dx,dy=MOVES[direction]
hx+=dx; hy+=dy; hx%=GRID_COLS; hy%=GRID_ROWS
new_head=(hx,hy)
if new_head in snake[1:]: return snake,food,score,False
ate=new_head==food; snake.insert(0,new_head)
if ate: score+=1; food=(random.randint(0,GRID_COLS-1),random.randint(0,GRID_ROWS-1))
else: snake.pop()
return snake,food,score,True
screen=pygame.display.set_mode((WINDOW_W,WINDOW_H)); pygame.display.set_caption("Snake")
font=pygame.font.SysFont(None,28); clock=pygame.time.Clock()
high_score=load_high_score(); running=True; direction="RIGHT"
snake=[(10,7),(9,7),(8,7)]; food=(random.randint(0,GRID_COLS-1),random.randint(0,GRID_ROWS-1)); score=0
while running:
for event in pygame.event.get():
if event.type==pygame.QUIT: running=False
if event.type==pygame.KEYDOWN:
if event.key==pygame.K_ESCAPE: running=False
if event.key==pygame.K_UP and direction!="DOWN": direction="UP"
elif event.key==pygame.K_DOWN and direction!="UP": direction="DOWN"
elif event.key==pygame.K_LEFT and direction!="RIGHT": direction="LEFT"
elif event.key==pygame.K_RIGHT and direction!="LEFT": direction="RIGHT"
snake,food,score,running=update_game(snake,direction,food,score)
draw_game(screen,snake,food,score,high_score,font)
clock.tick(SNAKE_SPEED)
if save_high_score(score): print(f"New high score: {score}!")
pygame.quit()
Run the game, get a score of 5, close it. Open highscore.txt in a text editor — what's in it? Run again, get score 3. Does the file change? Get score 10. Does it change now?
In draw_game, add a boolean parameter is_record. If True, draw a flashing "NEW RECORD!" text in gold. Alternate visibility with frame_count % 30 < 15.
Ask for the player's name at startup using name = input("Enter your name: "). Save scores as {"Ahmad":42, "Nurul":87} using the JSON approach. Print the top 3 scores on game over.
These are the errors beginners make most often in Step 8. Read them now so you can recognise them in your own code.
"w" mode when you want to readwith open("high_score.txt", "w") as f:
data = f.read() # error — "w" doesn't allow reading!
"w" opens for writing and immediately erases all existing content. Every run wipes the saved score even before reading it.with open("high_score.txt", "r") as f: # read mode
data = f.read()
saved = f.read() # saved = "42" (a string) if score > saved: # comparing int to str — TypeError!
saved = int(f.read()) # convert string to int first if score > saved:
try/except for the first runwith open("high_score.txt", "r") as f:
high = int(f.read()) # FileNotFoundError on first run!
try/except, the program crashes on every fresh install.try:
with open("high_score.txt", "r") as f:
high = int(f.read())
except FileNotFoundError:
high = 0 # no file yet — start from zero
Three questions — not graded. They help you spot gaps before the activities.
"w"?int(f.read()) rather than just f.read()?f"Score: {score}" produce when score = 42?You've covered all the concepts for Step 8. Time to apply them.
Start Activities → Tier 1 → 2 → 3 → 4