Guessing Game

Hello, in this artile we are going to cover how to build a fun guessing game with Rust. This post is meant to be fun so feel free to modify the code to your liking. I will be using numbers 1 - 100 but you can use 1 - 1000 if you want. Your choice. We will try to limit the players ability to guess the number in 10 turns or less. Again feel free to increase or decrease the amount of turns if you would like. Really make this code your own. I do want to preface this whole article with the fact that most of the code will be similar to the code you can go through on the Rust Book whereas the book doesn’t implement the turn counter we are going to use to increase the difficulty of the game. Unlimited turns to guess a number will get boring after awhile as there isn’t any challenge. So without further ado, let’s get started.

Setup of the project

We can set up our project pretty easily with cargo. Let’s open up our terminal and follow along.

$ cd Desktop/rust-projects
$ cargo new guessing_game
$ cd guessing_game
$ cargo run
// Hello, World!

The above commands do the following:

  1. change our current directory to the Desktop then to my rust-projects directory where I store all of my Rust apps.
  2. run cargo to create a new app called guessing game
  3. change into the guessing game directory
  4. run the application and print Hello, World! to the terminal to verify that everything is working.

Now that we have our application created we can start coding our game. We are going to be keeping everything in main.rs as the app itself is pretty small so we won’t need to make more than one file. If you have experience creating multiple files for Rust projects and you want to continue to do that and make all of your code into modules you are more than welcome to do that however I will not be doing that in this post. Let’s move on to the next section.

Creating Some Basic Game Logic

In this section we are going to be implementing our code to start off the game. First thing we are going to do is create a way for our app to process a user’s guess. In our main.rs file let’s update our main function to the below

use std::io;

fn main() {
    println!("Guess the number!");
    println!("Please input your guess.");

    let mut guess = String::new();
    let mut guess_count = 10;

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    println!("You guessed: {guess}");
}

This code should be pretty familiar to you if you either you have been programming for a little while now or you are familiar with either The Rust Book or Rust syntax. If you aren’t don’t worry I will break the lines down as best I can.

The use std::io; line at the top is similar to an import statement in TypeScript or Go. This is how you bring in crates to your Rust applications. This particular use statement brings in the io crate from the standard library for us to use with our application. It’s what we use to read user input and produce the output. The next line fn main() { starts our main function. All of our code will be in this function so we won’t be covering how to create our own in this post since this application is quite small there really isn’t a need for any custom functions.

The next two lines println!("Guess the number!"); and println!("Please input your guess."); are print statements similar to console.log(); or fmt.Println() in TypeScript and Go. These lines will just print the text between the double qoutes one on top of the other in the terminal. Similar to our Hello, World! printing when we first created our app with cargo.

The next line let mut guess = String::new(); creates a mutable variable named guess and assigns it a new empty String. It is now of the type String, which is helpful for our app because user or player input comes in as a string.

The next block of code:

io::stdin()
    .read_line(&mut guess)
    .expect("Failed to read line");

is what handles our input and output. The input given to use by our player or user is read into our app and stored in the mutable variable guess. If for some reason our input can’t be read from our player then the expect line will print a message to the screen and terminate the game. This is how we handle errors in Rust. Or one of the ways anyway.

The next line of code println!("You guessed: {guess}"); is similar to those of you that might know JavaScript or TypeScript and do something like this:

let guess: string = '';

console.log(`You guessed: ${guess});

You can think of the {} as placeholders. The Rust Book and other Rust developers, known as Rustaceans, call them crab pincers, and refer to the pincers holding a value in place.

Random Number Time

Now for those of you not fully aware of the package management that Rust has, we use what are called crates as packags. So if you come from something like JavaScript or Go a pacakge or library in Rust is referred to as a crate. We are going to be using the rand crate. Which can be used to generate random numbers. Let’s add the Rand crate to our Cargo.toml file.

[dependencies]
rand = "0.8.3"
colored = "2.0.0"

So typically the way you install a package in Rust is to add it manually to your Cargo.toml file however you can also run cargo add rand from inside your project directory and this will allow you to install the crate in a manner similar to npm i <name-of-package>. Let’s update our code to now generate a random number.

use std::io;
use rand::Rng;
use colored::*;

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..=100);
    println!("The secret number is: {secret_number}");

    ...
}

To save some typing for myself, and to make sure that the article doesn’t become too long, I decided to only add the snippet of code that changed. Using the … to signify that the code below that point is unchanged from what you had before. The first new line we added use rand::Rng; imports the rand crate but more specifically we can use the range trait. I won’t be going over what traits are in this article. I will have another article about what traits are. Now the next line uses the rand package with the gen_range() method. We use this method to generate a number from 1 - 100 and set the secret_number variable to that number. We then print out the number to make sure that we are getting a random number when we start up the game. Let’s test it out now.

$ cargo run
$ cargo run
$ cargo run

You should now have ran the program three times producing a random number each time. You can give a guess and your guess will be printed as well. Pretty awesome. We are making good progress so far.

Compare Guess to Random Number

We will now write out the comparison between the random number and our player’s guess. Again I will be using the … to signal no code changes above or below the three dots.

use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main()_ {
    ...

    println!("You guessed: {guess}");

    match guess.cmp(&secrete_number) {
        Ordering::Less => println!("Too small"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!'),
    }
}

We first add another use statement which allows us to use the Ordering type. The Ordering type is an enum which has Less, Greater and Equal. These three variants help us to compare the values. You can add multiple Less or Greater conditions depending on your needs. Of course we only need the three here. The match keyword is an expression which uses arms. An arm consists of a pattern to match against. You can think of it similar to a switch statement in something like Go or TypeScript:

switch expression
case 1 < 2:
    fmt.Println("1 is less than 2")
default:
    fmt.Println("All is well")

If we run the game now we will get an error. Remember how I told you earlier that the input from a user is a string value? Well you can’t compare string values to numbers in Rust. There isn’t any coercion going on like there is in JavaScript. So because of that we have to cast our player input to an int. We can do that the following way:

...
let guess: u32 = guess.trim().parse().expect("{}", "Please type a number!".magenta());

println!("You guessed: {guess}");

We should now be able to run the game and it work out the way we wanted. This is looking pretty good. And I know what you are thinking. Yes we already have a variable named guess how are we able to declare it a second time and give it a value. Well Rust has this awesome concept called shadowing. Go uses it as well. When you shadow something in Rust it lets us reuse the variablename rather than forcing us to create two unique variables for example: guess and player_guess. This feature of shadowing is used primarily for when you need to convert a value from one type to another type. Let’s run our game to make sure all is working.

$ cargo run

Everything should be working now and we are good to move on to implementing the allowing of multiple guesses.

Multiple Guesses

We will be using the loop keyword. This is similar to using a for loop but instead of needing to write all the logic like:

for (let i = 0; i < 10; i++) {
    console.log(i);
}

we just use the loop keyword and put what part of our code we want to loop over.

loop {
    println!("{}", "Please input your guess.".cyan());
    println!("You have {} guesses left", guess_count);

    if guess_counter <= 10 {
        let mut guess = String::new();
        ...

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small"),
            Ordering::Greater => println!("Too big"),
            Ordering::Equal => println!("You win"),
        }
    }
}

We need to take all of our code from the println!("Please input your guess) all the way to the end of our match statement and put it into our loop block. Now that all of our code is in place we should be able to test that we can enter multiple guesses. Go ahead and run your game and verify that your match statement works by giving a low guess as well as a high guess before typing in the actual guess to win the game.

Winning the Game

Now as I’m sure you have seen. When you guess the correct number it prints You win, however the game doesn’t quit. You are just left there in an infinite loop. We need a way to make sure the game ends. Let’s add the code for this. In our match statement we can update the code as follows:

match guess.cmp(&secret_number) {
    Ordering::Less => println!("{}", "Too small!".yellow());
    Ordering::Greater => println!("{}", "Too big!".red());
    Ordering::Equal => {
        println!("{}", "You win!".green());
        break;
    }
}

We now have a fully working game. Congratulations. Currently though there is a bug. We aren’t handling any cases of when a player accidentally enters in a non number value. We can fix this pretty easily. Where our code for turning the input to a u32 is let’s modify it so it now reads as:

let guess: u32 = match guess.trim().parse() {
    Ok(num) => num,
    Err(_) => continue,
};

This code uses a match statement in combination with the Result type which is an enum that has the variants Ok and Err. This will look and see if the number is passed in which case it will take that input and use it. If for some reason it was a non number value the program will loop again basically pretending to not see the input. It’s like when you have someone you are teasing by not listening to them and pretending not to hear them until they say a word or phrase you wanted them to say. Pretty cool huh? The final code should look like the following:

use std::io;
use std::cmp::Ordering;
use rand::Rng;
use colored::*;

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..=100);
    let mut guess_counter = 10;

    loop {
        println!("{}", "Please input your guess.".cyan());
        println!("You have {} guesses left", guess_counter);

        if guess_counter <= 10 {
            let mut guess = String::new();

            io::stdin()
            .read_line(&mut guess)
            .expect("Failed to read lie");

            let guess: u32 = match guess.trim().parse() {
                Ok(num) => num,
                Err(_) => continue,
            };

            println!("You guessd: {guess}");

            match guess.cmp(&secret_number) {
                Ordering::Less => {
                    println!("{}", "Too small!".yellow());
                    guess_counter -= 1;
                }
                Ordering::Greater => {
                    println!("{}", "Too big!".red());
                    guess_counter -= 1;
                }
                Ordering::Equal => {
                    println!("{}", "You win!".green());
                    break;
                }
            }
        }
    }
}

Conclusion

That’s it. I hope you enjoyed seeing how to make a number guessing game with Rust. I hope you can see how similar it is to building one in Go. The next article will discuss building one in Java. See you there.