Introduction
In our last lesson, we focused on structuring complex data by nesting structs, allowing us to model "has-a" relationships like a Player having a Weapon. This is fundamental for organizing the data in our game world. Today, we turn our attention to organizing the game's behavior and flow.
This lesson will introduce you to enums, or enumerated types. As previewed previously, enums provide a clean and robust way to define a set of named integer constants. We will use them to manage the overall state of our game—for example, transitioning from a MAIN_MENU to PLAYING, and finally to a GAME_OVER screen. By the end of this session, you will be able to implement a simple but powerful state machine, a core concept in programming that will bring a new level of structure and control to your game logic.
Why Do We Need to Manage States?
As a game grows, its logic can become a tangled mess. Imagine trying to control the game's flow using a series of boolean flags: bool isInMenu = true;, bool isPlaying = false;, bool isPaused = false;. Your main game loop would become a complex web of if-else statements, difficult to read, debug, and expand.
// The "ugly" way
while (!isGameDone) {
if (isInMenu) {
// process menu input
// if player clicks 'play', set isInMenu = false, isPlaying = true
} else if (isPlaying) {
// run game logic
// if player dies, set isPlaying = false, isGameOver = true
} else if (isGameOver) {
// show game over screen
}
// ... and what about a pause screen? more flags, more ifs...
}
This approach is brittle. What happens if, by mistake, both isInMenu and isPlaying are true? The behavior becomes unpredictable. We need a more structured way to define and transition between the distinct phases of a game.
Modeling Behavior with State Machines
A more robust solution is to model the game's flow as a state machine. A state machine is a system that can be in one of a finite number of states at any given time. It transitions from one state to another in response to specific events or conditions.
Consider a simple game of chess. The game is always in a specific state, and transitions occur based on player moves.

This diagram clearly shows the possible states (White's turn, Black's turn, Draw, etc.) and the transitions that move the game from one state to another (White moves, Checkmate). Our goal is to implement this kind of logical structure in C.
The C Solution: enum
The C language provides the perfect tool for this job: the enum. An enum allows us to define a custom type that consists of a set of named integer constants. Instead of using arbitrary numbers like 0 for the menu and 1 for playing, we can use descriptive names.
Let's look at the basic syntax and a practical game state example.
Mastering C Enums: Cleaner Code with Named Constants ...
This article provides a concise introduction to the syntax and a perfect example of using enums with a switch statement for game states.
First, read the definition to understand the basic syntax and how the compiler automatically assigns integer values. Then, focus on the section Enum in Switch Statements and study the accompanying code block, which demonstrates the exact pattern we want to implement.
As you saw in the reading, the enum gives us readable, self-documenting code. Under the hood, the compiler treats MENU as 0, PLAYING as 1, and GAME_OVER as 2. But in our code, we work with the meaningful names, which is far less error-prone.
The combination of an enum for the states and a switch statement in the main loop to handle the logic for the current state is the foundational pattern for state management in C.
#include <stdio.h>
// 1. Define the possible states using an enum
enum GameState {
STATE_MENU,
STATE_PLAYING,
STATE_GAME_OVER
};
int main() {
// 2. Create a variable to hold the current state
enum GameState currentState = STATE_MENU;
int isRunning = 1;
// 3. The main game loop
while (isRunning) {
// 4. Switch on the current state to execute the correct logic
switch (currentState) {
case STATE_MENU:
printf("In the Main Menu. Press 'p' to play.\n");
// In a real game, we'd get user input.
// For this example, let's just simulate it.
// char input = get_user_input();
// if (input == 'p') {
currentState = STATE_PLAYING;
// }
break;
case STATE_PLAYING:
printf("Playing the game... Player health is low!\n");
// Game logic runs here.
// Let's simulate the player losing.
currentState = STATE_GAME_OVER;
break;
case STATE_GAME_OVER:
printf("Game Over! Press 'q' to quit.\n");
// Simulate quitting
isRunning = 0;
break;
}
}
printf("Exiting game.\n");
return 0;
}
A Key Advantage: Compiler Warnings
One of the most powerful features of using enums with switch statements is that the compiler can help you write more robust code. If you enable all warnings (a practice you should always follow with -Wall -Wextra), the compiler can warn you if you've forgotten to handle a specific state in your switch block. This is an invaluable safety net.
The following video demonstrates this feature clearly.
Your Thirteenth Day in C (enums and switch statements) - Crash Course in C Programming
This video by Mike Shah demonstrates a key practical advantage of using enums with switch statements.
Watch the segment from enums and switch statements. Pay close attention to the part where he adds the -Wall compiler flag and gets warnings about unhandled enum cases. This illustrates how enums help you write more robust and complete code.
This compiler assistance makes your code far more maintainable. When you add a new state to your enum (e.g., STATE_PAUSED), the compiler will immediately point out all the switch statements you need to update.
A More Professional Pattern: State Lifecycle
The simple switch in the main loop is a great start, but it has an efficiency problem: the code inside each case runs on every single frame. For example, printf("In the Main Menu...") would be called 60 times per second! We only need to draw the menu once when we enter the state.
A more professional pattern treats each state as having its own lifecycle:
- Init: Code that runs once when the state begins (e.g., loading assets, drawing the UI).
- Update: Code that runs every frame while in that state (e.g., checking for input, moving a character).
- Cleanup: Code that runs once when the state ends (e.g., freeing memory, clearing the screen).
This is achieved by giving each state its own function, which contains its own internal loop. The main switch statement just calls the correct state function once.
This is a crucial concept for writing performant games, and the following tutorial explains it perfectly.
This tutorial, while for a specific SDK, presents a universally applicable and robust C pattern for managing game states.
First, read the sections Setting up and Creating States to see how an enum and a switch statement form the core of the state manager. Then, critically, read the State Structure section. This explains the inefficiency of the simple approach and introduces the advanced init/update/cleanup pattern using a while loop within each state function. This is a key technique for writing efficient game loops.
This init/update/cleanup pattern is a significant step up. It encapsulates all logic for a given state within its own function, leading to much cleaner, more organized, and more efficient code—exactly the kind of robust structure needed for your capstone project.
A Note on C++: enum class
As you progress on your game development journey, you will eventually move to C++. It's useful to know that C++ improves upon C's enum with a feature called enum class. Standard C enums place their member names (like MENU, PLAYING) into the global namespace, which can lead to naming conflicts. C++'s enum class is "strongly typed" and "scoped," meaning you must refer to the members through the enum's name (e.g., GameState::PLAYING).
You don't need to master this now, but seeing the difference will deepen your understanding of C's behavior and prepare you for the future.
C++/Game Tutorial 38: Game States, enums, enum Classes!
This video is for C++, but it clearly illustrates a key difference you should be aware of for your future studies.
Watch the section on enum class. The key concept is that C++'s enum class forces you to use the scope resolution operator (e.g., GameStates::Options), which prevents naming conflicts. Standard C enums do not have this feature, so their names are global. This is a point of comparison, not a feature you can use in C.
Conclusion
In this lesson, you've learned how to impose order on your game's logic using enums to build a state machine. This is a powerful technique that moves you away from fragile if-else chains and toward a clean, scalable, and maintainable architecture.
Key Takeaways:
- Enums for Clarity:
enumprovides meaningful, readable names for sets of integer constants, making code self-documenting. - State Machines for Flow: Modeling your game's logic as a set of states and transitions is a robust design pattern.
enum+switch: This combination is the classic C pattern for implementing a state machine.- Compiler as Your Ally: Using
enumsallows the compiler to warn you about unhandled states, preventing bugs. - State Lifecycle Pattern: For efficient code, structure each state with its own init, update, and cleanup phases, typically inside a dedicated function with its own loop.
In the last two lessons, we've defined game data with struct Player and struct Weapon, and game flow with enum GameState. However, typing struct Player and enum GameState repeatedly can be cumbersome. In our next lesson, we will explore the typedef keyword, a simple but powerful feature that lets us create convenient aliases for these complex types, further cleaning up our code.