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_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.
Consider an attempt to write a simple game with a handful of screens and how you’d track what screen it’s on:
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.
The main game where you play the actual game
A win and loss screen that records your score and how well you did
The level editor screen
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:
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.
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
The Enum class
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
They know about valid and invalid names, and you can create an enum element from its value:
- Enums are typed, and aid with code completion and type hinting
So if you declare a variable or argument as
GameStateyour 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:
And check for membership:
- Enums are symbolic
This is the most important thing to understand. Enums represent a symbol — in
GameStateeach 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
nameand, yes, a
That means all the usual conditional checks you want to do work perfectly. And if you use
IntEnumyou 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.
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 itself won’t run if it is not in its
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
ifstatements. 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
levelattribute to store level details for the map editor; a
menu_groupfor the menu items you render when you’re in a main menu; the
hudfor 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_statewith any state you like, even if it does not make sense to do so. Like reverting back to
initializingafter 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
TowerGameyou 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
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!
- 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.