Решение на Wordle от Петър Атанасов
Резултати
- 16 точки от тестове
- 0 бонус точки
- 16 точки общо
- 12 успешни тест(а)
- 3 неуспешни тест(а)
Код
use std::fmt;
#[derive(Debug, PartialEq, 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,
pub alphabet: String,
pub target_word: String,
pub history: Vec<Word>,
}
#[derive(Debug, Clone)]
pub struct Word {
pub letters: Vec<(char, LetterState)>,
}
#[derive(Debug, PartialEq, Clone)]
pub enum LetterState {
FullMatch,
PartialMatch,
NoMatch,
Unknown,
}
impl fmt::Display for Word {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for (a, b) in self.letters.iter() {
let to_print = char::to_uppercase(a.clone()).collect::<String>();
match b {
LetterState::NoMatch => write!(f, ">{}<", to_print)?,
LetterState::FullMatch => write!(f, "[{}]", to_print)?,
LetterState::PartialMatch => write!(f, "({})", to_print)?,
_ => write!(f, "|_|")?,
}
}
Ok(())
}
}
impl fmt::Display for Game {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
self.history
.iter()
.map(|word| word.to_string())
.reduce(|acc, word| acc + "\n" + &word)
.unwrap(),
)?;
Ok(())
}
}
impl Game {
fn get_first_diff(alphabet: &str, word: &str) -> Option<char> {
word.chars().filter(|&c| !alphabet.contains(c)).next()
}
/// Конструира нова игра с думи/букви от дадената в `alphabet` азбука. Alphabet е просто низ,
/// в който всеки символ е отделна буква, който вероятно искате да си запазите някак за после.
///
/// Подадената дума с `word` трябва да има само букви от тази азбука. Иначе очакваме да върнете
/// `GameError::NotInAlphabet` грешка с първия символ в `word`, който не е от азбуката.
///
/// Началното състояние на играта е `InProgress` а началния брой опити `attempts` е 0.
///
pub fn new(alphabet: &str, word: &str) -> Result<Self, GameError> {
let first_word: Vec<(char, LetterState)> =
vec![('_', LetterState::Unknown); word.chars().count()];
let initial_history = vec![Word {
letters: first_word,
}];
if let Some(c) = Game::get_first_diff(alphabet, word) {
Err(GameError::NotInAlphabet(c))
} else {
Ok(Game {
status: GameStatus::InProgress,
attempts: 0,
alphabet: String::from(alphabet),
target_word: String::from(word),
history: initial_history,
})
}
}
/// Опитва се да познае търсената дума. Опита е в `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::Won | GameStatus::Lost => return Err(GameError::GameIsOver(self.status)),
GameStatus::InProgress => (),
}
if guess.chars().count() != self.target_word.chars().count() {
return Err(GameError::WrongLength {
expected: self.target_word.chars().count(),
actual: guess.chars().count(),
});
}
if let Some(c) = Game::get_first_diff(&self.alphabet, guess) {
return Err(GameError::NotInAlphabet(c));
}
let add: Vec<(char, LetterState)> = guess
.chars()
.enumerate()
.map(|(a, b)| {
(
b,
match b {
_ if b == self.target_word.chars().nth(a).unwrap() => {
LetterState::FullMatch
}
_ if self.target_word.contains(b) => LetterState::PartialMatch,
_ => LetterState::NoMatch,
},
)
})
.collect();
let out_word = Word { letters: add };
self.history.push(out_word.clone());
Ok(out_word)
}
}
#[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]");
let german_letters = "abcdefghijklmnopqrstuvwxyzäöüß";
let mut game = Game::new(german_letters, "süß").unwrap();
assert_eq!(game.gupess_word("füß").unwrap().to_string(), ">F<[Ü][SS]");
// >F<[Ü][SS]
}
Лог от изпълнението
Compiling solution v0.1.0 (/tmp/d20230111-3772066-thpeji/solution) Finished test [unoptimized + debuginfo] target(s) in 0.87s 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 ... FAILED test solution_test::test_game_state_2 ... FAILED test solution_test::test_game_state_3 ... FAILED test solution_test::test_word_display ... ok test solution_test::test_word_display_german ... ok test solution_test::test_word_display_bulgarian ... 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_guess_cyrillic ... ok test solution_test::test_word_not_in_alphabet_on_construction_cyrrilic ... ok test solution_test::test_wrong_length ... ok failures: ---- solution_test::test_game_state_1 stdout ---- thread 'solution_test::test_game_state_1' panicked at 'Expression InProgress does not match the pattern "GameStatus::Won"', tests/solution_test.rs:145: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 InProgress does not match the pattern "GameStatus::Lost"', tests/solution_test.rs:159:5 ---- solution_test::test_game_state_3 stdout ---- thread 'solution_test::test_game_state_3' panicked at 'assertion failed: `(left == right)` left: `0`, right: `1`', tests/solution_test.rs:172:9 failures: solution_test::test_game_state_1 solution_test::test_game_state_2 solution_test::test_game_state_3 test result: FAILED. 12 passed; 3 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s error: test failed, to rerun pass `--test solution_test`
История (2 версии и 0 коментара)
Петър качи решение на 24.11.2022 16:04 (преди почти 3 години)
use std::fmt;
#[derive(Debug, PartialEq, 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,
pub alphabet: String,
pub target_word: String,
pub history: Vec<Word>,
- // Каквито други полета ви трябват
}
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub struct Word {
- pub letters: Vec<(char, LetterState)>, // Каквито полета ви трябват
+ pub letters: Vec<(char, LetterState)>,
}
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Clone)]
pub enum LetterState {
FullMatch,
PartialMatch,
NoMatch,
Unknown,
}
impl fmt::Display for Word {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- for letter in self.letters.iter() {
- let to_print = char::to_uppercase(letter.0).next().unwrap();
- if letter.1 == LetterState::FullMatch {
- write!(f, "[{}]", to_print)?;
- } else if letter.1 == LetterState::PartialMatch {
- write!(f, "({})", to_print)?;
- } else if letter.1 == LetterState::NoMatch {
- write!(f, ">{}<", to_print)?;
- } else {
- write!(f, "|_|")?;
+ for (a, b) in self.letters.iter() {
+ let to_print = char::to_uppercase(a.clone()).collect::<String>();
+ match b {
+ LetterState::NoMatch => write!(f, ">{}<", to_print)?,
+ LetterState::FullMatch => write!(f, "[{}]", to_print)?,
+ LetterState::PartialMatch => write!(f, "({})", to_print)?,
+ _ => write!(f, "|_|")?,
}
}
Ok(())
}
}
impl fmt::Display for Game {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- for word in &self.history {
- write!(f, "{}", word)?;
- }
+ write!(
+ f,
+ "{}",
+ self.history
+ .iter()
+ .map(|word| word.to_string())
+ .reduce(|acc, word| acc + "\n" + &word)
+ .unwrap(),
+ )?;
+
Ok(())
}
}
impl Game {
fn get_first_diff(alphabet: &str, word: &str) -> Option<char> {
- let mut check = word.chars().filter(|&c| !alphabet.contains(c));
-
- check.nth(0)
+ word.chars().filter(|&c| !alphabet.contains(c)).next()
}
/// Конструира нова игра с думи/букви от дадената в `alphabet` азбука. Alphabet е просто низ,
/// в който всеки символ е отделна буква, който вероятно искате да си запазите някак за после.
///
/// Подадената дума с `word` трябва да има само букви от тази азбука. Иначе очакваме да върнете
/// `GameError::NotInAlphabet` грешка с първия символ в `word`, който не е от азбуката.
///
/// Началното състояние на играта е `InProgress` а началния брой опити `attempts` е 0.
///
pub fn new(alphabet: &str, word: &str) -> Result<Self, GameError> {
- let mut first_word: Vec<(char, LetterState)> = Vec::new();
- for _ in 0..word.chars().count() {
- first_word.push(('a', LetterState::Unknown));
- }
- let mut initial_history = Vec::new();
- initial_history.push(Word {
+ let first_word: Vec<(char, LetterState)> =
+ vec![('_', LetterState::Unknown); word.chars().count()];
+ let initial_history = vec![Word {
letters: first_word,
- });
+ }];
+
if let Some(c) = Game::get_first_diff(alphabet, word) {
Err(GameError::NotInAlphabet(c))
} else {
Ok(Game {
status: GameStatus::InProgress,
attempts: 0,
alphabet: String::from(alphabet),
target_word: String::from(word),
history: initial_history,
})
}
}
/// Опитва се да познае търсената дума. Опита е в `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::Won | GameStatus::Lost => return Err(GameError::GameIsOver(self.status)),
GameStatus::InProgress => (),
}
if guess.chars().count() != self.target_word.chars().count() {
return Err(GameError::WrongLength {
expected: self.target_word.chars().count(),
actual: guess.chars().count(),
});
}
if let Some(c) = Game::get_first_diff(&self.alphabet, guess) {
return Err(GameError::NotInAlphabet(c));
}
let add: Vec<(char, LetterState)> = guess
.chars()
.enumerate()
- .map(|pair| {
- if pair.1 == self.target_word.chars().nth(pair.0).unwrap() {
- (pair.1, LetterState::FullMatch)
- } else if self.target_word.contains(pair.1) {
- (pair.1, LetterState::PartialMatch)
- } else {
- (pair.1, LetterState::NoMatch)
- }
+ .map(|(a, b)| {
+ (
+ b,
+ match b {
+ _ if b == self.target_word.chars().nth(a).unwrap() => {
+ LetterState::FullMatch
+ }
+ _ if self.target_word.contains(b) => LetterState::PartialMatch,
+ _ => LetterState::NoMatch,
+ },
+ )
})
.collect();
let out_word = Word { letters: add };
+ self.history.push(out_word.clone());
Ok(out_word)
}
}
#[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]");
let german_letters = "abcdefghijklmnopqrstuvwxyzäöüß";
let mut game = Game::new(german_letters, "süß").unwrap();
- assert_eq!(game.guess_word("füß").unwrap().to_string(), ">F<[Ü][S]");
-
- let bulgarian_letters = "абвгдежзийклмнопрстуфхцчшщьюя";
- let mut game = Game::new(bulgarian_letters, "дебеллебед").unwrap();
-
- assert_eq!(
- game.guess_word("даепбделжт").unwrap().to_string(),
- "[Д]>А<(Е)>П<(Б)(Д)[Е](Л)>Ж<>Т<"
- );
+ assert_eq!(game.gupess_word("füß").unwrap().to_string(), ">F<[Ü][SS]");
+ // >F<[Ü][SS]
}