Engine Overview

An introduction to the generic engine classes we'll use to create the game
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Free, Unlimited Access
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Updated

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:

  1. An "Engine" module containing components that are generally useful across a wide range of projects, not specific to Minesweeper.
  2. 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

Globals.h

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:

  1. 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.
  2. 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.
  3. 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:

Engine/Window.h

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:

  1. A constructor that creates an SDL window using the configurations defined in our Globals.h file.
  2. A Render method that fills the window with the background color.
  3. An Update method that refreshes the window surface.
  4. 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() {
      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:

Engine/Rectangle.h

The Rectangle class is a fundamental building block in our engine, used for drawing rectangles on the screen. It provides the following key features:

  1. A constructor that takes position (x, y), dimensions (width, height), and an optional color.
  2. A virtual Render method for drawing the rectangle on an SDL surface.
  3. Methods for setting the color and checking if a point is within the rectangle's bounds.
  4. 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:

Engine/Button.h

Building upon the Rectangle class, the Button class adds interactivity to our visual elements. It includes:

  1. A constructor that sets up a button with a given position and size.
  2. A virtual HandleEvent method for processing SDL events related to the button.
  3. Protected virtual methods for handling left clicks, right clicks, and mouse motion.
  4. 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:

Engine/Image.h

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:

  1. A constructor that loads an image from a file and applies padding.
  2. An Render method that draws the image on the given surface.
  3. 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:

Engine/Text.h

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:

  1. A constructor that sets up the Text member variables, including a rectangle to determine where the text should be rendered within the destination surface..
  2. SetText() methods to change the text content and color. These methods also update a TextPosition rectangle to ensure the text is rendered in the middle of the destination rectangle
  3. A Render method that draws the text on the given surface.
  4. 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:

Engine/Random.h

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.

  1. It sets up a random number generator using std::random_device and std::mt19937.
  2. 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++:

Minesweeper/UI.h

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){
    // ...
  };
};

main.cpp

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:

  1. It continuously processes SDL events, allowing the game to respond to user inputs and other events we’ll add in future lessons.
  2. It calls the Render methods to draw the current game state.
  3. 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:

Assets

Our game will require some assets to function properly:

  1. SDL libraries, which we covered in our earlier installation guides.
  2. A font file for rendering text.
  3. Images for our bomb and flag icons.

Feel free to use any assets you prefer. The assets used in our screenshots are:

Remember, these assets should be located in the same directory as your compiled executable for the game to find them.

CMakeLists.txt

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
)

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!

Screenshot of our program

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.

Was this lesson useful?

Next Lesson

Creating the Grid

Building a two-dimensional grid of interactive minesweeper cells
Abstract art representing computer programming
New: AI-Powered AssistanceAI Assistance

Questions and HelpNeed Help?

Get instant help using our free AI assistant, powered by state-of-the-art language models.

Ryan McCombe
Ryan McCombe
Updated
sdl2-promo.jpg
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Free, Unlimited Access
Project: Making Minesweeper
  • 44.GPUs and Rasterization
  • 45.SDL Renderers
sdl2-promo.jpg
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Free, unlimited access

This course includes:

  • 46 Lessons
  • 100+ Code Samples
  • 91% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

Creating the Grid

Building a two-dimensional grid of interactive minesweeper cells
Abstract art representing computer programming
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved