Unit 12 · lesson

Refactor the Player Roster Into Objects

Core path: 35 minutes

The dictionary-based roster works. The question now is whether the code has reached a point where the player's data and behavior belong together behind a clearer boundary.

This is a refactor, not a rewrite. Preserve the behavior while changing the structure.

Decide what the Player model should own

The Player object owns player-specific state and behavior:

name
score
rank
add_score()
determine_rank()

It should not automatically own everything that happens near a player.

These responsibilities still belong elsewhere:

saving JSON
displaying menus
loading the whole roster
searching application-wide collections

That boundary is part of the lesson.

Create player.py

Build a Player class with an initializer and behavior:

class Player:
    def __init__(self, name, score):
        self.name = name
        self.score = score
        self.rank = self.determine_rank()

    def determine_rank(self):
        # use your score rules
        ...

    def add_score(self, points):
        self.score += points
        self.rank = self.determine_rank()

Test one object independently:

player = Player("Nova", 850)
player.add_score(100)
print(player.score, player.rank)

Expected state:

950 ELITE

Convert objects into storage-friendly data

JSON does not know what a custom Player instance means by itself.

Add:

def to_dict(self):
    return {
        "name": self.name,
        "score": self.score,
    }

If rank is always derived from score, decide whether storing both values would create two possible sources of truth.

Update storage in both directions

Saving becomes:

Player objects
    ↓ to_dict()
plain dictionaries
    ↓ json.dump(...)
JSON file

Loading reverses the process:

JSON file
    ↓ json.load(...)
plain dictionaries
    ↓ Player(...)
Player objects

That conversion boundary matters. The file contains data, not live Python objects.

Update the rest of the application

Dictionary access such as:

player["name"]

becomes attribute access:

player.name

Update display, search, and coordination code accordingly.

Decide whether presentation belongs on the object itself. For this project, keeping display_player() in display.py gives us a cleaner separation between the model and console presentation.

Inspect self with the debugger

Set a breakpoint inside add_score().

Step through:

self.score += points
self.rank = self.determine_rank()

Watch self.score and self.rank change.

The word self is not magic. It is the particular instance whose method is currently executing.

Create two players and prove that changing one does not change the other.

Keep the Git history readable

Good checkpoints might be:

Create Player model
Add Player serialization
Update storage for Player objects
Update display and search

Inspect each diff before committing.

Verify the refactor end to end

The finished application should still let you:

  • add a player;
  • change a score;
  • update rank;
  • save;
  • quit;
  • reload; and
  • display the same logical roster.

If the object architecture looks elegant but the old behavior disappeared, the refactor is not complete.

Objects are useful when they clarify ownership. They are not a prize you earn for making the code more complicated.