Placing Flags
Implement flag placement and tracking to complete your Minesweeper project.
In this final part of our Minesweeper series, we'll add the ability for players to place flags on suspected mine locations.
We'll update our game logic to handle flag placement and removal, create a visual representation for flags, and implement a counter to track the number of flags placed.
By the end of this lesson, you'll have a fully functional Minesweeper game with all the classic features players expect!
Updating Globals
Let's start by updating our Globals.h
. We'll register events for flags being placed and cleared, and we'll also add a string to define where our flag image is stored:
// Globals.h
// ...
namespace UserEvents{
// ...
inline Uint32 FLAG_PLACED =
SDL_RegisterEvents(1);
inline Uint32 FLAG_CLEARED =
SDL_RegisterEvents(1);
}
namespace Config{
// ...
inline const std::string FLAG_IMAGE{
"flag.png"};
}
// ...
Placing Flags on Right Clicks
To place a flag, players will right click on a cell, so we need to update our MinesweeperCell
class. The base Engine::Button
class has a HandleRightClick()
method we can override, and we'll additionally add a boolean to store whether the player has placed a flag in this cell:
// Minesweeper/Cell.h
// ...
class MinesweeperCell : public Engine::Button {
protected:
void HandleRightClick() override;
// ...
private:
// ...
bool hasFlag{false};
};
In our HandleRightClick()
implementation, we'll simply toggle the hasFlag
variable, and report the appropriate event:
// Minesweeper/Cell.cpp
// ...
void MinesweeperCell::HandleRightClick(){
if (hasFlag) {
ReportEvent(UserEvents::FLAG_CLEARED);
hasFlag = false;
} else {
ReportEvent(UserEvents::FLAG_PLACED);
hasFlag = true;
}
}
Left-clicking on a cell that contains a flag shouldn't clear the cell, so let's add that logic to our HandleLeftClick()
function:
// Minesweeper/Cell.cpp
// ...
void MinesweeperCell::HandleLeftClick(){
if (!hasFlag) { ClearCell(); }
}
// ...
When the player starts a new game, all the flags should be cleared. Let's add that to our Reset()
function:
// Minesweeper/Cell.cpp
// ...
void MinesweeperCell::Reset(){
isCleared = false;
hasFlag = false;
hasBomb = false;
AdjacentBombs = 0;
SetIsDisabled(false);
SetColor(Config::BUTTON_COLOR);
}
// ...
The player doesn't necessarily need to mark all the bombs to win the game. As a small presentational improvement, let's just automatically flag all the cells with bombs in response to the GAME_WON
event:
// Minesweeper/Cell.cpp
// ...
void MinesweeperCell::HandleEvent(
const SDL_Event& E){
if (E.type == UserEvents::CELL_CLEARED) {
HandleCellCleared(E.user);
} else if (E.type ==
UserEvents::BOMB_PLACED) {
HandleBombPlaced(E.user);
} else if (E.type == UserEvents::GAME_WON) {
if (hasBomb) {
hasFlag = hasBomb;
SetColor(Config::BUTTON_SUCCESS_COLOR);
}
SetIsDisabled(true);
} else if (E.type == UserEvents::GAME_LOST) {
if (hasBomb) {
isCleared = true;
SetColor(Config::BUTTON_FAILURE_COLOR);
}
SetIsDisabled(true);
}
Button::HandleEvent(E);
}
// ...
Rendering Flags
Now that our cells are keeping track of whether or not they're flagged, let's render some visual output for the flags. We'll add another Engine::Image
member to our MinesweeperCell
to render the flag:
// Minesweeper/Cell.h
class MinesweeperCell : public Engine::Button {
// ...
private:
// ...
Engine::Image FlagImage;
};
In our constructor, we initialize this variable in much the same way we initialized the bomb image:
// Minesweeper/Cell.cpp
// ...
MinesweeperCell::MinesweeperCell(
int x, int y, int w, int h, int Row, int Col)
: Button{x, y, w, h}, Row{Row}, Col{Col},
FlagImage{
x, y, w, h,
Config::FLAG_IMAGE},
BombImage{
x, y, w, h,
Config::BOMB_IMAGE},
Text{
x, y, w, h,
std::to_string(AdjacentBombs),
Config::TEXT_COLORS[AdjacentBombs]}{};
// ...
Finally, in our Render()
method, if the cell has a flag, we'll render the FlagImage
:
// Minesweeper/Cell.cpp
// ...
void MinesweeperCell::Render(
SDL_Surface* Surface){
Button::Render(Surface);
if (hasFlag) {
FlagImage.Render(Surface);
} else if (isCleared && hasBomb) {
BombImage.Render(Surface);
} else if (isCleared && AdjacentBombs > 0) {
Text.Render(Surface);
}
#ifdef SHOW_DEBUG_HELPERS
else if (hasBomb) { BombImage.Render(Surface); }
#endif
}
Running our game, we can now right-click in any uncleared cell to place a flag. Left clicking a flagged cell should have no effect, but right clicking it will remove the flag:

Flag Counter
Let's add a counter to our footer, so the player can keep track of how many bombs they have yet to place flags on. This indicator will span the full height of the footer, with its width and color controlled by Config
variables in Global.h
:
// Globals.h
// ...
namespace Config{
// ...
inline constexpr int FLAG_COUNTER_WIDTH{100};
// ...
inline constexpr SDL_Color FLAG_COUNTER_COLOR{
80, 80, 80, 255};
// ...
}
// ...
Let's create our Minesweeper/FlagCounter
class. It inherits from Engine::Rectangle
, and will contain an Image
and Text
element side-by-side.
The position and size of the Rectangle
will be controlled by constructor arguments, with the color coming from our Config
namespace.
The Image
will be placed at the top left of the Rectangle
, with its height being based on the FOOTER_HEIGHT
config variable, and its width being the same as the height to ensure it is square. We also apply a 24 pixel padding to make the image slightly smaller.
The Text
variable uses slightly more arithmetic to ensure it appears to the right of the Image
. We also set the initial string to be the total number of bombs in the grid. We set the color to white, and the font size to 20:
// Minesweeper/FlagCounter.h
#pragma once
#include <string>
#include "Globals.h"
#include "Engine/Rectangle.h"
#include "Engine/Text.h"
#include "Engine/Image.h"
class FlagCounter : public Engine::Rectangle {
public:
FlagCounter(int x, int y, int w, int h)
: Rectangle{
x, y, w, h, Config::FLAG_COUNTER_COLOR},
Image{
x, y,
Config::FOOTER_HEIGHT - Config::PADDING,
Config::FOOTER_HEIGHT - Config::PADDING,
Config::FLAG_IMAGE,
24},
Text{
x + Config::FOOTER_HEIGHT, y,
w - Config::FOOTER_HEIGHT - 24, h,
std::to_string(Config::BOMB_COUNT),
{255, 255, 255, 255}, 20}{}
private:
Engine::Image Image;
Engine::Text Text;
};
We'll override the base rectangle's Render()
method to ensure the Text
and Image
are also rendered:
// Minesweeper/FlagCounter.h
// ...
class FlagCounter : public Engine::Rectangle {
public:
// ...
void Render(SDL_Surface* Surface) override{
Rectangle::Render(Surface);
Text.Render(Surface);
Image.Render(Surface);
}
// ...
};
Finally, we'll implement a HandleEvent()
method so that the FlagCounter
can update appropriately:
- When the player places a flag, we decrement the number of available flags
- When the player clears a flag, we increment the number of available flags
- When the game is won, we set the available flags to
0
because, when the game is won, ourMinesweeperCell
objects automatically place flags where all the bombs were - When the user restarts the game, the number of available flags is set to its initial value.
Additionally, when any of these events occur, we call the Text
member's SetText()
function to inform it what number it should be rendering:
// Minesweeper/FlagCounter.h
// ...
class FlagCounter : public Engine::Rectangle {
public:
// ...
void HandleEvent(const SDL_Event& E){
if (E.type ==
UserEvents::FLAG_PLACED) {
--FlagsAvailable;
} else if (E.type ==
UserEvents::FLAG_CLEARED) {
++FlagsAvailable;
} else if (E.type == UserEvents::GAME_WON) {
FlagsAvailable = 0;
} else if (E.type == UserEvents::NEW_GAME) {
FlagsAvailable = Config::BOMB_COUNT;
} else { return; }
Text.SetText(
std::to_string(FlagsAvailable)
);
}
private:
// ...
int FlagsAvailable{Config::BOMB_COUNT};
};
Finally, let's add it to the UI. As before, we add it as a member of our MinesweeperUI
, and add it to our Render()
and HandleEvent()
functions.
We reduce the width of the NewGameButton
to make space in the footer, and then do some arithmetic to set the FlagCounter
's position (x
and y
) and size (w
and h
).
The calculations involved to determine these constructor arguments is getting excessively messy and something we'd typically want to address.
Given we're essentially finished with this project, we'll leave it like this for now. However, it's a problem we should consider solving in our Engine
module to make future projects easier. We discuss options for this later in the lesson.
Our final MinesweeperUI
class looks like this:
// Minesweeper/UI.h
#pragma once
#include "Globals.h"
#include "Minesweeper/Grid.h"
#include "Minesweeper/NewGameButton.h"
#include "Minesweeper/FlagCounter.h"
class MinesweeperUI {
public:
void Render(SDL_Surface* Surface){
Grid.Render(Surface);
Button.Render(Surface);
Counter.Render(Surface);
}
void HandleEvent(const SDL_Event& E){
Grid.HandleEvent(E);
Button.HandleEvent(E);
Counter.HandleEvent(E);
};
private:
MinesweeperGrid Grid{
Config::PADDING, Config::PADDING
};
NewGameButton Button{
Config::PADDING,
Config::GRID_HEIGHT + Config::PADDING * 2,
Config::WINDOW_WIDTH - Config::PADDING * 3
- Config::FLAG_COUNTER_WIDTH,
Config::FOOTER_HEIGHT - Config::PADDING
};
FlagCounter Counter{
Config::WINDOW_WIDTH - Config::PADDING
- Config::FLAG_COUNTER_WIDTH,
Config::GRID_HEIGHT + Config::PADDING * 2,
Config::FLAG_COUNTER_WIDTH,
Config::FOOTER_HEIGHT - Config::PADDING
};
};
Our game is now complete and should be fully working! Remember to disable the SHOW_DEBUG_HELPERS
definition to hide the bombs:

Complete Code
Complete versions of the files we changed in this part are available below
The remaining project files (which were not changed in this section) are available here:
We're also using the following assets:
Assets/Rubik-SemiBold.tff
available from Google FontsAssets/bomb.png
available from IconFinderAssets/flag.png
available from IconsDB
Improvement Ideas
To make the most of our learning, it can be helpful to go back to our project and change some things around, reworking how things are designed, or even completely rebuilding it.
This self-directed learning helps solidify the concepts we've covered, and makes it easier to apply them to new projects that won't have as much guidance. Some ideas on how the project could be reworked are below:
Refactoring
Something we should constantly be reviewing is areas of our code that seem quite complex and difficult to follow.
For example, our program has a very simple visual layout, with a grid at the top and a new game button and flag counter side by side at the bottom.
However, the code to establish such a simple layout is already difficult to write and follow:
// ...
class MinesweeperUI {
public:
void Render(SDL_Surface* Surface){
Grid.Render(Surface);
Button.Render(Surface);
Counter.Render(Surface);
}
// ...
private:
MinesweeperGrid Grid{
Config::PADDING, Config::PADDING
};
NewGameButton Button{
Config::PADDING,
Config::GRID_HEIGHT + Config::PADDING * 2,
Config::WINDOW_WIDTH - Config::PADDING * 3
- Config::FLAG_COUNTER_WIDTH,
Config::FOOTER_HEIGHT - Config::PADDING
};
FlagCounter Counter{
Config::WINDOW_WIDTH - Config::PADDING
- Config::FLAG_COUNTER_WIDTH,
Config::GRID_HEIGHT + Config::PADDING * 2,
Config::FLAG_COUNTER_WIDTH,
Config::FOOTER_HEIGHT - Config::PADDING
};
};
Laying objects out in columns and rows is a recurring problem, so we could consider introducing generic classes like Column
and Row
that take care of that for us.
These classes can simply position children next to each other vertically or horizontally, and could accept arguments to control things like the padding between objects:
// ...
#include "Engine/Column.h"
#include "Engine/Row.h"
class MinesweeperUI {
public:
void Render(SDL_Surface* Surface){
Layout.Render(Surface);
}
// ...
private:
MinesweeperGrid Grid;
NewGameButton Button;
FlagCounter Counter;
Column Layout{
Grid,
Row{
Button, Counter
}
}
};
Gameplay Improvements
Currently, our game places bombs before the user clears the first cell. This can cause unfair outcomes, such as the first cell containing a bomb.
Many implementations of Minesweeper do not choose bomb positions until after the user clicks their first cell. This allows them to guarantee the cell is safe so the player doesn't lose immediately.
Additionally, it allows them to place bombs further away from this starting point. For example, we can ensure the adjacent cells to where the player starts have no bombs, either.
This results in the first click clearing a large area of the grid, allowing the player to proceed strategically and reducing the probability they'll need to make guesses.
Pooling Image Data
Currently, the cells in our grid are generating surfaces unnecessarily. For example, if our settings specify the grid has 50 cells, then 50 bomb images are loaded, and 50 identical surfaces are held in memory.
Flag and text surfaces are similarly wasteful. Every cell that shows a 3
image, for example, is generating its own surface, which is identical to every other 3
.
Additionally, if a cell ultimately has 3
adjacent bombs, the bomb placement algorithm has it generate 1
and 2
surfaces which it discards before the player ever sees them.
These suboptimal approaches are fine for a simple game running on modern hardware, but we should consider reworking it anyway as learning opportunity. We'd want to pool these common surfaces into a shared location where our cells can access and share them.
We can optimize this concept even further by pooling the separate images into a single image, called a texture atlas. The original Microsoft version of Minesweeper might have a texture atlas that looks like this:

Cells could then select the data they want by providing a source rectangle to the SDL_BlitSurface()
or SDL_BlitScaled()
functions:
#include "MinesweeperAtlas.h"
// ...
void Render(SDL_Surface* Surface) {
SDL_BlitScaled(
MinesweeperAtlas::Surface,
MinesweeperAtlas::Bomb,
Surface,
&Destination
);
}
// ...
Don't worry if you're not sure how you would set this up now. We revisit asset management multiple times throughout the course, and future projects will use increasingly more sophisticated techniques.
Difficulty Modes
Most Minesweeper games allow players to choose their preferred difficulty - typically, easy, medium or hard.
Harder difficulties increase the size of the grid, and increase the number of bombs within the grid. It would involve a lot of changes to set our game up to support this. However, it wouldn't require any new concepts - we know everything we need to set this up.
The SDL_SetWindowSize()
function (official documentation) let's us programatically resize the window, which can be useful if our grid size changes based on difficulty setting.
Reducing Event Traffic
To keep things simple, our project routed pretty much every event through to every relevant object in our game. This results in a lot of excess traffic, where object's HandleEvent()
functions are invoked with an event the object has no interest in.
Every function call that results in no effect has an unnecessary performance cost, and that's especially true when the functions are virtual
.
An obvious way to reduce this traffic is having each parent check an event is relevant to its children before forwarding it:
void HandleEvent(const SDL_Event& E){
if (isRelevantToChildren(E.type) {
for (auto& Child : Children) {
Child.HandleEvent(E);
}
}
};
However, this limits flexibility. When creating a parent class, we often won't know what type of children we'll eventually be managing.
Even if we did know exactly what event types the children are interested in, declaring that within the parent would typically be a bad design.
To keep our code well organised and understandable, the event types that a class is interested in should be managed within that same class. For example, the event types that MinesweeperCell
objects are interested in should be declared in MinesweeperCell
class.
Objects can then provide that list to their parent, or to some object that is managing event traffic. For example:
class Child: public EventReceiver {
// Subscribe to event types from constructor
Child(EventReceiver* Parent) {
Parent->SubscribeToEventTypes({
UserEvents::GAME_START,
UserEvents::GAME_END,
UserEvents::CELL_CLEARED,
// ...
})
}
// ...
}
This system can become increasingly elaborate - for example, objects may subscribe or unsubscribe from events at different points during their lifecycle:
class Child: public EventReceiver {
// ...
void SetIsDisabled() {
// No longer interested in mouse events
Parent->UnsubscribeToEventTypes({
SDL_MOUSEMOTION,
SDL_MOUSEBUTTON
})
}
// ...
}
Designs that implement these inversions of control are often called publish-subscribe pattern or observer pattern. We cover this in more detail in the next chapter.
Timer and High Score
Usually, Minesweeper tracks how long it takes us to complete a game, and keeps track of our fastest time. Implementing this now may require research into some new topics. For example, the C++ standard library includes chrono, a system for working with time. We covered it in our beginner course:
Dates, Times and Durations
Learn the basics of the chrono library and discover how to effectively manage durations, clocks, and time points
Remembering our best time within a single session is easy, but keeping track of it from one session to the next requires saving the data to the player's hard drive.
Again, the C++ standard library can help us here. We cover it's file system utilities in our advanced course:
Working with the File System
Create, delete, move, and navigate through directories and files using the std::filesystem
library.
Alternatively, mechanics like these can be implemented using functions provided by the SDL library. SDL_GetTicks64()
(official documentation) can be used to track the time between two events, and RWops
(official documentation) can be used to read and write data to the hard drive.
We'll cover the SDL timing and read/write functions later in the course.
Summary
Well done on finishing your Minesweeper project! We've successfully added flag placement functionality, complete with visual feedback and a counter in the UI.
These final features round out the gameplay, allowing players to mark potential mine locations and strategize their moves.
With all elements now in place, you have a fully-featured Minesweeper game that offers the same engaging puzzle-solving experience as the classic versions.
Congratulations on your achievement!
Ticking
Using Tick()
functions to update game objects independently of events