Introduction
In our last lesson, you meticulously designed the data structures that will serve as the architectural blueprint for your text-based adventure game. By defining the Player, Room, and Item structs, you've established how the core components of your game world will be represented.
Today, we transition from blueprint to foundation. We will take those designs and give them a physical home by creating the file structure for your capstone project. This involves organizing your code into logical modules—separating declarations in header (.h) files from their implementations in source (.c) files. Just as a complex engineering project is broken down into distinct schematics and sub-assemblies, a well-structured C program is modularized for clarity, reusability, and efficient management. To automate the assembly of these parts, we will write a Makefile, a powerful tool that acts as the set of build instructions for our project.
Why Split a Program into Multiple Files?
Before we start creating files, let's solidify the reasoning behind this essential practice. While it's possible to write an entire program in a single main.c file, this becomes unmanageable for any project of non-trivial size. Splitting your code provides several key advantages:
- Organization: It allows you to group related functions and data structures. For example, all code that manipulates the player's state can reside in
player.c. - Faster Compilation: When you make a change, you only need to recompile the files that were affected. For large projects, this can save a significant amount of time compared to recompiling everything.
- Modularity and Reusability: Well-defined modules with clean interfaces can be reused in other projects. Your
item.c/.hpair, for instance, could be the basis for an inventory system in a future game.
This modular approach is fundamental to professional software development. To explore these concepts further, let's read an article that provides a great overview.
Organizing Code Files in C and C++
This article from Gamedev.net explains the rationale and basic rules for structuring a C/C++ project.
Please read the sections titled Why split code? and How to do it. Pay close attention to the distinction it makes between the interface (what goes in the header) and the implementation (what goes in the source file).
The Role of Header and Source Files
As the article explained, the core principle of a multi-file project is the separation of interface from implementation.
-
Header Files (
.h): These are the public-facing "interfaces" of your modules. They contain the declarations that other parts of your program need to know about. This includes:structdefinitionsenumdefinitionstypedefaliases- Function prototypes (the function's signature without the body)
externdeclarations for global variables
-
Source Files (
.c): These are the private "implementations." They contain the actual executable code and data definitions:- The full bodies of your functions.
- The definitions of any global variables (without the
externkeyword).
Crucially, a source file will typically #include its own corresponding header file. This allows the compiler to check that the function definitions in the .c file match the function declarations in the .h file.
Header Guards
When one header file includes another, you can run into a situation where the same header is included multiple times within a single source file. This leads to the compiler seeing the same struct or function prototype defined more than once, resulting in an error. To prevent this, we use header guards.
Organizing Code Files in C and C++
The same article provides a clear explanation and example of inclusion guards.
Read the section that begins with this paragraph. This is a critical technique that you must use in every header file you create for your capstone project.
The Capstone Project File Structure
Based on these principles, let's establish the file structure for your game. For now, we will create the following files. You can create them as empty files in your project directory; we will add content to them as we proceed.
main.c: The entry point of your program. It will contain themain()function, which will initialize the game and start the main game loop.game.h/game.c: For core game logic, such as the game loop itself, command processing, and state management.player.h: Will contain thetypedeffor yourPlayerstruct.room.h: Will contain thetypedefs for yourRoomandItemstructs.Makefile: The build script to compile and link everything together.
For now, player.c and room.c are not needed, as we haven't written any functions for them yet, but they will be added later.
Your player.h file should look like this to start:
#ifndef PLAYER_H
#define PLAYER_H
// Forward declaration to break the circular dependency between Player and Room.
struct Room;
typedef struct Player {
char name[50];
int health;
int maxHealth;
struct Room* currentLocation;
// We will add the inventory later.
// struct Item* inventory;
} Player;
#endif // PLAYER_H
And your room.h file:
#ifndef ROOM_H
#define ROOM_H
typedef struct Item {
char name[50];
char description[200];
struct Item* next;
} Item;
typedef struct Room {
char name[50];
char description[500];
Item* items;
struct Room* north;
struct Room* south;
struct Room* east;
struct Room* west;
} Room;
#endif // ROOM_H
Automating Compilation with a Makefile
Now that we have multiple files, typing out the gcc command every time becomes tedious and error-prone. This is where the make utility comes in. make reads a file named Makefile that contains a set of rules for building your project.
A Makefile primarily consists of rules in this format:
target: dependency1 dependency2 ...
<tab>command
- target: The file to be created (e.g., an executable or an object file).
- dependencies: Files that the target depends on. If any dependency has been modified more recently than the target, the command will be run.
- command: The shell command to execute to create the target. This line must start with a tab character, not spaces.
To get a practical feel for how this works, let's watch a video that builds up a Makefile from first principles.
Learn to program with c - Part 17 - Makefiles (and a little on multi-file projects)
This video from the "Learn to program with c" series gives a fantastic, step-by-step guide to creating Makefiles.
First, watch the segment on basic syntax from this point. This explains the core target: prerequisites rule and the concept of implicit rules for compiling .c files. Next, see how variables make a Makefile cleaner and more maintainable by watching from this section. Finally, learn about automatic variables like $@ (the target) and $^ (all prerequisites), which make your rules generic and powerful. Watch the explanation from here.
A Makefile for Your Capstone
Putting these concepts together, we can create a robust and scalable Makefile for our adventure game. Create a file named Makefile in your project directory and add the following content. This file will serve you for the rest of the project.

An example of a well-structured Makefile in a code editor, using variables and pattern rules similar to the one we will create.
# Compiler to use
CC = gcc
# Compiler flags:
# -g: Adds debugging information to the executable
# -std=c99: Uses the C99 standard
# -Wall, -Wextra, -Werror: Enables all warnings and treats them as errors
CFLAGS = -g -std=c99 -Wall -Wextra -Werror
# The name of our final executable
TARGET = adventure
# All .c source files in our project
SRCS = main.c game.c
# Object files are derived from source files by replacing .c with .o
OBJS = $(SRCS:.c=.o)
# The default rule, which is the first one in the file.
# 'all' is a conventional name for the rule that builds the main target.
all: $(TARGET)
# Rule to link all object files into the final executable.
# $@ is an automatic variable representing the target (adventure).
# $^ is an automatic variable representing all dependencies ($(OBJS)).
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $@ $^
# A pattern rule for compiling any .c file into a .o file.
# $< is an automatic variable representing the first dependency (the .c file).
%.o: %.c game.h player.h room.h
$(CC) $(CFLAGS) -c -o $@ $<
# A "phony" target to clean up build files.
# It removes the executable and all object files.
clean:
rm -f $(TARGET) $(OBJS)
# Declares that 'all' and 'clean' are not actual files.
.PHONY: all clean
Let's break down this Makefile:
- Variables: We define
CC,CFLAGS,TARGET,SRCS, andOBJSat the top. This makes it easy to change the compiler, add new flags, or add source files later. You'll simply add new file names to theSRCSlist. allTarget: Typingmakein your terminal will run the first rule, which isall. This rule depends on$(TARGET), somakewill then look for the rule to buildadventure.- Linking Rule
$(TARGET): $(OBJS): This rule links all your compiled object files (.o) into the finaladventureexecutable. - Pattern Rule
%.o: %.c ...: This is a powerful feature. It's a generic rule that tellsmakehow to create any.ofile from a corresponding.cfile. We've also made all object files dependent on our header files. This ensures that if you changeplayer.h, all source files will be recompiled correctly. cleanTarget: Typingmake cleanwill execute this rule, deleting all compiled files. This is useful for starting a fresh build..PHONY: This tellsmakethatallandcleanare just names for commands, not actual files to be created.
With this Makefile in your project directory, you can now simply run make to build your game and make clean to clean up.
Conclusion
You have successfully laid the structural foundation of your capstone project. This is a significant step that moves you from abstract design into concrete implementation.
Key Takeaways:
- A multi-file C project is organized by separating interfaces (
.hfiles) from implementations (.cfiles). - Header guards (
#ifndef/#define/#endif) are essential to prevent multiple-inclusion errors during compilation. - You have established a logical file structure for your game:
main.c,game.h/.c,player.h, androom.h. - A
Makefileautomates the entire build process, managing dependencies and commands to compile and link your project efficiently. - You now have a professional, scalable
Makefilethat uses variables and pattern rules, which will serve you for the remainder of the project.
In our next lesson, we will begin to breathe life into this structure. We'll implement the main game loop in game.c, getting to the point where your program can start, display a welcome message, and await user input. The foundation is built; next, we raise the walls.