Tower Defense Game: Finite State Automata / State Machines

Complex codebases – and games are usually complex – tend to rely on a lot of state, usually captured in variables. Navigating from one screen in a game to another involves a lot of change: you need to render different things; the key bindings you use may change also; and perhaps you need to clear out old objects, like if you change from game play to the score screen.

But instead of having an ever-increasing number of variables to represent what your code is supposed to do – like, is_in_menu, has_won_game, is_in_level_editor, etc. – you should consider formalizing your stateful code using finite state automata, or commonly called a state machine.

To improve our code’s extensibility, it’s time to consider how we can effectively use a simple state machine to represent the state the game is in, and how OOP and inheritance can help with separation of concerns.

What is Statefulness?

All useful computer programs capture some form of state. In a toy application that asks for your name and repeats it back, it could be a variable called name that makes our application stateful — that is, our program is storing, and can recall at will, information we either gave it explicitly, or that it generated implicitly, by perhaps reading something from a file or database. Regardless of how, we usually store this in variables, or more generically, in memory somewhere, given whatever loose definition of memory we may choose to use.

So why is statefulness important? Because it’s all too easy to ensnare important business logic in simple variables, and add layer upon layer of information of top of that.

Booleans everywhere

Consider an attempt to write a simple game with a handful of screens and how you’d track what screen it’s on:

  1. A main menu that you first encounter when you start the game, or quit out to when you exit from the game play or map editor.

  2. The main game where you play the actual game

  3. A win and loss screen that records your score and how well you did

  4. The level editor screen

  5. The configuration / options screen

And so on.

Naively, you could store what your game’s currently supposed to display to the user with an endless number of booleans:

in_main_menu = True
in_game_playing = False
in_score_screen = False
in_level_editor = False
in_options_screen = False
# ... etc ...

So, every time you switch from one screen (and functional part of the game) to another you must remember to update all those boolean flags.

If you accidentally set two to True your game will most probably break in awkward ways. You might render two different things on the screen. Furthermore, you may have a set of paths that a user must navigate through to get to certain screens; for instance, going from the main menu to the score screen is usually not possible.

That’s not to say the boolean approach is bad. Boolean variables are very useful, and the demo makes great use of them.

But there’s a way of capturing the state of a game in a manner that is easier to reason about.

Finite State Automata / State Machines

Finite State Automata is just one facet of the model of computation in Computer Science. We won’t get into the weeds of that at all, as the concept of state machines is very intuitive to most people, and especially to programmers, who often end up creating state machines without necessarily knowing that it’s a formal discipline.

Example State chart for the tower defense game
An example of the possible transitions our game is able to make. Each box represents a state and the directed arrows the possible paths.

You’re probably familiar with flowcharts, like the included example. Well, that’s a state machine. Starting at the roundel, you can transition from one block to the next by following the direction of the arrows. The example closely mirrors the state machine used in the demo.

It also shows where and what you can or cannot transition to. Though, for this game that is mostly intuitive without a chart, for very large games or applications you’d want a tool capable of drawing them to the screen.

In our case, we can keep it simple. I do not enforce legal transitions in most places in the demo, except where it’s vital to prevent crashes or other serious issues, but in a larger application you definitely want to do that!

Consider an ecommerce website. You’d want to ensure the customer first goes through the has_paid state before the ecommerce system updates to ship_merchandise! A large number of logic errors in applications are directly attributable to this sort of mistake.

You can represent states in many ways. I think the simplest way to represent machine states in Python is using the Enum class from the enum module.

The Enum class

import enum

class GameState(enum.Enum):
    """
    Enum for the Game's State Machine. Every state represents a
    known game state for the game engine.
    """

    # Unknown state, indicating possible error or misconfiguration.
    unknown = "unknown"
    # The state the game engine would rightly be set to before
    # anything is initialized or configured.
    initializing = "initializing"
    # The game engine is initialized: pygame is configured, the sprite
    # images are loaded, etc.
    initialized = "initialized"
    # The game engine is in map editing mode
    map_editing = "map_editing"
    # The game engine is in game playing mode
    game_playing = "game_playing"
    # The game engine is in the main menu
    main_menu = "main_menu"
    # The game engine is rendering the game ended screen.
    game_ended = "game_ended"
    # The game engine is exiting and is unwinding
    quitting = "quitting"


class StateError(Exception):
    """
    Raised if the game is in an unexpected game state at a point
    where we expect it to be in a different state. For instance, to
    start the game loop we must be initialized.
    """

You might be tempted to skip this step and just use strings. Avoid that temptation. Strings are useful and they’re perfectly capable of representing information important to your application, but they lack some of the features of enums:

Enums record a name and associated value for each member

An Enum class is just like a regular class. You can write docstrings and add methods. Each enum also records the name (the left-hand side) and its constituent value (the right-hand side)

Each enum element remembers both its name and value properties:

>>> GameState.starting
<GameState.starting: 'starting'>
>>> GameState.starting.value
'starting'
>>> GameState.starting.name
'starting'

They know about valid and invalid names, and you can create an enum element from its value:

>>> GameState("bad name")
ValueError: 'bad name' is not a valid GameState
>>> GameState("game_ended")
<GameState.game_ended: 'game_ended'>
Enums are typed, and aid with code completion and type hinting

So if you declare a variable or argument as GameState your editor will help you code complete.

Enums are iterable and support membership testing

So you can use them in loops to capture all the elements:

>>> list(GameState)
[<GameState.unknown: 'unknown'>,
 <GameState.starting: 'starting'>,
 <GameState.initialized: 'initialized'>,
 <GameState.map_editing: 'map_editing'>,
 <GameState.game_playing: 'game_playing'>,
 <GameState.main_menu: 'main_menu'>,
 <GameState.game_ended: 'game_ended'>,
 <GameState.quitting: 'quitting'>]

And check for membership:

>>> GameState.map_editing in GameState
True
Enums are symbolic

This is the most important thing to understand. Enums represent a symbol — in GameState each value is a string, but it could be an int or some other primitive type, also. But usually the exact value is not important, only that it’s the same and consistent everywhere. And that’s really the thing to keep in mind. You’re not passing around a string, or an integer, but a symbol (GameState) with a name and, yes, a value.

That means all the usual conditional checks you want to do work perfectly. And if you use IntEnum you gain the advantage of the enum elements behaving like numbers, meaning >, <, and so on, also work.

This course will make heavy use of enums to represent things that’re symbolic.

Putting it all together

Now we can take our new-found knowledge and extend TowerGame, from the last chapter, with a basic state machine.

@dataclass
class TowerGame:
    ...
    state: GameState
    ...

    @classmethod
    def create(cls, ...):
        return cls(..., state=GameState.initializing)

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

    def assert_state_is(self, *expected_states: GameState):
        """
        Asserts that the game engine is one of
        `expected_states`. If that assertions fails, raise
        `StateError`.
        """
        if not self.state in expected_states:
            raise StateError(
                f"Expected the game state to be one of {expected_states} not {self.state}"
            )

    def start_game(self):
        self.assert_state_is(GameState.initialized)
        self.set_state(GameState.main_menu)
        self.loop()

    def loop(self):
        while self.state != GameState.quitting:
            if self.state == GameState.main_menu:
                # pass control to the game menu's loop
            elif self.state == GameState.map_editing:
                # ... etc ...
            elif self.state == GameState.game_playing:
                # ... etc ...
        self.quit()

    def init(self):
        self.assert_state_is(GameState.initializing)
        ...
        self.set_state(GameState.initialized)

Here I’ve added a couple of helper functions to aid with the state transition stuff. We can now assert, whenever we like, that the game state is one or more known states. That way start_game will error out if we call it before calling init. And init itself won’t run if it is not in its GameState.initializing state.

This means we can now, finally, write out some of our main loop code also: its while loop will keep spinning as long as we’re not in a quitting state. Presently, the loop will check if we’re in one of a number of game states and – though it’s not written yet, as we’ve not gotten that far yet – hand off control to another part of our game’s code.

Why do it like this? Because this loop is a controller. Its aim is not to do any of the heavy lifting of drawing stuff to the screen, nor should it handle keyboard input per se. You could definitely make it do that: you have a screen to draw on, and you can listen to keyboard and mouse events also. So why not do it like that? Well:

Every game state represents vastly different requirements

Consider a main menu. We want – as we’re hewing somewhat close to the demo – a menu of items and maybe some fancy graphics effects and a logo to show off our cool game. But that’s… not what we want in the map editor. In fact, it’s completely different from the main menu.

So how would you deal with two very conflicting requirements? With if statements. Lots of them. And don’t forget, every single object, sprite, or asset you want to draw to the screen, needs to be accessible from TowerGame. So you’ll end up with a level attribute to store level details for the map editor; a menu_group for the menu items you render when you’re in a main menu; the score and hud for the game play itself.

You’ll end up with dozens of different things that only apply in some circumstances, and only in some game states.

Instead of submitting ourselves to what will be a messy development experience, we’ll instead create a class structure that represents everything we need to draw stuff to the screen; handle keyboard and mouse inputs; and so on, and in a nice, clean and easy-to-understand structure.

The demo’s state machine is basic, but you could do a few things to improve it, if you wish:

Enforcing only legitimate transitions

Currently, you can call set_state with any state you like, even if it does not make sense to do so. Like reverting back to initializing after it’s already initialized. You could extend it to check and enforce that only valid state transitions from the current state is possible. That way your game will error out if you send it to the wrong state. It’s a useful way to catch serious bugs in larger codebases.

Separating the state machine into a new class

Instead of integrating it into TowerGame you could create a standalone class that also takes a state enum class to use as its source of transitions instead of hardcoding it, like I’ve done in TowerGame.

Next Steps

We have a skeleton class that can initialize our game, and we have a state machine capable of tracking its current state, and also enforcing that it must be in certain states before proceeding.

There’s a lot more to do there, if you wish, but that’s enough to get us started.

Now we need to build a simple class structure that lets us represent a game screen – and thus a unique game state – and why we’d want to do that. After that, it’s time to start building the game!

Summary

State Machines are useful abstractions

They’re very useful for things that require ordering – initialize first, then show the main menu, for instance – but they are not a panacea. Sometimes a boolean or two is easier to reason about; and you can end up with far too many states that do nothing at all, or are too granular.

It’s a balancing act, so think of state machines are another tool in your toolbox.

Enums represent symbolic values

They have an actual value, and a Python-friendly attribute name, but their main purpose is to remove any ambiguities about what you’re passing around between functions. Strings lose context quickly and require fastidious updating if you change them.

Liked the Article?

Why not follow us …

Be Inspired Get Python tips sent to your inbox

We'll tell you about the latest courses and articles.

Absolutely no spam. We promise!