-
Recent Posts
Recent Comments
Archives
Categories
Meta
C++23 brought a new and useful addition to the language: insert_range().
This new member function provides a more efficient way to insert a range of elements into a vector. Until C++23, the only way to insert a range of elements into a vector was to use a loop. For example:
std::vector<int> vec1 = {1, 2, 3, 4, 5};
std::vector<int> vec2 = {6, 7, 8, 9, 10};
for (int i = 0; i < vec2.size(); i++)
{
vec1.insert(vec1.begin() + 2 + i, vec2[i]);
}
// Print the vector
for (int i = 0; i < vec1.size(); i++)
{
std::cout << vec1[i] << ” “;
}
Our output will be: 1 2 6 7 8 9 10 3 4 5
However, especially when we use large vectors, loops could be very inefficient. When you loop through a large container, you are essentially accessing each element in the container one at a time. This can be a very slow process, especially if the container is very large.
The insert_range() function solves this problem by providing a single function that can be used to insert a range of elements into a vector with a single function call. So now, instead of using the loop in our previous code, we can simply write:
std::vector<int> vec1 = {1, 2, 3, 4, 5};
std::vector<int> vec2 = {6, 7, 8, 9, 10};
auto iter = vec1.insert_range(vec1.begin() + 2, vec2.begin(), vec2.end());
for (int i = 0; i < vec1.size(); i++)
{
std::cout << vec1[i] << ” “;
}
As you can see, we don’t need to use a loop, we simply add the new range to our vector.
The new insert_range() function is a really valuable addition to the C++ language, and we expect it will be widely used.
In our new book Learning C++ published by Manning Publication, we teach C++ core language features, including the newest C++23 standard.
Do you know there are ways to hide files and folders and make them invisible? Normally you have the option to check or uncheck the “hidden items” checkbox in the Explorer, as shown in the screenshot below:
Nevertheless, when a file or a folder is hidden at the lowest level, it won’t show even if the “hidden items” option is checked.
In the digital age, privacy and security are of paramount importance. While operating systems like Windows provide basic mechanisms to hide files and folders, these methods can often be circumvented. In this blog post, we’ll explore a more robust approach to hiding files and folders using a custom filesystem driver in C++20. This technique ensures that hidden files and folders remain concealed even at the lowest system level.
The need for advanced file and folder hiding techniques arises when you want to protect sensitive data from prying eyes or malicious software. By creating a filesystem driver, we can create a mechanism for truly hiding that sensitive data. Other use cases may be Lawful Interception.
Creating a custom filesystem driver is an advanced and challenging task that requires in-depth knowledge of system programming and kernel-level development. The slightest bug may bring the well-known BSOD. Always conduct code review. Here is a high-level overview of the steps involved:
Hiding files and folders at a low-level using a custom filesystem driver in C++20 is a powerful technique for enhancing data security and privacy. However, it’s essential to approach this task with caution and expertise, as kernel-level development can be complex and carries significant risks. Always prioritize system stability and security when developing and deploying such drivers.
Requirements for Submitting a Driver to Microsoft Labs
Driver Stability and Security: Your driver must be thoroughly tested and verified for stability and security. It should not introduce system instability or security vulnerabilities. Ensure that your code adheres to best practices for kernel-mode development.
Driver Compatibility: The driver should be compatible with the target Windows operating systems (e.g., Windows 10, and Windows 11). Microsoft may require you to provide documentation specifying which OS versions your driver is intended for.
Clean Code: Make sure your code is well-documented and follows established coding standards and practices, such as using the Allman code style indentation as specified in your user profile.
Driver Package: Create a driver package that includes the driver binaries, INF files, and any necessary support files. The INF file provides installation instructions to Windows and specifies the driver’s information.
Driver Testing: Perform rigorous testing on various Windows configurations to ensure compatibility and stability. You should also verify that the driver can be properly installed and uninstalled.
Digital Signature: Your driver package should be digitally signed with a valid code signing certificate. Microsoft Labs will not sign drivers without proper digital signatures.
Prepare Your Driver Package: Ensure that your driver package includes all necessary files, including the driver binaries, INF file, and any additional files required for installation or operation.
Acquire a Code Signing Certificate: If you don’t already have a code signing certificate, you’ll need to obtain one from a trusted Certificate Authority (CA). Microsoft recommends using a certificate issued by a CA that is a member of the Windows Hardware Developer Program. Since January 2016, and as I explained in this article, you need an EV Code Signing Certificate and each driver must be counter-signed by Microsoft as well.
Driver Submission: Visit the Windows Hardware Dev Center Dashboard (https://partner.microsoft.com/en-us/dashboard/hardware). If you don’t have an account, you’ll need to create one. Once logged in, you can start the driver submission process.
Fill in Driver Information: Provide information about your driver, including its name, version, and a brief description. Specify the hardware IDs and compatible IDs for the devices your driver supports.
Upload Driver Package: Upload your driver package to the Windows Hardware Dev Center. Ensure that the package is complete and correctly configured. Note that to create such a package, you would need to sign the driver file/s first, then create a special archive called .cab, and then sign that .cab again. Here is an article I wrote about the process.
Code Signing Request: During the submission process, you’ll be prompted to submit a request for code signing. Provide the details of your code signing certificate, and Microsoft Labs will use it to sign your driver package.
Review and Validation: Microsoft Labs will review your driver submission for compliance with their requirements. This process may take some time, and they may contact you for additional information or clarifications.
Digital Signature: Once your driver package is approved, Microsoft Labs will digitally sign it with a Microsoft signature. This signature signifies that the driver has passed their review and is safe for installation on Windows systems.
Note: in many cases, Microsoft will not sign the driver and in that case, you will have to download a report specifying the reasons for the rejection, and then fix whatever needs to be fixed and go through the entire process again.
Download the Signed Driver: After your driver package is signed, you can download the signed package from the Windows Hardware Dev Center.
Distribution: Distribute your signed driver to your users or through appropriate channels, knowing that it has the Microsoft signature, which enhances its trustworthiness.
There is an option to enjoy both worlds. Obtain a Source Code license for a ready-made source code project, where you have everything you need to add file and folder hiding capabilities to your software.
We have developed everything needed, so you don’t even have to code sign any driver, but just use a ready-made SDK and the source code. These can be obtained here and is demonstrated in this video.
New chapters out!
You can now access the following chapters of Learning C++ by Michael Haephrati & Ruth Haephrati:
Chapter 9, Making a good point – Pointers at work
Chapter 10, OOPs, we did it again – Object Oriented Programming and C++
Chapter 11, STL here – The C++ Standard Template Library
Chapter 12, C++ conceptual art: Templates and concepts
Chapter 13, Dancing the lambda
Chapter 14, The Power Ranges – C++ Ranges; Date and time handling.
We already shared with you Brain Train, our final exercise, brought to you in Chapter 18 of our book.
Happy to share a Mac demonstration of the same multiplatform source code. Remember, we are happy to offer our loyal readers a special discount price on our book. Use code: BLEARNC++ and this link.
We have developed our own software (in C++ of course) that uses Office Automation and sqlite3 to maintain a database of all editorial comments we receive from Technical Editors about our book‘s chapters.
For example, there is an option to enumerate all comments in the Word document, along with the status (resolved / not resolved), and any additional replies within the thread.
We use the same log function described in Chapter 17, WriteLogFile() to store the information in a log file as well, and in this video, we show what that log file looks like.
For example, here is how we extract the entire text form a given comment in a Word document
OLECHAR *OfficeAutomation::GetTextOfComment(IDispatch *pComment)
{
IDispatch *pCommentRange = nullptr;
// First we get the Range object associated with the text of the comment
VARIANT result;
VariantInit(&result);
m_hr = OLEMethod(DISPATCH_PROPERTYGET, &result, pComment, (LPOLESTR)L"Range", 0);
if (FAILED(m_hr))
{
return nullptr;
}
if (result.vt == VT_DISPATCH && result.pdispVal != nullptr)
{
pCommentRange = result.pdispVal;
// Now we get the text from the Range object
m_hr = OLEMethod(DISPATCH_PROPERTYGET, &result, pCommentRange, (LPOLESTR)L"Text", 0);
pCommentRange->Release();
if (FAILED(m_hr))
{
return nullptr;
}
if (result.vt == VT_BSTR)
{
return result.bstrVal; // comment text returned
}
}
return nullptr;
}
You can also see how AI can be used to help with such automation tasks in this article we published in InfoQ.
Read more about that in Chapter 12 of our book.
The Simpsons is one of the oldest and most beloved cartoon TV shows since 1987, and, unless you’ve been living in a cave, you probably know it. In the opening credits of each chapter, Bart Simpson, as the troublemaker he is, repeatedly writes a sentence on a chalkboard, in the old “write it 100 times” punishment. The thing is, each episode features a different sentence Bat writes on the chalkboard, such as “I will not fake seizures”, “Organ transplants are best left to professionals”, “The principal’s toupee is not a Frisbee”, and – well.. you get the idea.
source – wikipedia
And why are we telling you all this? well, throughout this book, we talked about templates more than a few times, and by now, you should have a pretty good idea about the general concept of templates, and that we use them to store whole part of a changing set of instructions – it can be a formula, a routine, or a function of any kind, as we can fill the changeable parts later.
we can draw an analogy between The Simpson’s opening sequence and the combination of C++ templates and concepts. Templates and concepts serve as frameworks that allow us to create different instances based on a common structure, just like The Simpsons opening follows a consistent structure that sets the stage for the episodes that follow.
Similar to templates, the core components of The Simpsons opening, such as the catchy theme song, the couch gag, and Lisa’s saxophone solo, remain unchanged, representing the constants or invariant parts. They form the foundational structure, just as templates provide a fixed foundation for creating various instances.
However, within this template, there are dynamic or variable elements that change with each episode. For instance, the chalkboard gag presents a different witty phrase written by Bart in every episode, adding a unique touch and personalization. This variability aligns with the customizable parts of a template, demonstrating the adaptability of concepts that allow different types to satisfy the requirements. The Simpsons opening is renowned for its clever references, cultural satire, and irreverent humor, showcasing the creative flexibility that concepts offer. Just as concepts empower developers to define requirements and ensure various types meet those criteria, the Simpsons opening incorporates humor and social commentary, ensuring its freshness and relevance with each iteration. For these reasons, we have selected the Simpsons opening sequence as an analogy to the combination of C++ templates and concepts. It combines constant elements that establish a recognizable structure with variable elements that provide unique and evolving content. This analogy underscores the significance of templates and concepts in programming, emphasizing their role in creating adaptable and flexible code structures.
Let’s dive into the code
#include <iostream>
#include <concepts>
#include <random>
#include <vector>
#include <limits>
#include <string>
// #B
template <typename T>
concept HasCloseFunction = requires(T t)
{
{ t.close() } -> std::same_as<void>;
};
// #C
template <typename T>
concept OpeningSequence = HasOpenFunction<T> && HasCloseFunction<T>;
// #D
template <OpeningSequence T>
void ExecuteOpeningSequence(T& opening)
{
opening.open();
opening.close();
}
// #E
class SimpsonsOpening
{
public:
void open()
{
std::cout << “INT. SIMPSONS LIVING ROOM – DAY\n”;
std::cout << “The Simpsons opening sequence starts.\n”;
std::cout << “We see the following elements:\n”;
for (const auto& element : openingElements)
{
std::cout << “- ” << element << “\n”;
}
std::cout << “CUT TO:\n”;
}
void close()
{
std::cout << “CUT TO BLACK.\n”;
std::cout << “The Simpsons opening sequence ends.\n”;
}
void addOpeningElement(const std::string& element)
{
openingElements.push_back(element);
}
// #F
void randomizeBartActivities()
{
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<size_t> dist(0, bartActivities.size() – 1);
size_t randomIndex = dist(gen);
openingElements[0] += ” ” + bartActivities[randomIndex];
if (bartActivities[randomIndex] == “writing on the chalkboard”)
{
openingElements[0] += ” – ” + GetRandomElement(chalkboardOptions);
}
}
private:
std::vector<std::string> openingElements;
std::vector<std::string> bartActivities =
{
“writing on the chalkboard”,
“skateboarding”,
“pranking Moe’s Tavern”,
“skipping school”,
“eating his homework”
};
std::vector<std::string> chalkboardOptions =
{
“\”I will not skateboard in the hallway\””,
“\”I will not throw spitballs in class\””,
“\”I will not prank call Moe’s Tavern\””,
“\”I will not skip school\””,
“\”I will not eat my homework\””
};
// #G
template <typename T>
T GetRandomElement(const std::vector<T>& vec)
{
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<size_t> dist(0, vec.size() – 1);
return vec[dist(gen)];
}
};
int main()
{
// Introduction
std::cout << “Welcome to the Simpsons Opening Script Generator!\n”;
std::cout << “Press ‘Enter’ to view a Hollywood script style random Simpsons opening script, or ‘q’ to quit.\n”;
bool quit = false;
while (!quit)
{
// Wait for user input
std::string input;
std::getline(std::cin, input);
if (input == “q”)
{
quit = true;
}
else
{
// #H
SimpsonsOpening opening;
opening.addOpeningElement(“Bart”);
opening.addOpeningElement(“Lisa playing the saxophone”);
opening.addOpeningElement(“Maggie being scanned at the grocery store”);
opening.randomizeBartActivities();
ExecuteOpeningSequence(opening);
std::cout << “\nPress Enter to view another Simpsons opening, or ‘q’ to quit.\n”;
}
}
std::cout << “Thank you for using the Simpsons Opening Viewer!\n”;
return 0;
}
#A Concept to check if a type has the member function `open`
#B Concept to check if a type has the member function `close`
#C Concept to check if a type satisfies the requirements of being an opening sequence
#D Function template to execute the opening sequence of a given type
#E Example class representing the opening sequence of The Simpsons
#F Function template for randomizing Bart’s activities and chalkboard writings
#G Utility function to get a random element from a vector
#H Generate a random Simpsons opening
Here is a typical output to expect
.
In 2017, during a meeting with a client, a government agency, we discussed a project we developed and offered them, (in C++, obviously…), they asked us if it was possible to add log entries about a PC user’s activity level. That gave us the idea to add such a feature and in this post, I am going to show you how we can assign a dedicated thread to measure your level of activity while using your PC. That is, in fact, the final exercise of Chapter 15, Multithreading the Needle.
When you wish to monitor the activity level of the user, you would need a separate thread for
that. In our example, we will start the program and constantly display a gauge as a User
Interface element to help us provide that feedback.
Here is the source code, which is compatible with Windows and Mac OSX.
#include <iostream>
#include <atomic>
#include <chrono>
#ifdef _WIN32
#include <Windows.h>
#elif defined(__APPLE__)
#include <ApplicationServices/ApplicationServices.h>
#else
#error Unsupported platform
#endif
// Constants for activity level gauge
constexpr int MinActivityLevel = 0;
constexpr int MaxActivityLevel = 10;
constexpr int GaugeWidth = 20;
constexpr int InactivityThreshold = 5; // Number of seconds of inactivity before decrementing activity level
// Atomic variable to hold the current activity level
std::atomic<int> activityLevel(0);
std::chrono::time_point<std::chrono::system_clock> lastActivityTime;
// Low-level keyboard hook procedure
#ifdef _WIN32
LRESULT CALLBACK KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode >= 0)
{
// Increase the activity level when a key is pressed
if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN)
{
activityLevel = std::min<int>(activityLevel.load() + 1, MaxActivityLevel);
lastActivityTime = std::chrono::system_clock::now(); // Update last activity time
}
else if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP)
{
activityLevel = std::max<int>(activityLevel.load() - 0.5, MinActivityLevel);
lastActivityTime = std::chrono::system_clock::now(); // Update last activity time
}
}
// Call the next hook procedure in the hook chain
return CallNextHookEx(nullptr, nCode, wParam, lParam);
}
#elif defined(__APPLE)
{
CGEventRef KeyboardEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void* refcon)
{
if (type == kCGEventKeyDown || type == kCGEventFlagsChanged)
{
activityLevel = std::min<int>(activityLevel.load() + 1, MaxActivityLevel);
lastActivityTime = std::chrono::system_clock::now(); // Update last activity time
}
else if (type == kCGEventKeyUp)
{
activityLevel = std::max<int>(activityLevel.load() - 0.5, MinActivityLevel);
lastActivityTime = std::chrono::system_clock::now(); // Update last activity time
}
return event;
}
}
#endif
// Low-level mouse hook procedure
#ifdef _WIN32
LRESULT CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode >= 0)
{
// Increase the activity level when the mouse moves
if (wParam == WM_MOUSEMOVE)
{
activityLevel = std::min<int>(activityLevel.load() + 0.5, MaxActivityLevel);
lastActivityTime = std::chrono::system_clock::now(); // Update last activity time
}
else if (wParam == WM_LBUTTONDOWN || wParam == WM_RBUTTONDOWN || wParam == WM_MBUTTONDOWN)
{
activityLevel = std::min<int>(activityLevel.load() + 1, MaxActivityLevel);
lastActivityTime = std::chrono::system_clock::now(); // Update last activity time
}
}
// Call the next hook procedure in the hook chain
return CallNextHookEx(nullptr, nCode, wParam, lParam);
}
#elif defined (__APPLE__)
CGEventRef MouseEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void* refcon)
{
if (type == kCGEventMouseMoved || type == kCGEventLeftMouseDown || type == kCGEventRightMouseDown)
{
activityLevel = std::min<int>(activityLevel.load() + 1, MaxActivityLevel);
lastActivityTime = std::chrono::system_clock::now(); // Update last activity time
}
else if (type == kCGEventLeftMouseUp || type == kCGEventRightMouseUp)
{
activityLevel = std::max<int>(activityLevel.load() - 0.5, MinActivityLevel);
lastActivityTime = std::chrono::system_clock::now(); // Update last activity time
}
return event;
}
#endif
// Function to calculate activity level based on keyboard and mouse events
void calculateActivityLevel()
{
#ifdef _WIN32
// Set low-level keyboard hook
HHOOK keyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProc, nullptr, 0);
if (!keyboardHook)
{
std::cerr << "Failed to set keyboard hook." << std::endl;
return;
}
// Set low-level mouse hook
HHOOK mouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProc, nullptr, 0);
if (!mouseHook)
{
std::cerr << "Failed to set mouse hook." << std::endl;
return;
}
// Message loop to keep the hooks active
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// Unhook the keyboard
// Unhook the keyboard hook
UnhookWindowsHookEx(keyboardHook);
// Unhook the mouse hook
UnhookWindowsHookEx(mouseHook);
#elif defined( __APPLE__)
// Create the event tap for keyboard events
CGEventMask keyboardEventMask = CGEventMaskBit(kCGEventKeyDown) | CGEventMaskBit(kCGEventKeyUp) | CGEventMaskBit(kCGEventFlagsChanged);
CFMachPortRef keyboardEventTap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault, keyboardEventMask, KeyboardEventCallback, nullptr);
if (!keyboardEventTap)
{
std::cerr << "Failed to create keyboard event tap." << std::endl;
return;
}
// Create a run loop source from the keyboard event tap
CFRunLoopSourceRef keyboardRunLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, keyboardEventTap, 0);
if (!keyboardRunLoopSource)
{
std::cerr << "Failed to create keyboard run loop source." << std::endl;
CFRelease(keyboardEventTap);
return;
}
// Add the keyboard run loop source to the current run loop
CFRunLoopAddSource(CFRunLoopGetCurrent(), keyboardRunLoopSource, kCFRunLoopDefaultMode);
// Enable the keyboard event tap
CGEventTapEnable(keyboardEventTap, true);
// Create the event tap for mouse events
CGEventMask mouseEventMask = CGEventMaskBit(kCGEventMouseMoved) | CGEventMaskBit(kCGEventLeftMouseDown) | CGEventMaskBit(kCGEventRightMouseDown) | CGEventMaskBit(kCGEventLeftMouseUp) | CGEventMaskBit(kCGEventRightMouseUp);
CFMachPortRef mouseEventTap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault, mouseEventMask, MouseEventCallback, nullptr);
if (!mouseEventTap)
{
std::cerr << "Failed to create mouse event tap." << std::endl;
return;
}
// Create a run loop source from the mouse event tap
CFRunLoopSourceRef mouseRunLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, mouseEventTap, 0);
if (!mouseRunLoopSource)
{
std::cerr << "Failed to create mouse run loop source." << std::endl;
CFRelease(mouseEventTap);
return;
}
// Add the mouse run loop source to the current run loop
CFRunLoopAddSource(CFRunLoopGetCurrent(), mouseRunLoopSource, kCFRunLoopDefaultMode);
// Enable the mouse event tap
CGEventTapEnable(mouseEventTap, true);
// Run the current run loop
CFRunLoopRun();
#endif
}
// Function to display the color gradient gauge for a given activity level
// Function to display the color gradient gauge for a given activity level
void displayColorGradientGauge(int activityLevel)
{
const int GaugeWidth = 20;
// Define the color codes for gradient display
const std::string ResetColor = "\033[0m";
// Calculate the number of filled and empty cells for the activity level gauge
int filledCells = std::round(static_cast<double>(GaugeWidth * activityLevel) / 10);
int emptyCells = GaugeWidth - filledCells;
// Display the activity level gauge
std::cout << "[";
// Display the filled cells with color gradient
for (int i = 0; i < filledCells; ++i)
{
double percentage = static_cast<double>(i) / (GaugeWidth - 1);
if (percentage <= 0.5)
{
// Gradient from light green to yellow
int greenValue = static_cast<int>(255 - 128 * percentage / 0.5);
int redValue = static_cast<int>(255 * percentage / 0.5);
std::cout << "\033[38;2;" << redValue << ";" << greenValue << ";" << 0 << "m#";
}
else
{
// Gradient from yellow to red
int redValue = 255;
int greenValue = static_cast<int>(255 * (1.0 - percentage) / 0.5);
std::cout << "\033[38;2;" << redValue << ";" << greenValue << ";" << 0 << "m#";
}
}
// Display the empty cells in the default color
std::cout << ResetColor;
for (int i = 0; i < emptyCells; ++i)
{
std::cout << "-";
}
std::cout << "]" << std::endl;
}
// Function to display the activity gauge on the console
void displayActivityGauge()
{
int previousLevel = -1; // Store the previous activity level
lastActivityTime = std::chrono::system_clock::now();
while (true)
{
int currentLevel = activityLevel.load(); // Read the current activity level
// Check for inactivity
std::chrono::time_point<std::chrono::system_clock> currentTime = std::chrono::system_clock::now();
std::chrono::duration<double> elapsedTime = currentTime - lastActivityTime;
if (elapsedTime.count() >= InactivityThreshold)
{
activityLevel = std::max<int>(activityLevel.load() - 1, MinActivityLevel);
lastActivityTime = currentTime; // Update last activity time
}
// Clear the console
#ifdef _WIN32
system("cls");
#elif defined(__APPLE__)
system("clear");
#endif
// Display the activity level gauge
std::cout << "System Activity Level Gauge" << std::endl;
displayColorGradientGauge(currentLevel);
previousLevel = currentLevel; // Update the previous activity level
// Sleep
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
int main()
{
// Start the activity level calculation thread
std::thread calculationThread(calculateActivityLevel);
// Start the activity gauge display thread
std::thread displayThread(displayActivityGauge);
// Wait for the threads to finish
calculationThread.join();
displayThread.join();
return 0;
}
As explained in Chapter 12 of our book, Learning C++, the idea of this exercise is to develop a Las Vegas-style slot machine game. Slot machines allow the player to press a button and get 3 or more images. The goal is to be lucky enough to get identical images in identical colors. Since our code snippets throughout this book are console based, we will be using ASCII art images. For simplicity, the images are ones from a deck of cards, which creates a somewhat new game that combines a cards deck game and a slot machine.
What is ASCII art
ASCII art is a graphic design technique that uses printable characters from the ASCII standard to create images and designs. We discussed ASCII in Chapter 7. In a nutshell, ASCII art offers an alternative to drawing an image with lines and shapes. In ASCII art, you build the image by arranging ASCII characters in a way that the different characters and symbols take a form that resembles the subject you want to depict. Essentially, the ASCII characters serve as the ‘pixels’ of the artwork.
Our Program
You can see our program on our YouTube channel. Here is how it looks. Here is the source code of our program, and how it works.
#include <algorithm>
#include <chrono>
#include <iostream>
#include <random>
#include <vector>
#undef _WIN32
#ifdef _WIN32
#include <conio.h>
#else
#include <termios.h>
#include <unistd.h>
#endif
#ifdef _WIN32
#define CLEAR_SCREEN system("cls")
#else
#define CLEAR_SCREEN system("clear")
#endif
enum class Color
{
DEFAULT,
RED,
GREEN,
BLUE,
YELLOW,
MAGENTA,
CYAN
};
struct Pixel
{
char character;
Color color;
};
void setTextColor(Color color)
{
switch (color)
{
case Color::DEFAULT:
std::cout << "\033[0m";
break;
case Color::RED:
std::cout << "\033[31m";
break;
case Color::GREEN:
std::cout << "\033[32m";
break;
case Color::BLUE:
std::cout << "\033[34m";
break;
case Color::YELLOW:
std::cout << "\033[33m";
break;
case Color::MAGENTA:
std::cout << "\033[35m";
break;
case Color::CYAN:
std::cout << "\033[36m";
break;
}
}
void displayMatrix(const std::vector<std::vector<Pixel>>& matrix)
{
for (const auto& row : matrix)
{
for (const Pixel& pixel : row)
{
setTextColor(pixel.color);
std::cout << pixel.character;
}
std::cout << std::endl;
}
setTextColor(Color::DEFAULT);
}
std::vector<std::string> generateRandomImage()
{
// #A
std::vector<std::vector<std::string>> images =
{
{" _______ ", "|A |", "| |", "| o |", "| |",
"|_______|"},
{" _______ ", "|2 |", "| o |", "| |", "| o |",
"|_______|"},
{" _______ ", "|3 |", "| o |", "| o |", "| o |",
"|_______|"},
{" _______ ", "|4 |", "| o o |", "| |", "| o o |",
"|_______|"},
{" _______ ", "|5 |", "| o o |", "| o |", "| o o |",
"|_______|"},
{" _______ ", "|6 |", "| o o |", "| o o |", "| o o |",
"|_______|"},
{" _______ ", "|7 |", "| o o o |", "| |", "| o o o |",
"|_______|"},
{" _______ ", "|8 |", "| o o o |", "| o o |", "| o o o |",
"|_______|"},
{" _______ ", "|9 |", "| o o o |", "| o o o |", "| o o o |",
"|_______|"},
{" _______ ", "|10 |", "| o o o |", "|o o o o|", "| o o o |",
"|_______|"},
{" _______ ", "|J |", "| o |", "| o |", "| o o |",
"|_______|"},
{" _______ ", "|Q |", "| o |", "| o o o |", "| o o |",
"|_______|"},
{" _______ ", "|K |", "| o o |", "| o o o |", "| o o |",
"|_______|"},
{" _______ ", "|A |", "| /\\ |", "| / \\|", "| /____\|",
"|_______|"},
{" _______ ", "|2 |", "| /\\ /\|", "| \\/ \\|", "| |",
"|_______|"},
{" _______ ", "|3 |", "| /\\ |", "| |", "| \\/ |",
"|_______|"},
{" _______ ", "|4 |", "| /\\ /|", "| |", "|\\/ \\|",
"|_______|"},
{" _______ ", "|5 |", "| /\\ /|", "| /\\ |", "|\\/ \\|",
"|_______|"},
{" _______ ", "|6 |", "| /\\ /|", "| /\\ /|", "|\\/ \\|",
"|_______|"},
{" _______ ", "|7 |", "| /\\ /\|", "| |", "| /\\ |",
"|_______|"},
{" _______ ", "|8 |", "| /\\ /\|", "| / \\ |", "| \\/ |",
"|_______|"},
{" _______ ", "|9 |", "| /\\ /\|", "| /\\ |", "| \\/ |",
"|_______|"},
{" _______ ", "|10 |", "| /\\ |", "| / \\ |", "|/____\\|",
"|_______|"},
{" _______ ", "|J |", "| /\\ |", "| / \\|", "|/____\\|",
"|_______|"},
{" _______ ", "|Q |", "| /\\ |", "| / \\|", "|/ \\|",
"|_______|"},
{" _______ ", "|K |", "| /\\ |", "| / \\|", "|/____\\|",
"|_______|"},
{" _______ ", "|A |", "| /\\ |", "| /__\\ |", "|/ \\|",
"|_______|"},
{" _______ ", "|2 |", "| /\\ |", "| /_\\ |", "|/____\\|",
"|_______|"},
{" _______ ", "|3 |", "| /\\ |", "| \\_/ |", "|/____\\|",
"|_______|"},
{" _______ ", "|4 |", "| /\\ |", "| //_\\ |", "|/____\\|",
"|_______|"},
{" _______ ", "|5 |", "| /\\ |", "| //_\\ |", "|/____\\|",
"|_______|"},
{" _______ ", "|6 |", "| /\\ |", "| //_\\ |", "|/____\\|",
"|_______|"},
{" _______ ", "|7 |", "| /\\ |", "| //_\\ |", "|/____\\|",
"|_______|"},
{" _______ ", "|8 |", "| /\\ |", "| //_\\ |", "|/____\\|",
"|_______|"},
{" _______ ", "|9 |", "| /\\ |", "| //_\\ |", "|/____\\|",
"|_______|"},
{" _______ ", "|10 |", "| /\\ |", "| //_\\ |", "|/____\\|",
"|_______|"},
{" _______ ", "|J |", "| /\\ |", "| //_\\ |", "|/____\\|",
"|_______|"},
{" _______ ", "|Q |", "| /\\ |", "| //_\\ |", "|/____\\|",
"|_______|"},
{" _______ ", "|K |", "| /\\ |", "| //_\\ |", "|/____\\|",
"|_______|"},
{" _______ ", "|A |", "| _ |", "| | | |", "| |",
"|_______|"},
{" _______ ", "|2 |", "| _ |", "| |_) |", "| / |",
"|_______|"},
{" _______ ", "|3 |", "| __ |", "| |_ \\|", "|____/\\|",
"|_______|"},
{" _______ ", "|4 |", "| |_ _||", "| | |", "| | |",
"|_______|"},
{" _______ ", "|5 |", "| |__ |", "| | |", "| |__| |",
"|_______|"},
{" _______ ", "|6 |", "| |__ |", "| |__| |", "| |__| |",
"|_______|"},
{" _______ ", "|7 |", "| |___ |", "| / /|", "| / / |",
"|_______|"},
{" _______ ", "|8 |", "| /\\ |", "| / \\ |", "| \\ / |",
"|_______|"},
{" _______ ", "|9 |", "| /\\ |", "| |__| |", "| / |",
"|_______|"},
{" _______ ", "|10 |", "| /\\ |", "| |__) |", "|____ |",
"|_______|"},
{" _______ ", "|J |", "| /\\|", "| / / |", "| / / |",
"|_/_____|"},
{" _______ ", "|Q |", "| /\\|", "| / \|", "| |\\__|",
"|_/_____|"},
{" _______ ", "|K |", "| \\_/|", "| / \\|", "| | \|",
"|_/_____|"}};
std::random_device rd;
std::mt19937 rng(rd());
std::shuffle(images.begin(), images.end(), rng);
std::vector<std::string> selectedImages;
int randomIndex = rand() % images.size();
const std::vector<std::string>& image = images[randomIndex];
for (const std::string& line : image)
{
selectedImages.push_back(line);
}
images.erase(images.begin() + randomIndex);
return selectedImages;
}
std::vector<std::vector<Pixel>>
composeCombinedImage(const std::vector<std::string>& image1,
const std::vector<std::string>& image2,
const std::vector<std::string>& image3, Color color1,
Color color2, Color color3)
{
// #B
int maxLines = std::max({image1.size(), image2.size(), image3.size()});
// #C
int maxWidth =
std::max({image1[0].size(), image2[0].size(), image3[0].size()});
std::vector<std::vector<Pixel>> combinedImage(maxLines);
// #D
for (int i = 0; i < image1.size(); ++i)
{
for (char c : image1[i])
{
combinedImage[i].push_back({c, color1});
}
// #E
int spacesToAdd = maxWidth - image1[i].size();
for (int j = 0; j < spacesToAdd; ++j)
{
combinedImage[i].push_back({' ', Color::DEFAULT});
}
}
// #F
for (int i = image1.size(); i < maxLines; ++i)
{
for (int j = 0; j < maxWidth; ++j)
{
combinedImage[i].push_back({' ', Color::DEFAULT});
}
}
// #G
for (int i = 0; i < image2.size(); ++i)
{
for (char c : image2[i])
{
combinedImage[i].push_back({c, color2});
}
// #H
int spacesToAdd = maxWidth - image2[i].size();
for (int j = 0; j < spacesToAdd; ++j)
{
combinedImage[i].push_back({' ', Color::DEFAULT});
}
}
// #I
for (int i = image2.size(); i < maxLines; ++i)
{
for (int j = 0; j < maxWidth; ++j)
{
combinedImage[i].push_back({' ', Color::DEFAULT});
}
}
// #J
for (int i = 0; i < image3.size(); ++i)
{
for (char c : image3[i])
{
combinedImage[i].push_back({c, color3});
}
// #K
int spacesToAdd = maxWidth - image3[i].size();
for (int j = 0; j < spacesToAdd; ++j)
{
combinedImage[i].push_back({' ', Color::DEFAULT});
}
}
return combinedImage;
}
bool checkWin(const std::vector<std::string>& image1,
const std::vector<std::string>& image2,
const std::vector<std::string>& image3, Color color1,
Color color2, Color color3)
{
return (image1 == image2 && image2 == image3 && color1 == color2 &&
color2 == color3);
}
int main()
{
srand(static_cast<unsigned>(time(0)));
std::cout << "Welcome to the Slot Machine Game!" << std::endl;
std::cout << "Press any key to start..." << std::endl;
while (true)
{
_getch();
CLEAR_SCREEN;
// #L
std::vector<std::string> image1 = generateRandomImage();
std::vector<std::string> image2 = generateRandomImage();
std::vector<std::string> image3 = generateRandomImage();
// #M
Color color1 =
static_cast<Color>(rand() % static_cast<int>(Color::CYAN) + 1);
Color color2 =
static_cast<Color>(rand() % static_cast<int>(Color::CYAN) + 1);
Color color3 =
static_cast<Color>(rand() % static_cast<int>(Color::CYAN) + 1);
// #N
std::vector<std::vector<Pixel>> combinedImage = composeCombinedImage(
image1, image2, image3, color1, color2, color3);
// #O
displayMatrix(combinedImage);
// #P
if (checkWin(image1, image2, image3, color1, color2, color3))
{
std::cout << "Congratulations! You win!" << std::endl;
}
else
{
std::cout << "Sorry, better luck next time!" << std::endl;
}
std::cout << std::endl;
}
return 0;
}
Here are the code annotations:
#A Array of ASCII art images
#B Determine the maximum number of lines among the three images
#C Determine the maximum width among the three images
#D Add pixels from image1
#E Add spaces to match the maximum width
#F Add empty pixels to align with image2 and image3
#G Add pixels from image2
#H Add spaces to match the maximum width
#I Add empty pixels to align with image3
#J Add pixels from image3
#K Add spaces to match the maximum width
#L Generate three random images
#M Generate three random colors
#N Compose the combined image
#O Display the combined image
#P Check if the user wins
We have created a new YouTube channel where we intend to publish clips showing code listings from the book and how the programs actually look and feel.
Our amazing maze program is shown in this video.
Our final exercise, Brain-Train is shown here.
More videos to come soon…