Learning Rust 102: Writing a Hangman Game

Introduction

In this tutorial, we will be implementing the classic game of Hangman in Rust. Hangman is a word guessing game where the player has a limited number of attempts to guess the correct word. If the player runs out of attempts before guessing the word, they lose.

The game

The game will be played in the terminal, and it will be a two-player game. The first player will think of a word and the second player will try to guess it. The second player will have a limited number of attempts to guess the word. If the player runs out of attempts before guessing the word, they lose.

Let's code!

Let's start by creating a new Rust project.

cargo new hangman-game

Let's start by setting up the basic structure of the game. We will begin by importing the io module from the standard library, which we will use to read input from the user:

use std::io;

Next, we will define a constant called MAX_MISSES which represents the maximum number of incorrect guesses the player is allowed before losing the game. We will set this value to 6

const MAX_MISSES: u32 = 6;

To begin, we will prompt the user to enter the secret word that they will be trying to guess. We will read this input using the read_line function from the io module and store it in a variable called secret_word:

    println!("Enter the secret word: ");

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

    let secret_word = secret_word.trim();

Next we will create a couple vectors to store the letters that have been guessed and the letters that have not been guessed. We will also push underscores to the correct_letters vector to represent the unknown letters in the secret word:

    let mut missed_letters = Vec::new();
    let mut correct_letters = Vec::new();

    // Initialize the correct letters with underscores to represent the unknown letters
    for _ in 0..secret_word.len() {
        correct_letters.push('_');
    }

Now, we can start the main game loop. This loop will continue until either the word is fully guessed or the player has missed too many times. We will display the current state of the word being guessed and the missed letters to the user at the beginning of each iteration:

    while missed_letters.len() < MAX_MISSES as usize {
        println!("Missed letters: {:?}", missed_letters);

        println!("Please enter your guess: ");

Next, we will read the user's guess using the read_line function, just like we did for the secret word. We will then parse the input into a char using the parse method. If the input is invalid (e.g. not a single letter), we will display an error message and continue to the next iteration of the loop:

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

        let guess: char = match guess.trim().parse() {
            Ok(c) => c,
            Err(_) => {
                println!("Please enter a valid letter");
                continue;
            }

Now, we can check if the guessed letter is correct or not. If the letter is contained in the secret word, we will update the correct_letters vector to reflect this. If the letter is not contained in the secret word, we will add it to the missed_letters vector:

        if secret_word.contains(guess) {
            println!("Correct!");
            for (i, c) in secret_word.chars().enumerate() {
                if c == guess {
                    correct_letters[i] = c;
                }
            }
        } else {
            println!("Incorrect!");
            missed_letters.push(guess);
        }

After each guess, we will check if the player has won or not by checking if the correct_letters vector contains any underscores. If it does not, then the player has successfully guessed the entire word and we can break out of the game loop:

        if !correct_letters.contains(&'_') {
            println!("You win! The word was {}", secret_word);
            break;
        }
    }

Finally, we will check if the player has lost the game by checking if the number of missed letters is equal to the maximum number of misses allowed. If this is the case, we will display a message indicating that the player has lost:

    if missed_letters.len() == MAX_MISSES as usize {
        println!("You lose! The word was {}", secret_word);
    }
}

And that's it! We have now implemented the game of Hangman in Rust.

Final code

Here is the final code for the game:

use std::io;

const MAX_MISSES: u32 = 6;

fn main() {
    println!("Welcome to Hangman!");

    println!("Enter the secret word: ");

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

    let secret_word = secret_word.trim();

    let mut missed_letters = Vec::new();
    let mut correct_letters = Vec::new();

    // Initialize the correct letters with underscores to represent the unknown letters
    for _ in 0..secret_word.len() {
        correct_letters.push('_');
    }

    println!("The word is: {:?}", correct_letters);

    while missed_letters.len() < MAX_MISSES as usize {
        println!("Missed letters: {:?}", missed_letters);
        println!("Misses remaining: {}", MAX_MISSES - missed_letters.len() as u32);

        println!("Please enter your guess: ");

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

        let guess: char = match guess.trim().parse() {
            Ok(c) => c,
            Err(_) => {
                println!("Please enter a valid letter");
                continue;
            }
        };

        if secret_word.contains(guess) {
            println!("Correct!");
            for (i, c) in secret_word.chars().enumerate() {
                if c == guess {
                    correct_letters[i] = c;
                }
            }
        } else {
            println!("Incorrect!");
            missed_letters.push(guess);
        }

        println!("Current state: {:?}", correct_letters);

        if !correct_letters.contains(&'_') {
            println!("You win! The word was {}", secret_word);
            break;
        }
    }

    if missed_letters.len() == MAX_MISSES as usize {
        println!("You lose! The word was {}", secret_word);
    }
}

Conclusion

In this tutorial, we have implemented the game of Hangman in Rust. We have learned how to read input from the user, how to store and manipulate data using vectors, and how to use the if and while control flow statements.

You can find the source code of the project here.

Thanks for reading!