Project · after Level 1

Number Guessing Game 🎯

The computer picks a secret number. You guess. It tells you higher or lower until you get it. Simple to describe, and it will teach you more than the last three lessons combined, because this time nobody is holding the pen.

📋 The spec

Build a program that:

  • Picks a secret whole number between 1 and 100.
  • Asks the player to guess, over and over.
  • After each guess, prints Too low!, Too high!, or a congratulation.
  • Stops when the player wins, telling them how many guesses it took.
  • Does not crash when the player types banana instead of a number. It says something friendly and asks again.
🛠️ This one needs a real install Reading keyboard input needs a terminal, so the browser playground will not work here. If you have not set up your lab yet, the setup guide takes about ten minutes. Start with:
cd ~/rusty-lab
cargo new guessing_game
cd guessing_game
cargo run

What you already know

Everything you need is in Levels 1 and 2, except two small new things. Rather than make you hunt for them, here they are:

Reading a line of input

use std::io;

fn main() {
    let mut input = String::new();
    io::stdin()
        .read_line(&mut input)
        .expect("failed to read line");

    println!("you typed: {}", input.trim());
}

Note the &mut input: you are lending the String to read_line so it can fill it in (Lesson 7 in the wild). And .trim() matters, because the text arrives with the Enter keypress still attached to the end.

A random number

The standard library has no random numbers, so we borrow a crate. In your project folder:

cargo add rand
use rand::prelude::*;

fn main() {
    let secret: u32 = rand::rng().random_range(1..=100);
    println!("shh, it's {secret}");
}

That is your first real dependency. You just used the same mechanism that professional Rust projects use every day.

⚠️ You will meet older code for this Most tutorials and Stack Overflow answers written before 2025 use rand::thread_rng() and .gen_range(). Those names were retired in newer versions of the crate, so that code no longer compiles. The modern spellings are rand::rng() and .random_range(), shown above.

This is worth internalizing beyond this project: libraries change, and the internet remembers old versions forever. When example code fails, check the crate's own current documentation on docs.rs before assuming you made the mistake. Reading the real docs is a senior habit you can start today.
💡 Now go build it Seriously: close this tab and try. Come back only when you are stuck, and take only the smallest hint that unsticks you. The hints below go from gentle nudge to full code.

Hints, in order of desperation

Hint 1: I don't know where to start

Build it in stages, running after each one. Never write the whole thing before testing.

  1. Print a welcome message. Run it. (Yes, really.)
  2. Generate the secret number and print it temporarily so you can see it.
  3. Read one guess and print it back.
  4. Compare the guess to the secret and print higher or lower.
  5. Wrap it in a loop.
  6. Handle bad input.
  7. Remove that temporary print of the secret.

Each step is small. That is the actual professional technique, not a beginner's crutch.

Hint 2: How do I turn the typed text into a number?

Text goes in, a number must come out, and it might fail. That shape is a Result (Lesson 11):

let guess: u32 = input.trim().parse().expect("not a number!");

That works but crashes on bad input, which the spec forbids. Keep it for now to get the game working, then come back and fix it with Hint 5.

Hint 3: How do I compare and loop?

You can compare with plain if, but Rust has something made exactly for this, and it reads beautifully:

use std::cmp::Ordering;

match guess.cmp(&secret) {
    Ordering::Less => println!("Too low!"),
    Ordering::Greater => println!("Too high!"),
    Ordering::Equal => println!("You got it!"),
}

For the loop, use loop { ... } and break when the guess is Equal. Remember that break can live inside a match arm.

Hint 4: My input keeps repeating the first guess

Classic bug, and a good one to hit. read_line appends to the String rather than replacing it. If you create input once outside the loop, it grows like "5\n7\n12\n" and parsing fails.

Fix: create a fresh String::new() inside the loop each time, or call input.clear() before each read.

Hint 5: How do I stop it crashing on "banana"?

Replace .expect(...) with a match on the Result, and use continue to go round the loop again:

let guess: u32 = match input.trim().parse() {
    Ok(number) => number,
    Err(_) => {
        println!("That's not a number. Try again!");
        continue;
    }
};

This is the single most useful pattern in beginner Rust: turn a possible failure into either a value or a fresh attempt.

Hint 6: Just show me the whole thing

Fine, but try running yours first, even broken. Comparing a broken attempt with a working one teaches far more than reading alone.

use rand::prelude::*;
use std::cmp::Ordering;
use std::io;

fn main() {
    println!("🦀 Guess my number! (1 to 100)");

    let secret: u32 = rand::rng().random_range(1..=100);
    let mut attempts = 0;

    loop {
        println!("Your guess:");

        let mut input = String::new();
        io::stdin()
            .read_line(&mut input)
            .expect("failed to read line");

        let guess: u32 = match input.trim().parse() {
            Ok(number) => number,
            Err(_) => {
                println!("That's not a number. Try again!");
                continue;
            }
        };

        attempts += 1;

        match guess.cmp(&secret) {
            Ordering::Less => println!("Too low!"),
            Ordering::Greater => println!("Too high!"),
            Ordering::Equal => {
                println!("You got it in {attempts} guesses! 🎉");
                break;
            }
        }
    }
}

Did you build it? 🎉

Then you have written a complete, interactive program with input handling, state, a game loop, and graceful error recovery. That is not a toy in any meaningful sense; it is the skeleton of every interactive program there is.

Stretch goals

Make it yours

  • Limit the guesses. Five tries, then the game reveals the number and you lose.
  • Difficulty levels. Ask "easy, medium, or hard?" at the start and set the range to 1-50, 1-100, or 1-1000.
  • Warmer and colder. If a guess is within 5 of the secret, say "you're burning up!"
  • Play again? After a win, ask whether to start a new round, and keep a running tally of games won.
  • Reverse it. You think of a number, and the computer guesses using binary search while you answer higher or lower. Watch it win in seven guesses every time.
Reveal a hint for "limit the guesses"

You already count attempts. After incrementing it, check if attempts >= 5, print the secret, and break. The whole feature is three lines, which is a nice lesson in itself: once the structure is right, features get cheap.