Решение на Wordle от Петко Каменов
Резултати
- 20 точки от тестове
- 0 бонус точки
- 20 точки общо
- 15 успешни тест(а)
- 0 неуспешни тест(а)
Код
use std::fmt;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum GameStatus {
InProgress,
Won,
Lost,
}
#[derive(Debug, PartialEq, Eq)]
pub enum GameError {
NotInAlphabet(char),
WrongLength { expected: usize, actual: usize },
GameIsOver(GameStatus),
}
#[derive(Debug)]
pub struct Game {
pub status: GameStatus,
pub attempts: u8,
pub word: String,
pub word_history: Vec<Word>,
pub len: usize,
pub alphabet: String,
// Каквито други полета ви трябват
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Letter {
Unknown(char),
FullMatch(char),
PartialMatch(char),
NoMatch(char),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Word {
letters: Vec<Letter>,
}
impl Word {
pub fn new(word: &str) -> Word {
Word {
letters: word.chars().map(Letter::Unknown).collect(),
}
}
pub fn change_status(&mut self, index: usize, status: Letter) {
assert!(index < self.letters.len());
self.letters[index] = status;
}
pub fn is_complete(&self) -> bool {
self.letters.iter().all(|l| matches!(l, Letter::FullMatch(_)))
}
}
impl fmt::Display for Word {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for letter in &self.letters {
match letter {
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())?,
}
}
Ok(())
}
}
impl fmt::Display for Game {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut result = String::new();
for word in &self.word_history {
result.push_str(&format!("{}\n", word));
}
result.pop();
write!(f, "{}", result)?;
Ok(())
}
}
impl Game {
/// Конструира нова игра с думи/букви от дадената в `alphabet` азбука. Alphabet е просто низ,
/// в който всеки символ е отделна буква, който вероятно искате да си запазите някак за после.
///
/// Подадената дума с `word` трябва да има само букви от тази азбука. Иначе очакваме да върнете
/// `GameError::NotInAlphabet` грешка с първия символ в `word`, който не е от азбуката.
///
/// Началното състояние на играта е `InProgress` а началния брой опити `attempts` е 0.
///
pub fn new(alphabet: &str, word: &str) -> Result<Self, GameError> {
let missing_char = word.chars().find(|c| !alphabet.contains(*c));
if let Some(c) = missing_char {
Err(GameError::NotInAlphabet(c))
} else {
Ok(Game {
status: GameStatus::InProgress,
attempts: 0,
word: word.to_lowercase(),
word_history: vec![Word::new(word)],
len: word.chars().count(),
alphabet: alphabet.to_lowercase(),
// Каквито други полета ви трябват
})
}
}
/// Опитва се да познае търсената дума. Опита е в `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> {
let guess = guess.to_lowercase();
if self.status == GameStatus::Won || self.status == GameStatus::Lost {
return Err(GameError::GameIsOver(self.status));
}
if self.len != guess.chars().count() {
return Err(GameError::WrongLength {
expected: self.len,
actual: guess.len(),
});
}
let missing_char = guess.chars().find(|c| !self.alphabet.contains(*c));
if let Some(c) = missing_char {
return Err(GameError::NotInAlphabet(c));
}
let mut new_guess = Word::new(&guess);
for (index, c) in guess.chars().enumerate() {
if self.word.contains(c) {
if self.word.chars().nth(index).unwrap() == c {
new_guess.change_status(index, Letter::FullMatch(c));
} else {
new_guess.change_status(index, Letter::PartialMatch(c));
}
} else {
new_guess.change_status(index, Letter::NoMatch(c));
}
}
self.word_history.push(new_guess.clone());
self.attempts += 1;
if new_guess.is_complete() {
self.status = GameStatus::Won;
} else if self.attempts == 5 {
self.status = GameStatus::Lost;
}
Ok(new_guess)
}
}
#[cfg(test)]
mod test {
use super::*;
#[cfg(test)]
#[test]
fn test_new() {
let game = Game::new("abc", "cab").unwrap();
assert_eq!(game.status, GameStatus::InProgress);
assert_eq!(game.attempts, 0);
assert_eq!(game.word_history[0].to_string(), "|_||_||_|");
assert_eq!(game.to_string(), "|_||_||_|");
assert_eq!(game.word, "cab");
}
#[test]
fn test_basic() {
let english_letters = "abcdefghijklmnopqrstuvwxyz";
// Конструираме по два различни начина, just in case -- няма причина да не работи и с двата.
assert!(Game::new(english_letters, "!!!").is_err());
let mut game = Game::new(&String::from(english_letters), "abc").unwrap();
assert!(matches!(game.status, GameStatus::InProgress));
assert_eq!(game.attempts, 0);
assert_eq!(game.to_string(), "|_||_||_|");
assert_eq!(game.guess_word("abc").unwrap().to_string(), "[A][B][C]");
assert!(matches!(game.status, GameStatus::Won));
assert_eq!(game.attempts, 1);
}
#[test]
fn german_test() {
let german_letters = "abcdefghijklmnopqrstuvwxyzäöüß";
let mut game = Game::new(german_letters, "süß").unwrap();
assert_eq!(game.guess_word("füß").unwrap().to_string(), ">F<[Ü][SS]");
assert_eq!(game.guess_word("süß").unwrap().to_string(), "[S][Ü][SS]");
assert_eq!(game.status, GameStatus::Won);
assert_eq!(game.attempts, 2);
}
#[test]
fn test_game_with_bulgarian() {
let bulgarian_letters = "абвгдежзийклмнопрстуфхцчшщъьюя";
let mut game = Game::new(bulgarian_letters, "свобода").unwrap();
assert_eq!(game.attempts, 0);
assert_eq!(game.status, GameStatus::InProgress);
assert_eq!(game.to_string(), "|_||_||_||_||_||_||_|");
assert_eq!(
game.guess_word("самотаа").unwrap().to_string(),
"[С](А)>М<(О)>Т<(А)[А]"
);
assert_eq!(game.attempts, 1);
assert_eq!(game.status, GameStatus::InProgress);
assert_eq!(
game.to_string(),
"|_||_||_||_||_||_||_|\n[С](А)>М<(О)>Т<(А)[А]"
);
assert_eq!(
game.guess_word("свобода").unwrap().to_string(),
"[С][В][О][Б][О][Д][А]"
);
assert_eq!(game.attempts, 2);
assert_eq!(game.status, GameStatus::Won);
assert_eq!(
game.to_string(),
"|_||_||_||_||_||_||_|\n[С](А)>М<(О)>Т<(А)[А]\n[С][В][О][Б][О][Д][А]"
);
assert_eq!(
game.guess_word("свобода").unwrap_err(),
GameError::GameIsOver(GameStatus::Won)
);
}
#[test]
fn test_game_over() {
let english_letters = "abcdefghijklmnopqrstuvwxyz";
let mut game = Game::new(english_letters, "pig").unwrap();
assert_eq!(game.guess_word("cat").unwrap().to_string(), ">C<>A<>T<");
assert_eq!(game.attempts, 1);
assert_eq!(game.status, GameStatus::InProgress);
assert_eq!(game.guess_word("dog").unwrap().to_string(), ">D<>O<[G]");
assert_eq!(game.attempts, 2);
assert_eq!(game.status, GameStatus::InProgress);
assert_eq!(game.guess_word("rat").unwrap().to_string(), ">R<>A<>T<");
assert_eq!(game.attempts, 3);
assert_eq!(game.status, GameStatus::InProgress);
assert_eq!(game.guess_word("bat").unwrap().to_string(), ">B<>A<>T<");
assert_eq!(game.attempts, 4);
assert_eq!(game.status, GameStatus::InProgress);
assert_eq!(game.guess_word("hat").unwrap().to_string(), ">H<>A<>T<");
assert_eq!(game.status, GameStatus::Lost);
assert_eq!(game.attempts, 5);
assert_eq!(
game.to_string(),
"|_||_||_|\n>C<>A<>T<\n>D<>O<[G]\n>R<>A<>T<\n>B<>A<>T<\n>H<>A<>T<"
);
assert_eq!(
game.guess_word("pig").unwrap_err(),
GameError::GameIsOver(GameStatus::Lost)
);
}
#[test]
pub fn test_wrong_attempts() {
let english_letters = "abcdefghijklmnopqrstuvwxyz";
let mut game = Game::new(english_letters, "happy").unwrap();
assert_eq!(
game.guess_word("sad").unwrap_err(),
GameError::WrongLength {
expected: 5,
actual: 3
}
);
assert_eq!(
game.guess_word("happier").unwrap_err(),
GameError::WrongLength {
expected: 5,
actual: 7
}
);
assert_eq!(
game.guess_word("книга").unwrap_err(),
GameError::NotInAlphabet('к')
);
}
#[test]
fn test_solution() {
let english_letters = "abcdefghijklmnopqrstuvwxyz";
let mut game = Game::new(english_letters, "happy").unwrap();
assert_eq!(
game.guess_word("HaPpY").unwrap().to_string(),
"[H][A][P][P][Y]"
);
assert_eq!(game.status, GameStatus::Won);
assert_eq!(game.attempts, 1);
assert_eq!(game.to_string(), "|_||_||_||_||_|\n[H][A][P][P][Y]");
}
#[test]
fn test_game_with_numbers() {
let english_letters = "abcdefghijklmnopqrstuvwxyz0123456789";
let mut game = Game::new(english_letters, "1234567890").unwrap();
assert_eq!(
game.guess_word("1234567890").unwrap().to_string(),
"[1][2][3][4][5][6][7][8][9][0]"
);
assert_eq!(game.status, GameStatus::Won);
assert_eq!(game.attempts, 1);
assert_eq!(
game.to_string(),
"|_||_||_||_||_||_||_||_||_||_|\n[1][2][3][4][5][6][7][8][9][0]"
);
}
#[test]
fn test_game_with_russian_letters() {
let russian_letters = "абвгдеёжзийклмнопрстуфхцчшщъыьэюя";
let mut game = Game::new(russian_letters, "ребёнок").unwrap();
assert_eq!(
game.guess_word("ребёнок").unwrap().to_string(),
"[Р][Е][Б][Ё][Н][О][К]"
);
assert_eq!(game.status, GameStatus::Won);
assert_eq!(game.attempts, 1);
assert_eq!(game.to_string(), "|_||_||_||_||_||_||_|\n[Р][Е][Б][Ё][Н][О][К]");
}
#[test]
fn test_with_many_duplications() {
let english_letters = "abcdefghijklmnopqrstuvwxyz";
let mut game = Game::new(english_letters, "aaaaabbc").unwrap();
assert_eq!(
game.guess_word("aaaaaaaa").unwrap().to_string(),
"[A][A][A][A][A](A)(A)(A)"
);
assert_eq!(game.status, GameStatus::InProgress);
assert_eq!(game.attempts, 1);
assert_eq!(
game.guess_word("aaaaaaba").unwrap().to_string(),
"[A][A][A][A][A](A)[B](A)"
);
assert_eq!(game.status, GameStatus::InProgress);
assert_eq!(game.attempts, 2);
assert_eq!(
game.guess_word("aaaaaabb").unwrap().to_string(),
"[A][A][A][A][A](A)[B](B)"
);
assert_eq!(game.status, GameStatus::InProgress);
assert_eq!(game.attempts, 3);
assert_eq!(
game.guess_word("aaaaaabc").unwrap().to_string(),
"[A][A][A][A][A](A)[B][C]"
);
assert_eq!(game.status, GameStatus::InProgress);
assert_eq!(game.attempts, 4);
assert_eq!(
game.guess_word("aaaaabbc").unwrap().to_string(),
"[A][A][A][A][A][B][B][C]"
);
assert_eq!(game.status, GameStatus::Won);
assert_eq!(game.attempts, 5);
assert_eq!(
game.guess_word("aaaaabbc").unwrap_err(),
GameError::GameIsOver(GameStatus::Won)
);
assert_eq!(game.status, GameStatus::Won);
assert_eq!(game.attempts, 5);
}
}
Лог от изпълнението
Compiling solution v0.1.0 (/tmp/d20230111-3772066-ejmksx/solution) Finished test [unoptimized + debuginfo] target(s) in 0.82s 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_german ... ok test solution_test::test_game_display_cyrillic ... 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_construction_cyrrilic ... ok test solution_test::test_word_not_in_alphabet_on_guess ... ok test solution_test::test_wrong_length ... ok test solution_test::test_word_not_in_alphabet_on_guess_cyrillic ... ok test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s