Just finished my TripleX game, decided to make it colorful

I went on a bit of a terminal ANSI color spree.

Note: I will be using Unreal’s conventions when I am actually in Unreal

#include <iostream>
#include <string>
#include <array>
#include <chrono>
#include <random>

#if WIN32
#include <windows.h>

bool EnableAnsiEscapes() {
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    if(hConsole != INVALID_HANDLE_VALUE) {
        DWORD currentMode = 0;
        if(GetConsoleMode(hConsole, &currentMode)) {
            SetConsoleMode(hConsole, currentMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
        }
    }

    // Used to return false on error, but that breaks support for terminals like MINGW, so if the Windows version
    // does not support ansi escape codes, ANSI support will not be disabled, so ANSI escapes will still be printed.
    return true;
}
#else
bool EnableAnsiEscapes() { return true; }
#endif //WIN32

bool ansiSupported = true;
std::mt19937 random;

namespace Ansi {
    inline std::string Reset() {
        if (!ansiSupported)
            return "";

        return "\033[0m";
    }

    inline std::string Escape(const std::string& flag, const std::string& val) {
        if (!ansiSupported)
            return "";

        return "\x1B[" + flag + val + "m";
    }

    std::string Clear() {
        if (!ansiSupported)
            return "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n";

        // Clear whole screen and reset cursor to 0, 0
        return Reset() + "\x1B[2J\x1B[0;0H";
    }

    std::string Bg(int val) {
        return Escape("48;5;", std::to_string(val));
    }

    std::string Fg(int val) {
        return Escape("38;5;", std::to_string(val));
    }
}

long GetTime() {
    auto now = std::chrono::system_clock::now();
    return std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
}

void DrawCorrect() {
    auto start = GetTime();
    bool redraw = true;

    bool invert = false;

    auto last = GetTime();

    while (GetTime() - start < 1500) {
        if (GetTime() - last > 500) {
            invert = !invert;
            redraw = true;
        }

        if (redraw) {
            std::cout << Ansi::Clear();

            if (!invert)
                std::cout << Ansi::Fg(2);
            else
                std::cout << Ansi::Fg(0) << Ansi::Bg(2);

            std::cout << R"(/=======================================\)" << "\n"
                      << R"(|    _____                         _    |)" << "\n"
                      << R"(|   / ____|                       | |   |)" << "\n"
                      << R"(|  | |     ___  _ __ _ __ ___  ___| |_  |)" << "\n"
                      << R"(|  | |    / _ \| '__| '__/ _ \/ __| __| |)" << "\n"
                      << R"(|  | |___| (_) | |  | | |  __/ (__| |_  |)" << "\n"
                      << R"(|   \_____\___/|_|  |_|  \___|\___|\__| |)" << "\n"
                      << R"(|                                       |)" << "\n"
                      << R"(\=======================================/)" << Ansi::Reset() << std::endl;

            last = GetTime();
            redraw = false;
        }
    }
}

void DrawWrong(std::array<int, 3> numbers) {
    std::string correctNumbers;
    for(int num : numbers) {
        if(!correctNumbers.empty())
            correctNumbers += ", ";

        correctNumbers += std::to_string(num);
    }

    auto start = GetTime();
    bool redraw = true;

    bool invert = false;

    auto last = GetTime();

    while (GetTime() - start < 2500) {
        if (GetTime() - last > 500) {
            invert = !invert;
            redraw = true;
        }

        if (redraw) {
            std::cout << Ansi::Clear();
            std::cout << "The correct numbers were: " << correctNumbers << "\n\n";

            if (!invert)
                std::cout << Ansi::Fg(1);
            else
                std::cout << Ansi::Fg(0) << Ansi::Bg(1);

            std::cout << R"(/======================================\)" << "\n"
                      << R"(|  __          __                      |)" << "\n"
                      << R"(|  \ \        / /                      |)" << "\n"
                      << R"(|   \ \  /\  / / __ ___  _ __   __ _   |)" << "\n"
                      << R"(|    \ \/  \/ / '__/ _ \| '_ \ / _` |  |)" << "\n"
                      << R"(|     \  /\  /| | | (_) | | | | (_| |  |)" << "\n"
                      << R"(|      \/  \/ |_|  \___/|_| |_|\__, |  |)" << "\n"
                      << R"(|                               __/ |  |)" << "\n"
                      << R"(\======================================/)" << Ansi::Reset() << std::endl;

            last = GetTime();
            redraw = false;
        }
    }
}

bool SimulateGame(int level, int lives) {
    std::cout << Ansi::Fg(3) << "You are in a bank trying to break into a " << Ansi::Fg(5) << "level " << level
              << Ansi::Fg(3) << " vault... \n"
              << Ansi::Fg(3) << "You have " << Ansi::Fg(5) << lives << Ansi::Fg(3) << " lives remaining.\n"
              << "Enter the correct code to continue..." << Ansi::Reset() << "\n\n";

    // Generates a random distribution based on level and how many lives the player lost
    std::uniform_int_distribution<int> distrib(level + (level - 1), level * 2 - ((3 - lives) / 3));
    const std::array<int, 3> numbers{distrib(random), distrib(random), distrib(random)};

    const int sum = numbers[0] + numbers[1] + numbers[2];
    const int product = numbers[0] * numbers[1] * numbers[2];

    std::cout << Ansi::Fg(3) << "* There are three numbers in the code\n"
              << "* The codes add-up to: " << Ansi::Fg(5) << sum << Ansi::Fg(3) << "\n"
              << "* The codes multiply to give: " << Ansi::Fg(5) << product << Ansi::Fg(3) << "\n"
              << "\nPlease enter your guess: " << Ansi::Fg(5);
    std::flush(std::cout);

    std::array<int, 3> guesses{};
    for (int& guess : guesses) {
        std::cin >> guess;
        std::cin.clear(); // Clear any potential errors
    }

    const int guessSum = guesses[0] + guesses[1] + guesses[2];
    const int guessProduct = guesses[0] * guesses[1] * guesses[2];

    bool wasCorrect = false;
    std::cout << "\n";

    if (sum == guessSum && product == guessProduct) {
        DrawCorrect();
        wasCorrect = true;
    } else {
        DrawWrong(numbers);
    }

    std::cout << Ansi::Reset() << std::endl;
    return wasCorrect;
}

int main() {
#if WIN32
    ansiSupported = EnableAnsiEscapes();
#endif //WIN32

    // Initialize randomizer
    random = std::mt19937(GetTime());

    constexpr int maxLevel = 10;

    int level = 1;
    int lives = 3;

    while (level <= maxLevel) {
        std::cout << Ansi::Clear();
        bool correct = SimulateGame(level, lives);
        std::cin.ignore(std::cin.rdbuf()->in_avail());
        std::cout << Ansi::Clear();

        if (correct)
            level++;
        else {
            lives--;
        }

        if (lives <= 0) {
            std::cout << Ansi::Fg(1) << "The police have found you, game over!";
            break;
        }
    }

    if (level > maxLevel)
        std::cout << Ansi::Fg(2) << "You have successfully opened the last vault, the riches are yours!";

    // Always reset the console formatting at the end, some consoles don't do it for us
    // looking at you powershell
    std::cout << Ansi::Reset() << std::endl;

    std::cin.clear();
    std::cin.ignore(std::cin.rdbuf()->in_avail());
    std::cout << "\nPress enter to exit...";
    std::cin.get(); // Prevent terminal from closing until player presses enter
    return 0;
}

This should work correctly on all platforms that support ANSI color codes (including Windows 10), unless I missed something obvious.

2 Likes

The color really makes a difference!

Privacy & Terms