Engine Overview
An introduction to the generic engine classes we'll use to create the game
In this series, we'll build a fully functional Minesweeper game from scratch, giving you hands-on experience with game development concepts and C++ programming.
We'll separate our project into two main parts:
- An "Engine" module containing components that are generally useful across a wide range of projects, not specific to Minesweeper.
- A Minesweeper-specific module that builds upon our engine to create the actual game.
For example, we'll create a general Button
class in our engine that can be used across various projects. The cells of our Minesweeper grid will then inherit from this Button
class, expanding it with Minesweeper-specific logic such as whether the cell contains a bomb and counting the number of bombs in adjacent cells.
This separation offers several benefits:
- We can reuse the engine code across other projects, saving time and effort in future game development.
- It provides a logical separation of concerns, allowing us to keep our classes smaller and more focused.
- As we add new features to our project, that generally involves adding new methods and variables to our classes. By approaching this thoughtfully, we can add generally useful methods to the base engine classes rather than the specific Minesweeper classes.
Over time, this approach will make our generic engine code more powerful, which future projects can also benefit from. It's a great way to build a personal library of reusable game components.
In this lesson, we'll introduce all of the engine code. While it might seem like a lot of code at first, you'll find that it's all built on concepts we've covered in previous sections of the course. Don't worry if you feel overwhelmed - future lessons will slow down and take things step by step as we build the new, Minesweeper-specific functionality.
Let's dive in and explore our starting point
The Globals.h
File
We'll start by creating a Globals.h
header file to store variables and functionality that are useful across a wide variety of files in our project. This includes:
- Custom SDL event types, which we'll store in the
UserEvents
namespace. This namespace is currently empty, but we'll add to it as needed in future parts of the tutorial. - General configuration options that we might want to change that are stored in the
Config
namespace. This includes settings like the game name, window dimensions, and color schemes. - Generally useful free functions that can be used across various files that are stored in the
Utils
(utilities) namespace.
An important feature to note is the SHOW_DEBUG_HELPERS
definition. This definition enables extra functionality that will be useful when we're developing and debugging the project. For example, it allows us to use the Utils::CheckSDLError
function to print detailed error messages during development.
By organizing our global definitions and utilities in this way, we create a central place for important constants and helper functions, making our code more organized and easier to maintain.
#pragma once
#define SHOW_DEBUG_HELPERS
#include <iostream>
#include <SDL.h>
#include <string>
namespace UserEvents{}
namespace Config{
// Game Settings
inline const std::string GAME_NAME{
"Minesweeper"};
// Size and Positioning
inline constexpr int WINDOW_HEIGHT{200};
inline constexpr int WINDOW_WIDTH{400};
// Colors
inline constexpr SDL_Color BACKGROUND_COLOR{
170, 170, 170, 255};
inline constexpr SDL_Color BUTTON_COLOR{
200, 200, 200, 255};
inline constexpr SDL_Color BUTTON_HOVER_COLOR{
220, 220, 220, 255};
// Asset Paths
inline const std::string FONT{
"Rubik-SemiBold.ttf"};
}
namespace Utils{
#ifdef SHOW_DEBUG_HELPERS
inline void CheckSDLError(
const std::string& Msg){
const char* error = SDL_GetError();
if (*error != '\0') {
std::cerr << Msg << " Error: " << error <<
'\n';
SDL_ClearError();
}
}
#endif
}
Note that the CheckSDLError()
function in this file is something we created in our earlier lesson on error handling:
Detecting and Managing Errors
Discover techniques for detecting and responding to SDL runtime errors
The Engine/Window.h
File
The first component of our engine is the Window
class, which is responsible for creating and managing SDL_Window
objects. This class, like all our engine components, is stored within the Engine
namespace and located in the Engine/
directory of our project.
The Window
class provides the following functionality:
- A constructor that creates an SDL window using the configurations defined in our
Globals.h
file. - A
Render
method that fills the window with the background color. - An
Update
method that refreshes the window surface. - A
GetSurface
method that returns the SDL surface associated with the window.
This class encapsulates all the basic window management functionality we'll need for our game, providing a clean interface for creating and manipulating our game window:
#pragma once
#include <SDL.h>
#include "Globals.h"
namespace Engine{
class Window {
public:
Window(){
SDLWindow = SDL_CreateWindow(
Config::GAME_NAME.c_str(),
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
Config::WINDOW_WIDTH,
Config::WINDOW_HEIGHT,
0
);
}
void Render(){
SDL_FillRect(
GetSurface(), nullptr,
SDL_MapRGB(GetSurface()->format,
Config::BACKGROUND_COLOR.r,
Config::BACKGROUND_COLOR.g,
Config::BACKGROUND_COLOR.b));
}
void Update(){
SDL_UpdateWindowSurface(SDLWindow);
}
SDL_Surface* GetSurface(){
return SDL_GetWindowSurface(SDLWindow);
}
~Window() {
if (SDLWindow && SDL_WasInit(SDL_INIT_VIDEO)) {
SDL_DestroyWindow(SDLWindow);
}
}
Window(const Window&) = delete;
Window& operator=(const Window&) = delete;
private:
SDL_Window* SDLWindow;
};
}
This Window
class is similar to what we created in our earlier lesson on SDL windows:
Creating a Window
Learn how to create and customize windows, covering initialization, window management, and rendering
The Engine/Rectangle.h
File
The Rectangle
class is a fundamental building block in our engine, used for drawing rectangles on the screen. It provides the following key features:
- A constructor that takes position (x, y), dimensions (width, height), and an optional color.
- A virtual
Render
method for drawing the rectangle on an SDL surface. - Methods for setting the color and checking if a point is within the rectangle's bounds.
- A virtual destructor, allowing for proper cleanup in derived classes.
This class will serve as the base for more complex visual elements in our game, such as buttons and cells in the Minesweeper grid.
#pragma once
#include <SDL.h>
namespace Engine{
class Rectangle {
public:
Rectangle(
int x, int y, int w, int h,
SDL_Color Color = {0, 0, 0, 255})
: Rect{x, y, w, h}, Color{Color}{}
virtual void Render(SDL_Surface* Surface){
SDL_FillRect(
Surface, &Rect, SDL_MapRGB(
Surface->format, Color.r, Color.g,
Color.b
)
);
}
void SetColor(SDL_Color C){ Color = C; }
bool IsWithinBounds(int x, int y) const{
// Too far left
if (x < Rect.x) return false;
// Too far right
if (x > Rect.x + Rect.w) return false;
// Too high
if (y < Rect.y) return false;
// Too low
if (y > Rect.y + Rect.h) return false;
// Within bounds
return true;
}
SDL_Rect* GetRect(){ return &Rect; }
virtual ~Rectangle() = default;
private:
SDL_Rect Rect{0, 0, 0, 0};
SDL_Color Color{0, 0, 0, 0};
};
}
This Rectangle
class is similar to what we created in our earlier lesson when we started creating UI elements for SDL:
Structuring SDL Programs
Discover how to organize SDL components using manager classes, inheritance, and polymorphism for cleaner code.
Building upon the Rectangle
class, the Button
class adds interactivity to our visual elements. It includes:
- A constructor that sets up a button with a given position and size.
- A virtual
HandleEvent
method for processing SDL events related to the button. - Protected virtual methods for handling left clicks, right clicks, and mouse motion.
- A method to disable/enable the button.
The use of virtual methods here is crucial for extensibility. By declaring these methods as virtual, we enable derived classes to override them, allowing us to implement Minesweeper-specific behavior without modifying the base Button
class. This adheres to the Open-Closed Principle of object-oriented design.
For example, we'll be able to create a custom cell class for our Minesweeper grid that inherits from Button
and implements its own behavior for clicks and mouse movement.
#pragma once
#include "Globals.h"
#include "Engine/Rectangle.h"
namespace Engine{
class Button : public Rectangle {
public:
Button(int x, int y, int w, int h)
: Rectangle{x, y, w, h}{
SetColor(Config::BUTTON_COLOR);
}
virtual void
HandleEvent(const SDL_Event& E){
if (isDisabled) return;
if (E.type == SDL_MOUSEMOTION) {
HandleMouseMotion(E.motion);
} else if (E.type ==
SDL_MOUSEBUTTONDOWN) {
if (IsWithinBounds(E.button.x,
E.button.y)) {
E.button.button == SDL_BUTTON_LEFT
? HandleLeftClick()
: HandleRightClick();
}
}
}
void SetIsDisabled(bool NewValue){
isDisabled = NewValue;
}
protected:
virtual void HandleLeftClick(){}
virtual void HandleRightClick(){}
virtual void HandleMouseMotion(
const SDL_MouseMotionEvent& E){
if (IsWithinBounds(E.x, E.y)) {
SetColor(Config::BUTTON_HOVER_COLOR);
} else { SetColor(Config::BUTTON_COLOR); }
}
private:
bool isDisabled{false};
};
}
This Button
class is similar to what we created in our earlier lesson:
Creating SDL2 Buttons
Learn to create interactive buttons in SDL2 and manage communication between different UI components.
The Engine/Image.h
File
The Image
class allows us to display images in our game. This will be useful for showing icons like bombs and flags in our Minesweeper grid. Key features include:
- A constructor that loads an image from a file and applies padding.
- An
Render
method that draws the image on the given surface. - Proper resource management with a destructor that frees the image surface.
This class demonstrates how we can extend our basic Rectangle
to create more specialized visual elements.
#pragma once
#include <SDL_image.h>
namespace Engine{
class Image {
public:
Image(
int x, int y, int w, int h,
const std::string& Filename,
int Padding = 12
): Destination{
x + Padding/2, y + Padding/2,
w-Padding, h-Padding
}{
ImageSurface = IMG_Load(Filename.c_str());
#ifdef SHOW_DEBUG_HELPERS
Utils::CheckSDLError("IMG_Load");
#endif
}
void Render(SDL_Surface* Surface) {
SDL_BlitScaled(
ImageSurface,nullptr,
Surface, &Destination
);
}
~Image() {
if (ImageSurface) {
SDL_FreeSurface(ImageSurface);
}
}
Image(const Image&){}
private:
SDL_Surface* ImageSurface{nullptr};
SDL_Rect Destination{0, 0, 0, 0};
};
}
This Image
class uses the techniques we covered in our introduction to images, surface blitting, and SDL_Image
:
Loading and Displaying Images
Learn how to load, display, and optimize image rendering in your applications
The Engine/Text.h
File
The Text
class enables us to render text in our game. We'll use this for displaying numbers and other information in our Minesweeper grid. It provides:
- A constructor that sets up the
Text
member variables, including a rectangle to determine where the text should be rendered within the destination surface. SetText()
methods to change the text content and color. These methods also update aTextPosition
rectangle to ensure the text is rendered in the middle of the destination rectangle.- A
Render
method that draws the text on the given surface. - Proper resource management for the font and text surface.
#pragma once
#include <SDL_ttf.h>
#include "Globals.h"
namespace Engine{
class Text {
public:
Text(
int x, int y, int w, int h,
const std::string& Content,
SDL_Color Color = {0, 0, 0, 255},
int FontSize = 30
) : DestinationRect{x, y, w, h},
Color{Color}
{
Font = TTF_OpenFont(
Config::FONT.c_str(), FontSize);
#ifdef SHOW_DEBUG_HELPERS
Utils::CheckSDLError("TTF_OpenFont");
#endif
SetText(Content);
}
void SetText(const std::string& Text){
SetText(Text, Color);
}
void SetText(const std::string& Text,
SDL_Color NewColor){
if (TextSurface) {
SDL_FreeSurface(TextSurface);
}
Color = NewColor;
TextSurface = TTF_RenderUTF8_Blended(
Font, Text.c_str(), Color
);
auto [x, y, w, h] = DestinationRect;
// Horizontally centering
const int WidthDifference{
w - TextSurface->w};
const int LeftOffset{WidthDifference / 2};
// Vertically centering
const int HeightDifference{
h - TextSurface->h};
const int TopOffset{HeightDifference / 2};
TextPosition = {
x + LeftOffset, y + TopOffset, w, h
};
}
void Render(SDL_Surface* Surface) {
SDL_BlitSurface(
TextSurface, nullptr,
Surface, &TextPosition
);
}
~Text() {
if (Font) { TTF_CloseFont(Font); }
if (TextSurface) {
SDL_FreeSurface(TextSurface);
}
}
private:
SDL_Surface* TextSurface{nullptr};
TTF_Font* Font{nullptr};
SDL_Rect DestinationRect{0, 0, 0, 0};
SDL_Rect TextPosition{0, 0, 0, 0};
SDL_Color Color{0, 0, 0, 255};
};
}
This Text
class uses the techniques we covered in our introduction to SDL_ttf:
Rendering Text with SDL_ttf
Learn to render and manipulate text in SDL2 applications using the official SDL_ttf
extension
The Engine/Random.h
File
Randomness is a crucial element in Minesweeper for generating unique game boards. We use it primarily to randomly place bombs on the grid, ensuring each game is different and challenging. The Random
namespace encapsulates our random number generation logic, providing a simple interface for generating random integers within a specified range.
- It sets up a random number generator using
std::random_device
andstd::mt19937
. - It provides an
Int
function to generate random integers within a specified range.
This abstraction will make it easy to add randomness to our game in a controlled and reusable manner.
#pragma once
#include <random>
namespace Engine::Random{
inline std::random_device SEEDER;
inline std::mt19937 ENGINE{SEEDER()};
inline size_t Int(size_t Min, size_t Max){
std::uniform_int_distribution Get{Min, Max};
return Get(ENGINE);
}
}
This Engine::Random
namespace uses the techniques we covered in our introduction to the randomness utilities in C++:
Random Number Generation
This lesson covers the basics of using randomness, with practical applications
The Minesweeper/UI.h
File
While most of the code we've looked at is part of our general-purpose engine, the MinesweeperUI
class is our first Minesweeper-specific component. We're storing it in the Minesweeper/
directory to keep it separate from our engine code.
The MinesweeperUI
class serves as the central point for managing all user interface elements specific to our Minesweeper game. It will be responsible for rendering the game board, handling user interactions, and updating the game state. For now, it contains placeholder Render
and HandleEvent
methods, which we'll implement in subsequent lessons to bring our Minesweeper game to life.
#pragma once
class MinesweeperUI {
public:
void Render(SDL_Surface* Surface){
// ...
}
void HandleEvent(const SDL_Event& E){
// ...
};
};
The main.cpp
File
Finally, we have our main.cpp
file, which sets up the main loop of our application. It initializes SDL and its subsystems (SDL_image and SDL_ttf), and creates our game window and UI objects.
It also contains the main game loop, which forms the core of our game's execution:
- It continuously processes SDL events, allowing the game to respond to user inputs and other events we'll add in future lessons.
- It calls the
Render
methods to draw the current game state. - It updates the display to show the newly rendered frame.
This loop runs repeatedly until we receive an SDL_QUIT
event:
#include <SDL_image.h>
#include <SDL_ttf.h>
#include "Globals.h"
#include "Engine/Window.h"
#include "Minesweeper/UI.h"
int main(int argc, char** argv){
SDL_Init(SDL_INIT_VIDEO);
#ifdef SHOW_DEBUG_HELPERS
Utils::CheckSDLError("SDL_Init");
#endif
IMG_Init(IMG_INIT_PNG);
#ifdef SHOW_DEBUG_HELPERS
Utils::CheckSDLError("IMG_Init");
#endif
TTF_Init();
#ifdef SHOW_DEBUG_HELPERS
Utils::CheckSDLError("TTF_Init");
#endif
Engine::Window GameWindow;
MinesweeperUI UI;
SDL_Event Event;
bool shouldQuit{false};
while (!shouldQuit) {
while (SDL_PollEvent(&Event)) {
if (Event.type == SDL_QUIT) {
shouldQuit = true;
} else { UI.HandleEvent(Event); }
}
GameWindow.Render();
UI.Render(GameWindow.GetSurface());
GameWindow.Update();
}
SDL_Quit();
return 0;
}
If the code in our main.cpp
is unclear, reviewing the lesson on application loops is recommended:
Implementing an Application Loop
Step-by-step guide on creating the SDL2 application and event loops for interactive games
Assets
Our game will require some assets to function properly:
- SDL libraries, which we covered in our earlier installation guides.
- A font file for rendering text.
- Images for our bomb and flag icons.
Feel free to use any assets you prefer. The assets used in our screenshots are:
- Rubik-SemiBold.tff font from Google Fonts
- Bomb image from IconFinder
- Flag image from IconsDB
Remember, these assets should be located in the same directory as your compiled executable for the game to find them.
The CMakeLists.txt
File
For those using CMake to manage their project, the following CMakeLists.txt
file may be helpful.
This file sets up the project, adds the necessary source files, and links against the required SDL libraries. It also includes a command to copy the SDL DLLs and asset files to the output directory, ensuring that everything the game needs is in the right place when you run it.
cmake_minimum_required(VERSION 3.16)
set(CMAKE_CXX_STANDARD 20)
project(Minesweeper VERSION 1.0.0)
add_executable(Minesweeper
"main.cpp"
"Globals.h"
"Engine/Button.h"
"Engine/Rectangle.h"
"Engine/Image.h"
"Engine/Text.h"
"Engine/Window.h"
"Engine/Random.h"
"Minesweeper/UI.h"
# Files that will be added later:
# "Minesweeper/Grid.h"
# "Minesweeper/UI.h"
# "Minesweeper/Cell.h"
# "Minesweeper/Cell.cpp"
# "Minesweeper/NewGameButton.h"
)
target_include_directories(
Minesweeper PUBLIC ${PROJECT_SOURCE_DIR}
)
add_subdirectory(external/SDL)
add_subdirectory(external/SDL_image)
add_subdirectory(external/SDL_ttf)
target_link_libraries(Minesweeper PRIVATE
SDL2
SDL2_image
SDL2_ttf
)
if (WIN32)
target_link_libraries(
Minesweeper PRIVATE SDL2main
)
endif()
# Copy DLLs and Assets to output directory
set(AssetDirectory "${PROJECT_SOURCE_DIR}/Assets")
add_custom_command(
TARGET Minesweeper POST_BUILD COMMAND
${CMAKE_COMMAND} -E copy_if_different
"$<TARGET_FILE:SDL2>"
"$<TARGET_FILE:SDL2_image>"
"$<TARGET_FILE:SDL2_ttf>"
"${AssetDirectory}/bomb.png"
"${AssetDirectory}/Rubik-SemiBold.ttf"
"${AssetDirectory}/flag.png"
"$<TARGET_FILE_DIR:Minesweeper>"
VERBATIM
)
Building SDL2 from a Subdirectory (CMake)
A step-by-step guide on setting up SDL2 and useful extensions in a project that uses CMake as its build system
Running the Application
At this point, you should be able to compile and run the application. When you do, you should see an empty window appear. This might not look like much, but it's the foundation upon which we'll build our game!

In the next part of this tutorial, we'll start adding Minesweeper-specific functionality to our program, bringing our game to life step by step.
Creating the Grid
Building a two-dimensional grid of interactive minesweeper cells