Code of the Day
IntermediateGame Architecture

Game state machines

A state machine names each mode of play and enforces explicit transitions — turning a tangle of if/elif chains into a structure you can reason about.

Game DevIntermediate7 min read
By the end of this lesson you will be able to:
  • Define a game state machine with title, playing, paused, and game-over states
  • Explain why centralising state transitions prevents hard-to-reproduce bugs
  • Identify which variables belong to global scope versus per-state scope

A beginner game tends to start clean: one while running loop, a handful of variables, and simple if/elif branches to handle different situations. That design works until you try to add a title screen, a pause menu, or a game-over screen. At that point the single loop grows a second set of if/elif arms, then a third, and the interactions between them become hard to track. Changing the pause logic can accidentally break the game-over logic. New states mean touching code that was already working.

The root problem is that the loop is handling several different modes of play with a single control flow. A state machine solves this by giving each mode a name and defining exactly which transitions between modes are allowed.

States and transitions

A typical arcade-style game has four states:

TITLE ──────────────────▶ PLAYING

                   ◀──────────┤ (ESCAPE key)
                   ▶──────────┤ (ESCAPE key / resume)
               PAUSED         │
                              │ (health == 0 / reached goal)

TITLE ◀─────────────────  GAME_OVER
  • TITLE: show the title screen and wait for the player to press a key.
  • PLAYING: run the game loop — input, update physics, render world.
  • PAUSED: freeze all game logic; show the pause overlay; resume or quit.
  • GAME_OVER: show the result screen; wait for the player to restart.

Each transition is intentional. You cannot go from TITLE directly to PAUSED. You cannot go from GAME_OVER to PAUSED. Making the valid transitions explicit means an invalid one simply cannot happen, which eliminates an entire class of bugs.

What goes where

Naming the states also clarifies which variables belong to which scope.

Global (lives as long as the program): screen, clock, font, current_state. These exist before any state activates and after all states end.

Per-state (lives as long as that state is active): player_rect, score, enemy_list, level_timer. These are created when PLAYING starts and discarded when it ends. If the player restarts, fresh state is created from scratch — no stale values from the previous run can leak through.

This separation makes restart trivial: switching from GAME_OVER back to PLAYING just creates new per-state variables. It also makes the pause state safe: PAUSED does not update the per-state variables at all, so the world is frozen in place.

The naive alternative

Without a state machine you write code like this:

if state == "title":
    draw_title()
    if any_key_pressed:
        state = "playing"
elif state == "playing":
    update_game()
    draw_game()
    if player_dead:
        state = "game_over"
    if paused:
        state = "paused"
elif state == "paused":
    draw_pause_overlay()
    # ...50 more lines
elif state == "game_over":
    draw_game_over()
    # ...

This works, but every state's logic is inline in one enormous loop. Adding a settings screen means adding another elif. Changing what happens on player death means searching through the "playing" block. Tests for any one state require constructing the entire loop context.

The state machine approach, covered in the next lesson, extracts each block into its own object with a clean interface. The main loop shrinks to a handful of lines that never need to change as you add new states.

State machines are not unique to games. UI frameworks, network protocols, and compilers all use them for the same reason: when a system has distinct modes of behaviour with defined transitions between them, a state machine is the natural way to model it.

Where to go next

Next: scene management — implementing the state machine as a Scene base class and a SceneManager that swaps between them.

Finished reading? Mark it complete to track your progress.

On this page