Решение на Wordle от Димитър Димитров

Обратно към всички решения

Към профила на Димитър Димитров

Резултати

  • 19 точки от тестове
  • 0 бонус точки
  • 19 точки общо
  • 14 успешни тест(а)
  • 1 неуспешни тест(а)

Код

use std::{
collections::{HashMap, HashSet},
fmt,
};
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum GameStatus {
InProgress,
Won,
Lost,
}
#[derive(Debug, PartialEq)]
pub enum GameError {
NotInAlphabet(char),
WrongLength { expected: usize, actual: usize },
GameIsOver(GameStatus),
}
// Тъй като Words вече е заето от (дума, оценка на съвпадения)
type LetterList = Vec<char>;
#[derive(Debug)]
pub struct Game {
pub status: GameStatus,
pub attempts: u8,
alphabet: HashSet<char>,
goal_letter_list: LetterList,
words: Vec<Word>,
}
#[derive(Copy, Clone, Debug, PartialEq)]
enum LetterGuessMatch {
Correct,
Partial,
Wrong,
}
#[derive(Clone, Debug)]
pub struct Word {
letter_list: LetterList,
guess_matches: Vec<LetterGuessMatch>,
}
impl Word {
fn is_correct(&self) -> bool {
self.guess_matches
.iter()
.find(|guess_match| !matches!(**guess_match, LetterGuessMatch::Correct))
.is_none()
}
}
impl Word {
fn fmt_letter(
f: &mut fmt::Formatter,
letter: &char,
guess_match: &LetterGuessMatch,
) -> fmt::Result {
let upper: String = letter.to_uppercase().collect();
match guess_match {
LetterGuessMatch::Correct => write!(f, "[{}]", upper),
LetterGuessMatch::Partial => write!(f, "({})", upper),
LetterGuessMatch::Wrong => write!(f, ">{}<", upper),
}
}
}
impl fmt::Display for Word {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.letter_list.iter().zip(self.guess_matches.iter()).fold(
Ok(()),
|prev, (letter, guess_match)| {
prev.and_then(|_| Word::fmt_letter(f, letter, guess_match))
},
)
}
}
impl Game {
const MAX_ATTEMPTS: u8 = 5;
fn first_incompatible_letter_with_alphabet(
alphabet: &HashSet<char>,
letter_list: &LetterList,
) -> Option<char> {
letter_list
.iter()
.find(|letter| !alphabet.contains(letter))
.map(|letter| *letter)
}
fn first_incompatible_letter(&self, letter_list: &LetterList) -> Option<char> {
Game::first_incompatible_letter_with_alphabet(&self.alphabet, letter_list)
}
pub fn new(alphabet: &str, word: &str) -> Result<Self, GameError> {
let alphabet: HashSet<char> = alphabet.chars().collect();
let goal_letter_list: Vec<char> = word.chars().collect();
if let Some(letter) =
Game::first_incompatible_letter_with_alphabet(&alphabet, &goal_letter_list)
{
Err(GameError::NotInAlphabet(letter))
} else {
Ok(Game {
alphabet,
goal_letter_list,
status: GameStatus::InProgress,
attempts: 0,
words: Vec::new(),
})
}
}
fn apply_hints_to_guess_matches(
guess_matches: &mut Vec<LetterGuessMatch>,
hints: &HashMap<char, usize>,
unmatched: &HashMap<char, Vec<usize>>,
) {
for (hint_char, hint_count) in hints.iter() {
if let Some(hint_indexes) = unmatched.get(hint_char) {
for index in hint_indexes.iter().take(*hint_count) {
guess_matches[*index] = LetterGuessMatch::Partial;
}
}
}
}
fn get_guess_matches(&self, valid_guess: &LetterList) -> Vec<LetterGuessMatch> {
let letter_list_len = self.goal_letter_list.len();
let mut guess_matches: Vec<LetterGuessMatch> = Vec::with_capacity(letter_list_len);
let mut hints: HashMap<char, usize> = HashMap::new();
let mut unmatched: HashMap<char, Vec<usize>> = HashMap::new();
for index in 0..letter_list_len {
let ref goal = self.goal_letter_list[index];
let ref guess = valid_guess[index];
if guess == goal {
guess_matches.push(LetterGuessMatch::Correct);
} else {
// Preliminary
guess_matches.push(LetterGuessMatch::Wrong);
*hints.entry(*goal).or_insert(0) += 1;
unmatched.entry(*guess).or_insert(Vec::new()).push(index);
}
}
Game::apply_hints_to_guess_matches(&mut guess_matches, &hints, &unmatched);
guess_matches
}
fn find_game_error(&self, guess: &LetterList) -> Option<GameError> {
if !matches!(self.status, GameStatus::InProgress) {
Some(GameError::GameIsOver(self.status))
} else if guess.len() != self.goal_letter_list.len() {
Some(GameError::WrongLength {
expected: self.goal_letter_list.len(),
actual: guess.len(),
})
} else if let Some(letter) = self.first_incompatible_letter(guess) {
Some(GameError::NotInAlphabet(letter))
} else {
None
}
}
fn evaluate_guess(&self, guess: &str) -> Result<Word, GameError> {
let letter_list = guess.chars().collect();
if let Some(error) = self.find_game_error(&letter_list) {
Err(error)
} else {
let guess_matches = self.get_guess_matches(&letter_list);
Ok(Word {
letter_list,
guess_matches,
})
}
}
fn save_word(&mut self, word: &Word) -> () {
self.words.push(word.clone());
}
pub fn guess_word(&mut self, guess: &str) -> Result<Word, GameError> {
let guess_evaluation = self.evaluate_guess(guess);
if let Ok(ref word) = guess_evaluation {
self.save_word(word);
self.attempts += 1;
if word.is_correct() {
self.status = GameStatus::Won
} else if self.attempts == Game::MAX_ATTEMPTS {
self.status = GameStatus::Lost
}
}
guess_evaluation
}
}
impl Game {
fn get_fmt_template(&self) -> String {
self.goal_letter_list
.iter()
.map(|_| "|_|")
.collect::<String>()
}
fn fmt_template(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.get_fmt_template())
}
}
impl fmt::Display for Game {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.fmt_template(f).and_then(|_| {
self.words.iter().fold(Ok(()), |prev, word| {
prev.and_then(|_| write!(f, "\n")).and_then(|_| word.fmt(f))
})
})
}
}
#[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]");
}
#[test]
fn goal_not_in_alphabet() {
let english_letters = "abcdefghijklmnopqrstuvwxyz";
assert_eq!(
Game::new(english_letters, "aaa1").unwrap_err(),
GameError::NotInAlphabet('1')
);
}
#[test]
fn guess_not_in_alphabet() {
let english_letters = "abcdefghijklmnopqrstuvwxyz";
let mut game = Game::new(english_letters, "aaaa").unwrap();
assert_eq!(
game.guess_word("aa1a").unwrap_err(),
GameError::NotInAlphabet('1')
);
}
#[test]
fn guess_wrong_length() {
let english_letters = "abcdefghijklmnopqrstuvwxyz";
let mut game = Game::new(english_letters, "aaaa").unwrap();
assert_eq!(
game.guess_word("aa").unwrap_err(),
GameError::WrongLength {
expected: 4,
actual: 2
}
);
}
#[test]
fn guess_wrong_length_takes_precedence_over_not_in_alphabet() {
let english_letters = "abcdefghijklmnopqrstuvwxyz";
let mut game = Game::new(english_letters, "aaaa").unwrap();
assert_eq!(
game.guess_word("11").unwrap_err(),
GameError::WrongLength {
expected: 4,
actual: 2
}
);
}
#[test]
fn sample_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]");
}
#[test]
fn repeated_letters() {
let english_letters = "abcdefghijklmnopqrstuvwxyz";
let mut game = Game::new(english_letters, "cool").unwrap();
assert_eq!(game.guess_word("ooll").unwrap().to_string(), "(O)[O]>L<[L]");
assert_eq!(game.guess_word("oool").unwrap().to_string(), ">O<[O][O][L]");
assert_eq!(game.guess_word("oolo").unwrap().to_string(), "(O)[O](L)>O<");
}
#[test]
fn game_display() {
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 win_flow() {
let english_letters = "abcdefghijklmnopqrstuvwxyz";
let mut game = Game::new(english_letters, "rebus").unwrap();
let _ = game.guess_word("aaaaa");
let _ = game.guess_word("aaaaa");
let _ = game.guess_word("rebus");
assert_eq!(game.status, GameStatus::Won);
assert_eq!(game.attempts, 3);
assert_eq!(
game.guess_word("rebus").unwrap_err(),
GameError::GameIsOver(GameStatus::Won)
);
}
#[test]
fn lose_flow() {
let english_letters = "abcdefghijklmnopqrstuvwxyz";
let mut game = Game::new(english_letters, "rebus").unwrap();
let _ = game.guess_word("aaaaa");
let _ = game.guess_word("aaaaa");
let _ = game.guess_word("aaaaa");
let _ = game.guess_word("aaaaa");
let _ = game.guess_word("aaaaa");
assert_eq!(game.status, GameStatus::Lost);
assert_eq!(game.attempts, 5);
assert_eq!(
game.guess_word("rebus").unwrap_err(),
GameError::GameIsOver(GameStatus::Lost)
);
}

Лог от изпълнението

Compiling solution v0.1.0 (/tmp/d20230111-3772066-6oro9j/solution)
    Finished test [unoptimized + debuginfo] target(s) in 1.04s
     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_word_display ... ok
test solution_test::test_game_state_3 ... ok
test solution_test::test_word_display_bulgarian ... FAILED
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_word_not_in_alphabet_on_guess_cyrillic ... ok
test solution_test::test_wrong_length ... ok

failures:

---- solution_test::test_word_display_bulgarian stdout ----
thread 'solution_test::test_word_display_bulgarian' panicked at 'assertion failed: `(left == right)`
  left: `"(Л)>А<>Л<>Е<"`,
 right: `"(Л)>А<(Л)>Е<"`', tests/solution_test.rs:77:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    solution_test::test_word_display_bulgarian

test result: FAILED. 14 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

error: test failed, to rerun pass `--test solution_test`

История (1 версия и 1 коментар)

Димитър качи първо решение на 24.11.2022 12:53 (преди почти 3 години)

Тестът със стол и лале се проваля, защото на 1 'л' в целевата дума се очакват 2 подсказки. Това е малко съмнително, защото сякаш означава, че в целевата дума има 2 'л'.

Предполагам обаче е очакваното поведение, защото в условието пише: Ако буквата присъства в думата някъде другаде, форматираме я като (Щ).

Оригиналният wordle не се държи така (последното изображение от линка): https://nerdschalk.com/wordle-same-letter-twice-rules-explained-how-does-it-work/

Чисто информативно, не ме е яд за едната точка (много).