2
\$\begingroup\$

I decided to try my hand at a Hangman game to familiarize myself a bit with the language.

use std::io::{stdin, stdout, Write};

fn main() {
    let stdin = stdin();
    let mut stdout = stdout();

    let to_guess = String::from("BENEDICT");

    let mut letters: u128 = 0;

    loop {
        // Replace unguessed letters of to_guess by underscores
        let current = to_guess.chars().map(|x| {
            if x <= 127 as char && letters & (1 << x as u8) > 0 {
                x
            } else {
                '_'
            }
        });
        // Add spaces between letters
        let current = current
            .map(|x| x.to_string() + &' '.to_string())
            .collect::<Vec<String>>()
            .join(" ");

        println!("Current word: {}", current.trim_end());

        if !current.contains('_') {
            println!("Congratulations!");
            break;
        }

        print!("Enter new letter: ");
        stdout.flush().unwrap();

        let mut letter = String::new();
        stdin.read_line(&mut letter).expect("Invalid letter");

        if letter.trim().len() != 1 {
            println!("Use only one ASCII letter");
            continue;
        }

        let letter = letter.to_uppercase().chars().next().unwrap();

        letters |= 1 << letter as u8
    }
}

The to_guess word is hardcoded, and there is an unlimited number of guesses.

To store letters already guessed, I use an u128 and flag the corresponding bit. It should be ok since all ASCII letters are represented by u8 < 128.

Can you help me review the code, are there "Rust" things I missed?

\$\endgroup\$
2
\$\begingroup\$

Welcome to Rust. Here's some suggestions to get you started:

Creating idiomatic use declarations

It is not common in Rust to bring a function into scope directly via a use declaration. Instead, io::stdin and io::stdout are preferred. See the section Creating Idiomatic use Paths in the book for more information.

Using structs to self-document code

You included this sentence in your explanation of the code:

To store letters already guessed, I use an u128 and flag the corresponding bit. It should be ok since all ASCII letters are represented by u8 < 128.

It is preferable to include this information in the code itself. In this case, we can introduce a struct:

#[derive(Clone, Copy, Debug, Default)]
struct AsciiSet {
    flag: u128,
}

impl AsciiSet {
    fn new() -> Self {
        Self { flag: 0 }
    }

    fn push(&mut self, letter: u8) {
        self.flag |= (1 << letter);
    }

    fn contains(&self, letter: u8) -> bool {
        (self.flag & (1 << letter)) != 0
    }
}

and modify the code accordingly:

let mut letters = AsciiSet::new();

loop {
    // ...
    if letters.contains(letter) { /* ... /* }

    // ...
    letters.push(letter);
}

Checking for ASCII

letter as u8 truncates the character if !letter.is_ascii(). Check for this case and prompt the user accordingly.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.