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, combining all the concepts we've covered so far.
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, we can thoughtfully 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.
In this lesson, we'll introduce all of the engine code. While it might seem like a lot of code at first, it's all very similar to what 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.
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, 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, 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.
src/Globals.h
#pragma once
#define SHOW_DEBUG_HELPERS
#include <iostream>
#include <SDL3/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 BASE_PATH{
SDL_GetBasePath()};
inline const std::string BOMB_IMAGE{
BASE_PATH + "Bomb.png"};
inline const std::string FLAG_IMAGE{
BASE_PATH + "Flag.png"};
inline const std::string FONT{
BASE_PATH + "Roboto-Medium.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
}
The CheckSDLError()
function in this file is something we created in our earlier lesson on .
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:
src/Engine/Window.h
#pragma once
#include <SDL3/SDL.h>
#include "Globals.h"
namespace Engine{
class Window {
public:
Window(){
SDLWindow = SDL_CreateWindow(
Config::GAME_NAME.c_str(),
Config::WINDOW_WIDTH,
Config::WINDOW_HEIGHT,
0
);
}
void Render(){
const auto* Fmt = SDL_GetPixelFormatDetails(
GetSurface()->format
);
SDL_FillSurfaceRect(
GetSurface(), nullptr,
SDL_MapRGB(Fmt, nullptr,
Config::BACKGROUND_COLOR.r,
Config::BACKGROUND_COLOR.g,
Config::BACKGROUND_COLOR.b)
);
}
void Update(){
SDL_UpdateWindowSurface(SDLWindow);
}
SDL_Surface* GetSurface() const {
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{nullptr};
};
}
This Window
class is similar to what we created in our earlier lesson on .
The Engine/Rectangle.h
File
The Rectangle
class 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.
src/Engine/Rectangle.h
#pragma once
#include <SDL3/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){
const auto* Fmt = SDL_GetPixelFormatDetails(
Surface->format
);
SDL_FillSurfaceRect(
Surface, &Rect, SDL_MapRGB(
Fmt, nullptr, Color.r, Color.g, Color.b
)
);
}
void SetColor(SDL_Color C){ Color = C; }
bool IsWithinBounds(int x, int y) const{
if (x < Rect.x) return false;
if (x > Rect.x + Rect.w) return false;
if (y < Rect.y) return false;
if (y > Rect.y + Rect.h) return false;
return true;
}
const SDL_Rect* GetRect() const { 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 .
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.
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.
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.
src/Engine/Button.h
#pragma once
#include <SDL3/SDL.h>
#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_EVENT_MOUSE_MOTION) {
HandleMouseMotion(E.motion);
} else if (E.type ==
SDL_EVENT_MOUSE_BUTTON_DOWN) {
if (IsWithinBounds(
(int)E.button.x, (int)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((int)E.x, (int)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 on .
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.
- A
Render()
method that draws the image on the given surface. - Proper resource management with a destructor that frees the image surface.
src/Engine/Image.h
#pragma once
#include <string>
#include <SDL3/SDL.h>
#include <SDL3_image/SDL_image.h>
#include "Globals.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_BlitSurfaceScaled(
ImageSurface, nullptr,
Surface, &Destination,
SDL_SCALEMODE_LINEAR
);
}
~Image() {
if (ImageSurface) {
SDL_DestroySurface(ImageSurface);
}
}
Image(const Image&) = delete;
Image& operator=(const Image&) = delete;
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 .
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.
src/Engine/Text.h
#pragma once
#include <string>
#include <SDL3/SDL.h>
#include <SDL3_ttf/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},
float FontSize = 30.0f
) : 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_DestroySurface(TextSurface);
}
Color = NewColor;
TextSurface = TTF_RenderText_Blended(
Font, Text.c_str(), 0, 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,
TextSurface->w, TextSurface->h
};
}
void Render(SDL_Surface* Surface) {
SDL_BlitSurface(
TextSurface, nullptr,
Surface, &TextPosition
);
}
~Text() {
if (TTF_WasInit()) {
TTF_CloseFont(Font);
}
if (TextSurface) {
SDL_DestroySurface(TextSurface);
}
}
Text(const Text&) = delete;
Text& operator=(const Text&) = delete;
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 .
The Engine/Random.h
File
Minesweeper games rely on randomly place bombs on the grid to ensure each game is different. 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.
src/Engine/Random.h
#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 .
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 game to life.
src/Minesweeper/UI.h
#pragma once
#include <SDL3/SDL.h>
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, and creates our game window and UI objects.
It also contains the main game loop, which forms the core of our game's execution as we've been doing in the past:
- 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_EVENT_QUIT
event:
src/main.cpp
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <SDL3_image/SDL_image.h>
#include <SDL3_ttf/SDL_ttf.h>
#include "Globals.h"
#include "Engine/Window.h"
#include "Minesweeper/UI.h"
int main(int, char**){
SDL_Init(SDL_INIT_VIDEO);
#ifdef SHOW_DEBUG_HELPERS
Utils::CheckSDLError("SDL_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_EVENT_QUIT) {
shouldQuit = true;
} else { UI.HandleEvent(Event); }
}
GameWindow.Render();
UI.Render(GameWindow.GetSurface());
GameWindow.Update();
}
TTF_Quit();
SDL_Quit();
return 0;
}
If the code in our main.cpp
is unclear, reviewing the lesson on is recommended.
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:
Roboto-Medium.tff
font from Google FontsBomb.png
image from IconFinderFlag.png
image from IconsDB
Remember, these assets should be located in the same directory as your compiled executable for the game to find them.
Include Directories
In the rest of this chapter, the #include
directives in our code examples assume that the /src
directory is in your project's include directories or, alternatively, you adapt the directives to match your project configuration.
If you're using CMake to manage your project, the CMakeLists.txt
file provided in the next section includes the required configuration.
If you're not sure what include directories are or how to set them, we cover it in more detail in our .
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 include directories, and links against the required SDL libraries.
CMakeLists.txt
cmake_minimum_required(VERSION 3.23)
project(
Minesweeper
VERSION 1.0
DESCRIPTION "Minesweeper"
LANGUAGES CXX
)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(
CMAKE_RUNTIME_OUTPUT_DIRECTORY
"${CMAKE_BINARY_DIR}/$<CONFIGURATION>"
)
set(
CMAKE_LIBRARY_OUTPUT_DIRECTORY
"${CMAKE_BINARY_DIR}/$<CONFIGURATION>"
)
add_executable(Minesweeper
src/main.cpp
# This file will be added later in the chapter
# src/Minesweeper/Cell.cpp
)
target_include_directories(Minesweeper
PRIVATE ${PROJECT_SOURCE_DIR}/src
)
if(APPLE)
set_target_properties(Minesweeper PROPERTIES
INSTALL_RPATH "@executable_path;@loader_path"
BUILD_WITH_INSTALL_RPATH TRUE
MACOSX_RPATH TRUE
)
elseif(UNIX)
set_target_properties(Minesweeper PROPERTIES
INSTALL_RPATH "$ORIGIN"
BUILD_WITH_INSTALL_RPATH TRUE
)
endif()
set(SDLTTF_VENDORED ON)
set(VENDOR_DIR "${PROJECT_SOURCE_DIR}/vendor")
add_subdirectory(${VENDOR_DIR}/SDL)
add_subdirectory(${VENDOR_DIR}/SDL_image)
add_subdirectory(${VENDOR_DIR}/SDL_ttf)
target_link_libraries(Minesweeper
SDL3::SDL3
SDL3_image::SDL3_image
SDL3_ttf::SDL3_ttf
)
You can find more information on using CMake with SDL3 in our earlier .
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.

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