Refactoring Python: Building a Scalable Game Engine Architecture

ArjanCodes////4 min read

Overview of Modern Refactoring

Code refactoring often feels like untangling a massive knot. In this exploration, we tackle a Tower Defense Game written in Python that, while functional and entertaining, suffers from common architectural pitfalls: global variables, wildcard imports, and extreme coupling. These issues make the code brittle and nearly impossible to test or extend. By restructuring the logic into a generic game engine, we can separate the core loop mechanics from the specific rules of a tower defense scenario, creating a more professional and maintainable codebase.

Prerequisites for Architectural Improvement

Refactoring Python: Building a Scalable Game Engine Architecture
Refactoring A Tower Defense Game In Python // CODE ROAST

To follow this refactor, you should possess a solid grasp of Python fundamentals, including classes and inheritance. Familiarity with Tkinter for basic GUI rendering is helpful, though the goal is to make the engine agnostic of specific libraries. You should also understand the concept of a game loop—the continuous cycle of updating logic and rendering frames that keeps a simulation running.

Key Libraries & Tools

  • Tkinter: Python's standard GUI library used here for the canvas and main event loop.
  • Enum: Part of the standard library used to define discrete game states.
  • Typing: Used for structural subtyping, allowing us to define interfaces for game objects without strict inheritance.
  • Tab9: An AI-driven code completion tool that assists in writing boilerplate and suggesting patterns during the refactoring process.

Code Walkthrough: Modularizing the Engine

1. The Generic Game Class

We start by stripping the Tower Defense Game of its specific logic to create a reusable Game base class. This class manages the window, canvas, and the timing of the loop.

import tkinter as tk

class Game:
    def __init__(self, title: str, width: int, height: int, time_step: int = 50):
        self.root = tk.Tk()
        self.root.title(title)
        self.canvas = tk.Canvas(self.root, width=width, height=height)
        self.canvas.pack()
        self.time_step = time_step
        self.running = False
        self.objects = []

    def add_object(self, obj):
        self.objects.append(obj)

    def _run(self):
        if not self.running: return
        self.update()
        self.paint()
        self.root.after(self.time_step, self._run)

    def run(self):
        self.running = True
        self._run()
        self.root.mainloop()

By moving the title, width, and height into the initializer, we remove the need for global constants. The _run method handles the recursion safely, checking the running boolean before scheduling the next frame.

2. Defining Game Objects with Protocols

Instead of forcing every object to inherit from a massive base class, we use Typing to define what a game object looks like. This is structural subtyping; if a class has update and paint methods, the engine accepts it.

from typing import Protocol

class GameObject(Protocol):
    def update(self) -> None: ...
    def paint(self, canvas: tk.Canvas) -> None: ...

3. Decoupling with Game States

Direct communication between objects—like a button telling a wave generator to start—creates spaghetti code. We solve this by introducing an Enum to track the state of the Tower Defense Game.

from enum import Enum, auto

class GameState(Enum):
    IDLE = auto()
    WAITING_FOR_SPAWN = auto()
    SPAWNING = auto()

# In the specific Tower Defense subclass
class TowerDefenseGame(Game):
    def __init__(self, ...):
        super().__init__(...)
        self.state = GameState.IDLE

    def set_state(self, new_state: GameState):
        self.state = new_state

Now, the button simply updates the state to WAITING_FOR_SPAWN. The WaveGenerator watches this state during its own update cycle. Neither object needs to know the other exists.

Syntax Notes: Pythonic Enhancements

Python offers unique syntax that can significantly clean up conditional logic. For instance, coordinate checking for buttons can be written as a chained comparison: self.x <= x <= self.x2. This mimics mathematical notation and is far more readable than multiple and statements. Furthermore, in Python 3, super() calls no longer require explicit class names, and classes do not need to inherit from object explicitly. These small changes reduce boilerplate and modernize the feel of the codebase.

Practical Examples

This engine architecture is applicable far beyond tower defense. Any simulation requiring a fixed update rate—such as a physics engine, a cellular automata visualization (like Conway's Game of Life), or a simple RPG—can use the Game base class. By swapping the GameObject list, you can change the entire behavior of the application without touching the core loop logic. This is the foundation of professional game development: the engine provides the "how," while the game objects provide the "what."

Tips & Gotchas

One common mistake when refactoring is neglecting the order of operations. In the paint method, the order in which you iterate through self.objects determines the Z-index (layering) on the screen. Always add background elements like the map first, and UI elements like the mouse cursor last.

Another "gotcha" involves modifying lists while iterating over them. If a projectile removes itself from the game during its update call, a standard index-based loop will skip the next item or crash. Use a list comprehension or a copy of the list for safer iteration: for obj in self.objects[:] or use a specialized removal queue to be processed at the end of the frame.

Topic DensityMention share of the most discussed topics · 13 mentions across 7 distinct topics
Python
23%· languages
Tower Defense Game
23%· products
Tkinter
15%· libraries
Typing
15%· libraries
Enum
8%· libraries
Other topics
15%
End of Article
Source video
Refactoring Python: Building a Scalable Game Engine Architecture

Refactoring A Tower Defense Game In Python // CODE ROAST

Watch

ArjanCodes // 36:49

On this channel, I post videos about programming and software design to help you take your coding skills to the next level. I'm an entrepreneur and a university lecturer in computer science, with more than 20 years of experience in software development and design. If you're a software developer and you want to improve your development skills, and learn more about programming in general, make sure to subscribe for helpful videos. I post a video here every Friday. If you have any suggestion for a topic you'd like me to cover, just leave a comment on any of my videos and I'll take it under consideration. Thanks for watching!

What they talk about
AI and Agentic Coding News
Who and what they mention most
Python
27.3%3
Python
18.2%2
Python
18.2%2
4 min read0%
4 min read