Решение на Wordle от Билян Хаджи
Резултати
- 17 точки от тестове
- 0 бонус точки
- 17 точки общо
- 13 успешни тест(а)
- 2 неуспешни тест(а)
Код
use std::collections::HashSet;
use std::fmt;
#[derive(Debug, Clone, Copy)]
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,
// Каквито други полета ви трябват
alphabet: HashSet<char>,
to_be_guessed: String,
guesses_history: Vec<Word>,
max_guess_count: u8
}
#[derive(Debug, Clone)]
pub struct Word {
// Каквито полета ви трябват
value: String,
letters: Vec<Letter>
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum LetterStatus {
NotPresent,
PresentWrongSpot,
PresentRightSpot
}
#[derive(Debug, Clone)]
pub struct Letter {
value: char,
status: LetterStatus
}
impl Game {
/// Конструира нова игра с думи/букви от дадената в `alphabet` азбука. Alphabet е просто низ,
/// в който всеки символ е отделна буква, който вероятно искате да си запазите някак за после.
///
/// Подадената дума с `word` трябва да има само букви от тази азбука. Иначе очакваме да върнете
/// `GameError::NotInAlphabet` грешка с първия символ в `word`, който не е от азбуката.
///
/// Началното състояние на играта е `InProgress` а началния брой опити `attempts` е 0.
///
pub fn new(alphabet: &str, word: &str) -> Result<Self, GameError> {
let alphabet_set = Self::distinct_chars(&alphabet);
match Self::validate_word_conforms_to_alphabet(&alphabet_set, &word) {
Some(err) => Err(err),
None => {
let status = GameStatus::InProgress;
let attempts = 0;
let to_be_guessed = String::from(word);
let guesses_history = vec!();
let max_guess_count = 5;
let game = Game {
status, attempts, alphabet: alphabet_set, to_be_guessed, guesses_history, max_guess_count
};
Ok(game)
}
}
}
fn validate_word_conforms_to_alphabet(alphabet_set: &HashSet<char>, word: &str) -> Option<GameError> {
let word_letters = Self::distinct_chars(&word);
for &word_letter in word_letters.iter() {
if !alphabet_set.contains(&word_letter) {
return Some(GameError::NotInAlphabet(word_letter));
}
}
None
}
fn distinct_chars(str: &str) -> HashSet<char> {
let mut set = HashSet::new();
for c in str.chars() {
set.insert(c);
}
set
}
/// Опитва се да познае търсената дума. Опита е в `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> {
match self.status {
GameStatus::InProgress => {
match self.validate_guess_length(guess) {
Some(err) => Err(err),
None => {
match Self::validate_word_conforms_to_alphabet(&self.alphabet, guess) {
Some(err) => Err(err),
None => {
self.attempts += 1;
let letters = self.generate_letter_state(guess);
let word = Word {value: String::from(guess), letters};
self.guesses_history.push(word.clone());
if word.all_letters_match_guess() {
self.status = GameStatus::Won;
} else if self.attempts >= self.max_guess_count {
self.status = GameStatus::Lost;
}
Ok(word)
}
}
}
}
},
_ => Err::<Word, GameError>(GameError::GameIsOver(self.status))
}
}
fn generate_letter_state(&self, guess: &str) -> Vec<Letter> {
let mut letters = vec!();
for (c1, c2) in guess.chars().zip(self.to_be_guessed.chars()) {
let letter_status;
if c1 == c2 {
letter_status = LetterStatus::PresentRightSpot;
} else if self.to_be_guessed.contains(&c1.to_string()) {
letter_status = LetterStatus::PresentWrongSpot;
} else {
letter_status = LetterStatus::NotPresent;
}
letters.push(Letter{value: c1, status: letter_status});
}
letters
}
fn validate_guess_length(&self, guess: &str) -> Option<GameError> {
let guess_length = guess.len();
let to_be_guessed_length = self.to_be_guessed.len();
if guess_length != to_be_guessed_length {
Some(GameError::WrongLength{expected: to_be_guessed_length, actual: guess_length})
} else {
None
}
}
}
impl Word {
pub fn all_letters_match_guess(&self) -> bool {
self.letters.iter().all(|letter| letter.status == LetterStatus::PresentRightSpot)
}
}
impl fmt::Display for Letter {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let uppercased_letter = self.value.to_uppercase();
match self.status {
LetterStatus::PresentRightSpot => write!(f, "[{}]", uppercased_letter),
LetterStatus::PresentWrongSpot => write!(f, "({})", uppercased_letter),
LetterStatus::NotPresent => write!(f, ">{}<", uppercased_letter)
}
}
}
impl fmt::Display for Word {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for l in self.letters.iter() {
write!(f, "{}", l).unwrap();
}
Ok(())
}
}
impl fmt::Display for Game {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for _ in 0..self.to_be_guessed.chars().count() {
write!(f, "|_|").unwrap();
}
let history_len = self.guesses_history.len();
if history_len > 0 {
write!(f, "\n").unwrap();
}
for (i, word) in self.guesses_history.iter().enumerate() {
write!(f, "{}", word).unwrap();
if i != history_len - 1 {
write!(f, "\n").unwrap();
}
}
Ok(())
}
}
Лог от изпълнението
Compiling solution v0.1.0 (/tmp/d20230111-3772066-qyvsr1/solution) warning: field `value` is never read --> src/lib.rs:32:5 | 30 | pub struct Word { | ---- field in this struct 31 | // Каквито полета ви трябват 32 | value: String, | ^^^^^ | = note: `Word` has derived impls for the traits `Clone` and `Debug`, but these are intentionally ignored during dead code analysis = note: `#[warn(dead_code)]` on by default warning: `solution` (lib) generated 1 warning Finished test [unoptimized + debuginfo] target(s) in 0.91s 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 ... ok test solution_test::test_game_state_1 ... ok test solution_test::test_game_state_2 ... ok test solution_test::test_game_state_3 ... ok test solution_test::test_word_display ... ok test solution_test::test_word_display_bulgarian ... ok test solution_test::test_word_display_german ... ok test solution_test::test_word_display_with_repetitions ... ok test solution_test::test_word_not_in_alphabet_on_construction ... ok test solution_test::test_word_not_in_alphabet_on_guess ... ok test solution_test::test_word_not_in_alphabet_on_construction_cyrrilic ... FAILED test solution_test::test_word_not_in_alphabet_on_guess_cyrillic ... FAILED test solution_test::test_wrong_length ... ok failures: ---- 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 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace ---- 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 Err(WrongLength { expected: 4, actual: 8 }) does not match the pattern "Err(GameError::NotInAlphabet('х'))"', tests/solution_test.rs:46:5 failures: solution_test::test_word_not_in_alphabet_on_construction_cyrrilic solution_test::test_word_not_in_alphabet_on_guess_cyrillic test result: FAILED. 13 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s error: test failed, to rerun pass `--test solution_test`