2025, Dec 13 19:00

How to Persist a High Score in Python Turtle: Read Before Write, Compare to Disk, Update Only When Higher

Learn a simple Python fix to persist a game high score: read the stored value, compare, and write only when higher. Prevent overwriting lower scores safely.

Persisting a high score sounds trivial until you realize your program keeps overwriting the stored value even when the current result is worse. The root of the issue is not in file I/O magic, but in what you compare against and when you write. Below is a compact walkthrough of the problem and a straight fix that respects the original behavior while updating the file only when appropriate.

Problem setup

The goal is to keep the highest score between game runs. If the new score is higher, write it to a CSV file; if not, keep the existing value. However, the implementation keeps updating the file even when the new score is lower.

Problematic implementation

The following snippet demonstrates the behavior that leads to unwanted file updates. All names are original in meaning but adjusted for clarity.

from turtle import Turtle
import csv

class ScorePanel:
    def __init__(self):
        self.tally = 0
        self.temp_peak = 0
        self.marker = Turtle()
        self.marker.hideturtle()
        self.marker.penup()
        self.marker.goto(200, 260)
        self.marker.color("white")
        self.render_tally()
        
    def render_tally(self):
        self.marker.clear()
        self.marker.write(f"Score: {self.tally}", align="center", font=("Arial", 20, 'normal'))        
            
    def add_point(self):
        self.tally += 1
        self.marker.clear()
        self.render_tally()
    
    def end_game(self):
        self.sync_highscore()
        self.marker.goto(0, 0)
        self.marker.color("red")            
        self.marker.write(f"Game over!\nYour highest score is: {self.tally}", align="center", font=("Arial", 20, "normal"))
    
    def sync_highscore(self):
        if self.tally > self.temp_peak:
            self.temp_peak = self.tally
            try:
                with open("score.csv", "w") as fh:
                    written = fh.write(str(self.temp_peak))
                    self.marker.write(f"Score: {written}", align="center", font=("Arial", 20, 'normal'))    
            except FileNotFoundError:
                with open("score.csv", 'w') as fh:
                    fh.write(self.tally)
                    self.marker.write(f"Score: {self.tally}", align="center", font=("Arial", 20, 'normal'))
        self.marker.write(f"Score: {self.tally}", align="center", font=("Arial", 20, 'normal')) 

Why it fails

The logic hinges on comparing the current score with a value stored in memory, not with what was previously persisted. On a fresh run, that in-memory value starts from zero, so even a low score appears to be an improvement and triggers a write. Furthermore, writing with "w" is unconditional in the branches that run, so the file content gets replaced regardless of whether the stored value is truly a high score. There are a few additional pitfalls that surface here. The code never reads the existing score before deciding whether to overwrite it, so it lacks the baseline for a correct comparison. Opening a file in "w" mode will attempt to create or truncate it, which makes handling a missing-file scenario in that exact block ineffective. The argument to write must be a string; relying on implicit conversion or writing a number directly will not work. And the file is treated as CSV in name only, while only a single value is stored and read.

The minimal fix

The working approach is straightforward. Read the existing value from the file. Compare the current score to that value. If it is higher, write the new value back; otherwise, leave the stored high score as is. If the file does not exist, initialize it with the current score.

def sync_highscore(self):
    try:
        with open("score.csv", "r") as fh:
            line = fh.readlines()[0]
            stored_peak = int(line)
            if self.tally > stored_peak:                
                with open("score.csv", 'w') as fhw:
                    fhw.write(str(self.tally))
                self.marker.write(f"Game over!\nYour highest score is: {self.tally}", align="center", font=("Arial", 20, "normal"))
            else:
                with open("score.csv", 'w') as fhw:
                    fhw.write(str(stored_peak))
                self.marker.write(f"Game over!\nYour highest score is: {stored_peak}", align="center", font=("Arial", 20, "normal"))
                                 
    except FileNotFoundError:
        with open("score.csv", 'w') as fhw:
            fhw.write(str(self.tally))
            self.marker.write(f"Score: {self.tally}", align="center", font=("Arial", 20, 'normal'))

This sequence enforces the intended rule: only promote the high score when the new value beats the stored one. It also ensures values written to disk are strings and establishes a clear initialization path when the storage file is absent.

Why it’s worth understanding

Conditional persistence is a common pattern that goes well beyond games. Reading before writing helps maintain consistent state and avoid silent regressions. It also underscores how file modes and exception handling interact during file creation and truncation, and why comparing against persisted state rather than ephemeral in-memory placeholders is essential when values must survive process restarts.

Wrap-up

Use the value already on disk as your source of truth, compare your current score to it, and only then decide whether to overwrite. Keep write inputs as strings, and keep I/O pathways simple and explicit. Even if your file is called CSV, treat it according to what you store in it. Following this order of operations eliminates accidental downgrades of the high score and makes the behavior predictable across runs.