I have been learning Rust for a few days (This is my third) now, and I've really fallen in love with the language, this is my first real project, Hangman. I was wondering what I could do better / what is currently fine.
lib.rs:
use rand::Rng;
use std::fs;
use std::io;
#[derive(PartialEq)] // im not even sure what this does but it wants me to add it.
#[derive(Debug)]
pub enum GuessResult {
CORRECT,
INCORRECT,
TAKEN,
INVALID,
}
pub enum WinState {
WIN,
LOSE,
NIL,
}
#[derive(Debug)]
pub struct Hangman {
pub guesses: Vec<char>,
pub correct_guesses: Vec<char>,
pub incorrect_guesses: Vec<char>,
pub incorrect_threshold: i32, // how many we can get wrong.
pub word: Word,
}
// Platform generously supplied on github by Chris Horton
// You can find it here: https://gist.github.com/chrishorton/8510732aa9a80a03c829b09f12e20d9c
const PLATFORM: [&str; 7] = [
"
+---+
| |
|
|
|
|
=========
",
"
+---+
| |
O |
|
|
|
=========
",
"
+---+
| |
O |
| |
|
|
=========
",
"
+---+
| |
O |
/| |
|
|
=========
",
"
+---+
| |
O |
/|\\ |
|
|
=========
",
"
+---+
| |
O |
/|\\ |
/ |
|
=========
",
"
+---+
| |
O |
/|\\ |
/ \\ |
|
=========
",
];
impl Hangman {
pub fn new(filename: String) -> Hangman {
Hangman {
guesses: vec![],
correct_guesses: vec![],
incorrect_guesses: vec![],
incorrect_threshold: 6,
word: get_random_word(filename),
}
}
/**
Take a guess from the user, either a full string or one character at once. If the user has already entered it, we try again.
*/
pub fn take_turn(&mut self) -> WinState {
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("Something went wrong while reading your line");
input = input.trim().to_string();
if input == "" {
// no value entered.
return WinState::NIL;
}
let guessed_char: char = input.chars().collect::<Vec<char>>()[0];
let result = match input.len() {
0 => GuessResult::INVALID,
1 => self.check_char(guessed_char),
_ => self.check_str(input),
};
self.guesses.push(guessed_char);
match result {
GuessResult::CORRECT => {
// dbg.
// println!("Added correct.");
self.correct_guesses.push(guessed_char);
}
GuessResult::INCORRECT => {
println!("Incorrect.");
self.incorrect_guesses.push(guessed_char);
}
_ => return WinState::NIL,
};
return if self.incorrect_guesses.len() >= self.incorrect_threshold as usize {
WinState::LOSE
} else if self
.word
.word // it works.
// by removing all the characters that are correct from the words chars, so if the len is 0, we have all the correct ones.
.chars()
.filter(|x| !self.correct_guesses.contains(x))
.collect::<Vec<_>>()
.len()
== 0
{
WinState::WIN
} else {
WinState::NIL
};
}
/*
Get what the current game looks like graphically.
*/
pub fn get_print_state(&self) -> String {
let plat = self.get_platform_state();
let mut underscores: String = (0..self.word.length).fold(String::new(), |b, _| b + "_");
let correct_guesses: String = (&self.correct_guesses).into_iter().collect();
for c in correct_guesses.chars() {
for (i, c2) in self.word.word.chars().enumerate() {
if c == c2 {
underscores.replace_range(i..i + 1, &String::from(c));
}
}
}
return format!(
"
{}
{} {}
",
plat,
underscores,
(&self.incorrect_guesses).into_iter().collect::<String>()
);
}
fn get_platform_state(&self) -> String {
PLATFORM[self.incorrect_guesses.len()].to_string()
}
/**
To be called whenever a CHARACTER is guessed.
*/
fn check_char(&self, what: char) -> GuessResult {
if self.guesses.contains(&what) {
return GuessResult::TAKEN;
} else {
return if self.word.contains(what) {
GuessResult::CORRECT
} else {
GuessResult::INCORRECT
};
}
}
/**
To be called whenever a full string is guessed.
*/
fn check_str(&self, str: String) -> GuessResult {
if str
.chars()
.all(|x| self.check_char(x) != GuessResult::INCORRECT)
// we loop through all the chars and check against them.
{
GuessResult::CORRECT
} else {
GuessResult::INCORRECT
}
}
}
#[derive(Debug)]
pub struct Word {
pub word: String,
pub length: usize,
}
impl Word {
pub fn new(word: String) -> Word {
Word {
word: word.to_string(),
length: word.len(),
}
}
pub fn contains(&self, letter: char) -> bool {
return self.word.contains(letter);
}
}
pub fn get_random_word(filename: String) -> Word {
let str = fs::read_to_string(filename).expect("Something went wrong when reading the file.");
let strings: Vec<&str> = str.split("\n").collect();
let choice = rand::thread_rng().gen_range(0..strings.len());
Word {
word: strings[choice].to_string(),
length: strings[choice].len(),
}
}
main.rs:
use hangman::Hangman;
fn main() {
let mut game = Hangman::new("./words.txt".to_string());
dbg!(&game);
loop {
println!("{}", game.get_print_state());
match &game.take_turn() {
hangman::WinState::WIN => {
println!("YOU WIN!");
break;
}
hangman::WinState::LOSE => {
println!("{}", game.get_print_state());
println!("You lost :( \nThe word was: {}", game.word.word);
break;
}
_ => (),
};
}
}
words.txt: This is a 500,000 line file of words generated from https://random-word-api.herokuapp.com/all