Решение на Wordle от Ангел Пенчев
Резултати
- 8 точки от тестове
- 0 бонус точки
- 8 точки общо
- 6 успешни тест(а)
- 9 неуспешни тест(а)
Код
use std::fmt;
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum GameStatus {
InProgress,
Won,
Lost,
}
#[derive(Debug)]
pub enum GameError {
NotInAlphabet(char),
WrongLength { expected: usize, actual: usize },
GameIsOver(GameStatus),
}
#[derive(Debug)]
pub struct Game {
pub status: GameStatus,
pub attempts: u8,
pub max_attempts: u8,
pub word: Word,
pub guesses: Vec<Word>,
}
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum Letter {
Unknown(char),
FullMatch(char),
PartialMatch(char),
NoMatch(char),
}
#[derive(Debug, PartialEq, Clone)]
pub struct Word {
pub word: Vec<Letter>,
}
impl Game {
/// Конструира нова игра с думи/букви от дадената в `alphabet` азбука. Alphabet е просто низ,
/// в който всеки символ е отделна буква, който вероятно искате да си запазите някак за после.
///
/// Подадената дума с `word` трябва да има само букви от тази азбука. Иначе очакваме да върнете
/// `GameError::NotInAlphabet` грешка с първия символ в `word`, който не е от азбуката.
///
/// Началното състояние на играта е `InProgress` а началния брой опити `attempts` е 0.
///
pub fn new(alphabet: &str, word: &str) -> Result<Self, GameError> {
let alphabet = alphabet.to_uppercase();
let word = word.to_uppercase();
let mut word_vec = Vec::new();
for c in word.chars() {
if alphabet.contains(c) {
word_vec.push(Letter::Unknown(c));
} else {
return Err(GameError::NotInAlphabet(c));
}
}
let mut guesses: Vec<Word> = Vec::new();
guesses.push(Word { word: word_vec.clone() });
Ok(Game {
status: GameStatus::InProgress,
attempts: 0,
max_attempts: 6,
word: Word { word: word_vec },
guesses,
})
}
/// Опитва се да познае търсената дума. Опита е в `guess`.
///
/// Ако играта е приключила, тоест статуса ѝ е `Won` или `Lost`, очакваме да върнете
/// `GameIsOver` със статуса, с който е приключила.
///
/// Ако `guess` има различен брой букви от търсената дума, очакваме да върнете
/// `GameError::WrongLength`. Полето `expected` на грешката трябва да съдържа броя букви на
/// търсената дума, а `actual` да е броя букви на опита `guess`.
///
/// Ако `guess` има правилния брой букви, но има буква, която не е от азбуката на играта,
/// очакваме `GameError::NotInAlphabet` както по-горе, с първия символ от `guess`, който не е
/// от азбуката.
///
/// Метода приема `&mut self`, защото всеки валиден опит (такъв, който не връща грешка) се
/// запазва в играта за по-нататък. Метода връща `Word`, което описва освен самите символи на
/// `guess`, и как тези символи са се напаснали на търсената дума. Също така инкрементира
/// `attempts` с 1.
///
/// След опита за напасване на думата, ако всички букви са уцелени на правилните места,
/// очакваме `state` полето да се промени на `Won`. Иначе, ако `attempts` са станали 5,
/// състоянието трябва да е `Lost`.
///
pub fn guess_word(&mut self, guess: &str) -> Result<Word, GameError> {
// Make guess uppercase
let guess = guess.to_uppercase();
// Check if game is over
if self.status == GameStatus::Won || self.status == GameStatus::Lost {
return Err(GameError::GameIsOver(self.status));
}
// Check if guess is the same length as the word
if guess.chars().count() != self.word.word.len() {
return Err(GameError::WrongLength {
expected: self.word.word.len(),
actual: guess.len(),
});
}
// Check if guess is correct
let mut guess_vec = Vec::new();
for (i, c) in guess.chars().enumerate() {
// Check if the letter is an exact match
if let Letter::Unknown(c2) = self.word.word[i] {
if c == c2 {
guess_vec.push(Letter::FullMatch(c));
continue;
}
}
// Check if the letter is in the word
if self.word.word.contains(&Letter::Unknown(c)) {
guess_vec.push(Letter::PartialMatch(c));
continue;
}
// The letter is not in the word
guess_vec.push(Letter::NoMatch(c));
}
// Increment attempts
self.attempts += 1;
// Add guess to guesses
let guess_word = Word { word: guess_vec };
self.guesses.push(guess_word.clone());
// Check if game is won by comparing all the letter characters
let mut won = true;
for letter in &self.word.word {
if let Letter::Unknown(c) = letter {
if !guess.chars().any(|c2| c == &c2) {
won = false;
break;
}
}
}
if won {
self.status = GameStatus::Won;
}
// Check if game is lost
if self.attempts == self.max_attempts {
self.status = GameStatus::Lost;
}
Ok(guess_word)
}
}
impl fmt::Display for Letter {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Letter::Unknown(_) => write!(f, "|_|"),
Letter::FullMatch(c) => write!(f, "[{}]", c.to_uppercase()),
Letter::PartialMatch(c) => write!(f, "({})", c.to_uppercase()),
Letter::NoMatch(c) => write!(f, ">{}<", c.to_uppercase()),
}
}
}
impl fmt::Display for Word {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for letter in &self.word {
write!(f, "{}", letter)?;
}
Ok(())
}
}
impl fmt::Display for Game {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for guess in &self.guesses[..self.guesses.len() - 1] {
writeln!(f, "{}", guess)?;
}
write!(f, "{}", self.guesses.last().unwrap()).unwrap();
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_english_guess_fmt() {
let english_letters = "abcdefghijklmnopqrstuvwxyz";
let mut game = Game::new(english_letters, "rebus").unwrap();
assert_eq!(game.guess_word("route").unwrap().to_string(), "[R]>O<(U)>T<(E)");
assert_eq!(game.guess_word("rules").unwrap().to_string(), "[R](U)>L<(E)[S]");
assert_eq!(game.guess_word("rebus").unwrap().to_string(), "[R][E][B][U][S]");
}
#[test]
fn test_bulgarian_guess_fmt() {
let bulgarian_letters = "абвгдежзийклмнопрстуфхцчшщъьюя";
let mut game = Game::new(bulgarian_letters, "ребус").unwrap();
assert_eq!(game.guess_word("роуте").unwrap().to_string(), "[Р]>О<(У)>Т<(Е)");
assert_eq!(game.guess_word("рулес").unwrap().to_string(), "[Р](У)>Л<(Е)[С]");
assert_eq!(game.guess_word("ребус").unwrap().to_string(), "[Р][Е][Б][У][С]");
}
#[test]
fn test_english_game_fmt() {
let english_letters = "abcdefghijklmnopqrstuvwxyz";
let mut game = Game::new(english_letters, "rebus").unwrap();
game.guess_word("route").unwrap();
game.guess_word("rebus").unwrap();
assert_eq!(game.to_string(), "|_||_||_||_||_|\n[R]>O<(U)>T<(E)\n[R][E][B][U][S]");
}
#[test]
fn test_bulgarian_game_fmt() {
let bulgarian_letters = "абвгдежзийклмнопрстуфхцчшщъьюя";
let mut game = Game::new(bulgarian_letters, "ребус").unwrap();
game.guess_word("роуте").unwrap();
game.guess_word("ребус").unwrap();
assert_eq!(game.to_string(), "|_||_||_||_||_|\n[Р]>О<(У)>Т<(Е)\n[Р][Е][Б][У][С]");
}
#[test]
fn test_game_status() {
let english_letters = "abcdefghijklmnopqrstuvwxyz";
let mut game = Game::new(english_letters, "rebus").unwrap();
assert_eq!(game.status, GameStatus::InProgress);
game.guess_word("route").unwrap();
assert_eq!(game.status, GameStatus::InProgress);
game.guess_word("rebus").unwrap();
assert_eq!(game.status, GameStatus::Won);
}
#[test]
fn test_game_max_attempts() {
let english_letters = "abcdefghijklmnopqrstuvwxyz";
let mut game = Game::new(english_letters, "rebus").unwrap();
assert_eq!(game.status, GameStatus::InProgress);
game.guess_word("route").unwrap();
assert_eq!(game.status, GameStatus::InProgress);
game.guess_word("rules").unwrap();
assert_eq!(game.status, GameStatus::InProgress);
game.guess_word("rules").unwrap();
assert_eq!(game.status, GameStatus::InProgress);
game.guess_word("rules").unwrap();
assert_eq!(game.status, GameStatus::InProgress);
game.guess_word("rules").unwrap();
assert_eq!(game.status, GameStatus::InProgress);
game.guess_word("rules").unwrap();
assert_eq!(game.status, GameStatus::Lost);
}
}
Лог от изпълнението
Compiling solution v0.1.0 (/tmp/d20230111-3772066-1jfk2wz/solution) Finished test [unoptimized + debuginfo] target(s) in 0.81s Running tests/solution_test.rs (target/debug/deps/solution_test-0edbea2040daef01) running 15 tests test solution_test::test_game_display ... ok test solution_test::test_game_display_cyrillic ... ok test solution_test::test_game_display_german ... FAILED test solution_test::test_game_state_1 ... ok test solution_test::test_game_state_2 ... FAILED test solution_test::test_game_state_3 ... FAILED test solution_test::test_word_display_bulgarian ... ok test solution_test::test_word_display ... FAILED test solution_test::test_word_display_german ... FAILED test solution_test::test_word_not_in_alphabet_on_construction ... ok test solution_test::test_word_display_with_repetitions ... FAILED test solution_test::test_word_not_in_alphabet_on_construction_cyrrilic ... FAILED test solution_test::test_word_not_in_alphabet_on_guess ... FAILED test solution_test::test_wrong_length ... ok test solution_test::test_word_not_in_alphabet_on_guess_cyrillic ... FAILED failures: ---- solution_test::test_game_display_german stdout ---- thread 'solution_test::test_game_display_german' panicked at 'assertion failed: `(left == right)` left: `"|_||_||_||_|"`, right: `"|_||_||_|"`', tests/solution_test.rs:133:5 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace ---- solution_test::test_game_state_2 stdout ---- thread 'solution_test::test_game_state_2' panicked at 'Expression Won does not match the pattern "GameStatus::InProgress"', tests/solution_test.rs:156:9 ---- solution_test::test_game_state_3 stdout ---- thread 'solution_test::test_game_state_3' panicked at 'called `Result::unwrap()` on an `Err` value: GameIsOver(Won)', tests/solution_test.rs:171:32 ---- solution_test::test_word_display stdout ---- thread 'solution_test::test_word_display' panicked at 'called `Result::unwrap()` on an `Err` value: GameIsOver(Won)', tests/solution_test.rs:59:39 ---- solution_test::test_word_display_german stdout ---- thread 'solution_test::test_word_display_german' panicked at 'assertion failed: `(left == right)` left: `"|_||_||_||_|"`, right: `"|_||_||_|"`', tests/solution_test.rs:87:5 ---- solution_test::test_word_display_with_repetitions stdout ---- thread 'solution_test::test_word_display_with_repetitions' panicked at 'called `Result::unwrap()` on an `Err` value: GameIsOver(Won)', tests/solution_test.rs:69:40 ---- solution_test::test_word_not_in_alphabet_on_construction_cyrrilic stdout ---- thread 'solution_test::test_word_not_in_alphabet_on_construction_cyrrilic' panicked at 'Expression Err(NotInAlphabet('О')) does not match the pattern "Err(GameError::NotInAlphabet('о'))"', tests/solution_test.rs:27:5 ---- solution_test::test_word_not_in_alphabet_on_guess stdout ---- thread 'solution_test::test_word_not_in_alphabet_on_guess' panicked at 'Expression Ok(Word { word: [NoMatch('\''), FullMatch('O'), FullMatch('O'), FullMatch('P')] }) does not match the pattern "Err(GameError::NotInAlphabet('\\''))"', tests/solution_test.rs:37:5 ---- solution_test::test_word_not_in_alphabet_on_guess_cyrillic stdout ---- thread 'solution_test::test_word_not_in_alphabet_on_guess_cyrillic' panicked at 'Expression Ok(Word { word: [NoMatch('Х'), NoMatch('О'), NoMatch('П'), NoMatch('А')] }) does not match the pattern "Err(GameError::NotInAlphabet('х'))"', tests/solution_test.rs:46:5 failures: solution_test::test_game_display_german solution_test::test_game_state_2 solution_test::test_game_state_3 solution_test::test_word_display solution_test::test_word_display_german solution_test::test_word_display_with_repetitions solution_test::test_word_not_in_alphabet_on_construction_cyrrilic solution_test::test_word_not_in_alphabet_on_guess solution_test::test_word_not_in_alphabet_on_guess_cyrillic test result: FAILED. 6 passed; 9 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s error: test failed, to rerun pass `--test solution_test`