Building a number guessing game
You have learnt enough about Rust, now it's time to build your first Rust project. In this tutorial, we're going to build a simple number guessing game.
Here's how it works, the program will generate a random number between 1 and 100. The user will have to guess the number. If the user's guess is higher than the random number, the program will display "Too high!". If the user's guess is lower, the program will display "Too low!". If the user guesses the number correctly, the program will display "You win!".
The user will have a limited number of attempts to guess the number. If the user runs out of attempts, the program will display "You lose!".
Let's get started!
Setting up the project
First things first, let's set up the project. Create a new Rust project by running the following command:
Cargo new
Navigate to the project directory, and open it in your favorite code editor:
Cargo automatically creates a folder with the name of the project and initializes a new Rust project inside it. The project structure should look like this:
When you open your editor, Rust will automatically create a Cargo.lock
file which is used to lock the dependencies of your project to specific versions.
Writing the code
Now that we have our project set up, let's start writing the code.
Since this project is only a CLI project and we will not have a UI, we need to show the user some text to interact with the program and then we need to read the inputs from the user. For that matter we will need to use the Rust standard library.
The Rust standard library
The Rust standard library is the core of the Rust programming language. It provides a rich set of APIs for working with strings, files, networking, and more. The standard library is divided into modules, each of which provides a set of related functionality.
For this project, we will use the std::io
module for the CLI interaction and the std::rand
module to generate random numbers.
In order to import some code from the standard library (or any other external crate), we use the use
keyword followed by the path to the module we want to import.
Let's start by importing the io
module from the standard library, add this to the top of your main.rs
file:
Generating a random number
We have explained functions in the previous chapters, now we have to create a function that generates a random number between 1 and 100.
In order to generate a random number, we will need to use another crate, but this one is external and we'll need to download it from crates.io.
Luckily Cargo provides a very convenient way to manage dependencies in Rust projects. We can add a dependency to our project by either adding it to the Cargo.toml
file, or by running the cargo add <crate-name>
command.
We will use the command line to add the rand
crate to our project:
Cargo add rand
Cargo will automatically add the latest version and update the Cargo.toml
file with the new dependency:
Now that we have the rand
crate added to our project, we can use it to create our generate_random_number()
function.
In this function, we use the rand::thread_rng()
function to get a random number generator, and then call the gen_range()
method on it to generate a random number between 1 and 100.
The gen_range()
method accepts a range as an argument, you have seen a range before in the loops chapter when we were using it in a for
loop.
This is the same thing, we are using the 1..=100
range to generate a random number between 1 and 100. The =
means that the range is inclusive, so the number 100
is included in the range.
We can then use the function in the main()
function to generate a random number.
Random number
Reading user input
In order to read the user input we now need to use the std::io
we imported earlier. We will create a function that reads the user input and returns it.
In the function above, we are creating a mutable variable user_input
of type String
, at first this variable will be empty, but then it will be given to the read_line()
method as a &mut
which is a mutable reference to the variable.
We will cover references later, but all you need to know about references for now is that they allow you to get access to data in memory without taking ownership of it. The
&
symbol is used to create a reference to a variable, and themut
keyword is used to make it mutable. Together&mut
means a mutable reference.
The read_line()
method will mutate the value to the value that the user types in the console. The expect()
method is used to handle errors, if the read_line()
method fails, it will make the program panic and display the message "Failed to read line".
Panicking in Rust makes the program stop immediately and display an error message.
After that we are using the trim()
method to remove any whitespace from the user input, and then we are using the parse()
method to convert the String
to a u32
(unsigned 32-bit integer).
You might be wondering, how does Rust know which data type should it parse
the string to? There are many possible types that the string can be parsed to like i32
, f32
, f64
, etc.
The answer is the explicit return type of the function. When we write -> u32
after the function name, we are telling Rust that this function will return a value of type u32
. So when we call the parse()
method, Rust will automatically know that we want to parse the string to a u32
.
We can then use this function in the main()
function to read the user input.
User input
Great! Now we have the random number generated and the user input read, but the problem is that the user input is only being read once, we need to read the user input multiple times until either the user hits their guess limit or they guess the number correctly.
To do that, we can use a loop:
Great! Now we can read the user input as many times as we want, but we need to add some logic to compare the user input with the random number and display the appropriate message.
Comparing the user input with the random number
Now we need to compare the user input with the random generated number and print a message based on the comparison. To do that, let's create a new function called compare_numbers()
.
In this function we are using control flow to compare the user input with the random number. If the user input is greater than the random number, we return a String
with the message Too high!, if it's less, we return Too low!, and if it's equal, we return You win!.
Let's add this to the main()
function:
Guessing loop
Now, the program prints the right message based on the user input, but we need to add some more logic to handle the case when the user guesses the number correctly or when the user runs out of attempts.
Handling the game logic
Let's first add the limit to the number of tries a user can have. We will add a counter to the main()
function and increment it every time the user makes a guess.
Here, we have added a few lines, one to create a mutable variable attempts
and set it to 0
, and then we increment it by 1
every time the user makes a guess.
After that, we added another if
expression to check if the user has hit the limit of 5
attempts, if they have, we print "You lose!" and the random number, and then we break
out of the loop.
Since the maximum number of attempts is a constant, we can actually create a global variable for it using the const
keyword.
Let's run the game and see how it works.
Max attempts
Great! It works exactly as expected. The user has a limited number of attempts to guess the number, and if they run out of attempts, the program displays "You lose!" and reveals the random number.
Handling the win condition
Now we need to handle the win condition. If the user guesses the number correctly, we need to print "You win!" and break out of the loop.
Win condition
As you can see, the program will exit automatically whenever the user guesses the number correctly.
Congratulations on building your first project with Rust! You have learned how to generate random numbers, read user input, and handle game logic. You can now expand on this project by adding more features, you can add a scoring system or give the user the ability to restart the game.
Here's the GitHub repository for the project, you can look at the code, run it, and even contribute to it.
Number Guessing Game
In the next lesson, we're going to learn about Ownership in Rust, the most unique feature of the Rust programming language.