← thecodex.expert · The Codex Family of Knowledge
Tier 0 · Absolute Beginner · C++ Project

Number Guessing Game

Your very first C++ program with a memory and a decision: the computer picks a secret number, and you guess until you get it. You will learn how a program asks a question, checks an answer, and repeats — and how to split that logic so it can actually be tested.

🧠 Teaches how to think spoonfed, every age Last verified:

1 The Problem

We want a tiny game: the computer thinks of a number between 1 and 100, and the player keeps guessing. After each guess the computer says “too high” or “too low” until the player gets it right. Simple — but it teaches the three things every program does: ask, decide, and repeat.

Where this shows up in real life: every time an app checks your password, validates a form, or keeps asking until you give a valid answer, it is doing exactly this — take input, compare it to something, and loop until the condition is met. Learn it here in 15 lines, and you have learned the heartbeat of almost every program.

2 How to Think About It

Before writing any code, picture the game as a loop with a decision inside it. You do not need to know C++ yet — you just need to know the shape of what happens:

The plan — in plain English
1. Pick a secret number once, at the start. → 2. Ask the player to guess. → 3. Compare the guess to the secret: lower, higher, or equal? → 4. If not equal, go back to step 2. If equal, celebrate and stop. That is the whole program. Everything below is just saying this in C++.

Too low

Too high

Correct!

Computer picks a secret number 1-100

Ask the player to guess

Is the guess right?

Say 'too low'

Say 'too high'

Show how many guesses it took

Game over

3 The Build — explained part by part

Here is the complete game, split into a header (NumberGuessingGame.hpp), the testable logic (NumberGuessingGame.cpp), and a tiny main.cpp that wires it to real std::cin/std::cout. Read each part’s plain-English note below it.

C++NumberGuessingGame.hpp / NumberGuessingGame.cpp / main.cpp
#pragma once
#include <istream>
#include <ostream>

// Plays one full game of number-guessing against `secret`, reading guesses
// from `in` and writing feedback to `out`. Returns the number of guesses it
// took to win, or -1 if the input ran out before a correct guess. Taking
// std::istream&/std::ostream& instead of touching std::cin/std::cout
// directly is what lets the tests below feed it a std::istringstream and
// capture output in a std::ostringstream -- no real terminal involved.
int play_game(int secret, std::istream &in, std::ostream &out);

#include "NumberGuessingGame.hpp"
#include <string>

int play_game(int secret, std::istream &in, std::ostream &out) {
    std::string line;
    int guesses = 0;
    while (std::getline(in, line)) {
        int guess;
        try {
            guess = std::stoi(line);
        } catch (const std::exception &) {
            continue; // not a whole number; ignore the line rather than crash
        }
        guesses++;
        if (guess < secret) {
            out << "Too low.\n";
        } else if (guess > secret) {
            out << "Too high.\n";
        } else {
            out << "Correct! You guessed it in " << guesses << " tries.\n";
            return guesses;
        }
    }
    return -1; // ran out of input without a correct guess
}

#include "NumberGuessingGame.hpp"
#include <iostream>
#include <random>

int main() {
    std::cout << "I'm thinking of a number between 1 and 100.\n";
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<int> dist(1, 100);
    int secret = dist(gen);
    play_game(secret, std::cin, std::cout);
    return 0;
}
⚠ No in-browser playground here
C++ compiles to a real, native binary, so unlike the Python version of this project there is no editor above you can run in the browser. Copy the code below and run it on your own machine — it takes seconds once a C++17-or-newer compiler like g++ or clang++ is installed.
What each part does — in plain words
int play_game(int secret, std::istream &in, std::ostream &out) — the whole game logic takes its input and output as C++ stream references instead of reaching for std::cin/std::cout directly. That is exactly what lets the tests below feed it a std::istringstream and capture its output in a std::ostringstream — no temporary files, no real terminal involved, and no separate library needed the way C’s fmemopen workaround was.

The header/source split — NumberGuessingGame.hpp declares the function; NumberGuessingGame.cpp defines it. Even though C++ classes can bundle declaration and definition together, a plain free function still benefits from the same C-style split: any other .cpp file (the real main.cpp, or the test file) can #include the header and link against the compiled logic without ever seeing its source.

try { guess = std::stoi(line); } catch (const std::exception &) { continue; } — std::stoi throws std::invalid_argument on text that is not a number, instead of C’s atoi, which silently returns 0 for garbage input. Catching the exception and skipping the line is how this project chooses to be more honest than the C version about a malformed guess.

std::random_device rd; std::mt19937 gen(rd()); — modern C++’s standard way to get a real random number: random_device is a genuine, non-deterministic entropy source on this platform (confirmed by checking its .entropy() is non-zero), used once to seed a fast mt19937 generator for repeated draws.
Common mistakes — and how to avoid them
✗ Constructing a new std::mt19937 (or worse, calling std::random_device) inside the loop for every guess — reseeding constantly is wasteful and can even reduce randomness quality on some platforms.
✓ Seed the generator once, before the loop starts, and reuse it for every draw, as main does here.
✗ Using atoi out of old C habit to parse the guess — it gives no way to detect a malformed input, silently returning 0.
✓ Use std::stoi inside a try/catch (as this project does), which throws a catchable exception instead of guessing.

4 Test & Prove Each Part

How do we know the game works without playing it by hand? We write small tests — each one checks one rule and proves it. Unlike C, this sandbox cannot reach a package registry to fetch Catch2 or GoogleTest either, so this project uses the same small, genuinely common practice C’s projects on this site use: plain assert() checks in a tiny hand-written harness — a perfectly defensible choice for a small header-only utility, and covered as one real option in the course’s Testing lesson alongside Catch2 and GoogleTest.

Guessing the secret on the first try counts as 1 guess
The game correctly narrows down with a mix of too-low and too-high guesses
Running out of input without a correct guess is reported, not silently ignored
The feedback text says exactly "Too low.", "Too high.", and "Correct!" at the right moments
C++test_NumberGuessingGame.cpp
#include "NumberGuessingGame.hpp"
#include <cassert>
#include <iostream>
#include <sstream>
#include <string>

#define RUN(name) do { name(); std::cout << "PASS: " << #name << "\n"; } while (0)

static void guessing_the_secret_on_the_first_try_counts_as_one_guess() {
    std::istringstream in("42\n");
    std::ostringstream out;
    int guesses = play_game(42, in, out);
    assert(guesses == 1);
}

static void the_game_narrows_down_with_a_mix_of_low_and_high_guesses() {
    std::istringstream in("10\n90\n50\n");
    std::ostringstream out;
    int guesses = play_game(50, in, out);
    assert(guesses == 3);
}

static void running_out_of_input_without_a_correct_guess_is_reported() {
    std::istringstream in("10\n20\n");
    std::ostringstream out;
    int guesses = play_game(99, in, out);
    assert(guesses == -1);
}

static void feedback_text_is_exact() {
    std::istringstream in("10\n90\n50\n");
    std::ostringstream out;
    play_game(50, in, out);
    std::string text = out.str();
    assert(text.find("Too low.") != std::string::npos);
    assert(text.find("Too high.") != std::string::npos);
    assert(text.find("Correct! You guessed it in 3 tries.") != std::string::npos);
}

int main() {
    RUN(guessing_the_secret_on_the_first_try_counts_as_one_guess);
    RUN(the_game_narrows_down_with_a_mix_of_low_and_high_guesses);
    RUN(running_out_of_input_without_a_correct_guess_is_reported);
    RUN(feedback_text_is_exact);
    std::cout << "All tests passed.\n";
    return 0;
}

Compile and run with g++ -std=c++20 -o test_run NumberGuessingGame.cpp test_NumberGuessingGame.cpp && ./test_run. Note that main.cpp is deliberately left out of this compile line: it has its own main, which would collide with the test harness’s main in test_NumberGuessingGame.cpp.

5 The Interface

Even a tiny program has an interface — the way a person interacts with it. Here is its contract, documented plainly, the same way a professional would describe any tool.

INPUTYour guessa whole number 1–100, typed by the player
What it expects
A number like 42, typed and then Enter pressed.
OUTPUTFeedbackone of three replies
What it returns
"Too low."
"Too high."
"Correct! You guessed it in N tries."

6 Run It & Automate It

Save the code as NumberGuessingGame.hpp / NumberGuessingGame.cpp / main.cpp and compile it with g++ — that turns your source directly into a native executable for your machine. No separate runtime needed: the compiled binary runs on its own.

Run it locally
g++ -std=c++20 -o guess main.cpp NumberGuessingGame.cpp && ./guess
Then type a number, press Enter, and follow the “too high / too low” hints until you win.

A CI tool like Jenkins runs the same compile-then-test-then-check-for-leaks steps automatically whenever the code changes — every line below has a plain explanation.

What you should see when it works
Terminala real run
I'm thinking of a number between 1 and 100.
50
Too high.
25
Too low.
37
Too high.
31
Correct! You guessed it in 4 tries.
If it breaks — how to fix it
🚨 error: ‘stoi’ is not a member of ‘std’
Add #include <string> — std::stoi lives there, not in <iostream>.
🚨 The game accepts a guess but never says anything.
Check the comparison branches all end in a real out << statement; a silently empty branch means a guess vanishes with no feedback.
GroovyJenkinsfile
// Jenkinsfile — compiles, tests, and checks for leaks on every change.
pipeline {
    agent any

    stages {
        stage('Get the code') {
            // download the latest code
            steps { checkout scm }
        }
        stage('Compile') {
            steps {
                // confirm a compiler is installed
                sh 'g++ --version'
                // compile with strict warnings on
                sh 'g++ -std=c++20 -Wall -Wextra -o app *.cpp'
            }
        }
        stage('Run the tests') {
            steps {
                // prints PASS/FAIL, exits non-zero on failure
                sh './app'
            }
        }
        stage('Check for memory leaks') {
            steps {
                // fails the build on any leak or invalid access
                sh 'valgrind --error-exitcode=1 --leak-check=full ./app'
            }
        }
    }

    post {
        success { echo 'All tests passed, no leaks found.' }
        failure { echo 'A test or Valgrind check failed — see above.' }
    }
}
🎯 Try this next — make it yours
  1. Limit the number of guesses. Print “Out of guesses!” after, say, 7 tries. (Teaches: a counter compared against a constant.)
  2. Add a difficulty setting. Let the player choose the range (1–10, 1–1000). (Teaches: passing extra parameters through play_game.)
  3. Use Catch2 or GoogleTest. If you have network access, replace the hand-written harness and compare the experience. (Teaches: what a real test framework buys you — fixtures, matchers, readable failure output.)
What you learned
You learned to split game logic from real I/O using stream references, why std::stoi with exception handling beats C’s silent-failure atoi, and how to seed <random> properly for real randomness. Related: Standard Library, Exceptions.