Решение на Hangman от Радослав Георгиев

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

Към профила на Радослав Георгиев

Резултати

  • 13 точки от тестове
  • 0 бонус точки
  • 13 точки общо
  • 13 успешни тест(а)
  • 2 неуспешни тест(а)

Код

use std::fmt::{self, Display};
#[derive(Debug)]
pub enum GameError {
ParseError(String),
BadGuess(String),
InvalidSolution(String),
GameOver,
}
impl Display for GameError {
/// Имплементацията на този метод може да върне какъвто низ искате, но типа `GameError` трябва
/// да имплементира trait-а. Чувствайте се свободни да бъдете креативни със съобщенията, или не.
///
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&GameError::ParseError(ref msg) => f.write_fmt(format_args!("Syntax error: parsing command line failed: {}!", msg)),
&GameError::BadGuess(ref msg) => f.write_fmt(format_args!("Bad guess: {}!", msg)),
&GameError::InvalidSolution(ref msg) => f.write_fmt(format_args!("Argument error: invalid solution word: {}!", msg)),
&GameError::GameOver => f.write_str("Game over."),
}
}
}
use std::str::FromStr;
#[derive(Debug)]
pub enum Command {
TryLetter(char),
TryWord(String),
Info,
Help,
Quit,
}
impl FromStr for Command {
type Err = GameError;
/// Този метод ще приеме string slice, и ще върне нова команда, която му съответства. Правилата
/// за това кои низове се превръщат в какви команди са по-долу.
///
/// В случай на грешка, винаги ще се върне `GameError::ParseError`. С какъвто низ искате -- било то
/// само входния низ, или пълно съобщение за грешка.
///
fn from_str(s: &str) -> Result<Self, Self::Err> {
let command_line = s.to_lowercase();
let mut words = command_line.split_whitespace();
match words.next() {
Some("help") | Some("hel") | Some("he") | Some("h") => Ok(Command::Help),
Some("info") | Some("inf") | Some("in") | Some("i") => Ok(Command::Info),
Some("quit") | Some("qui") | Some("qu") | Some("q") => Ok(Command::Quit),
Some("try") | Some("tr") | Some("t") => match words.next() {
Some("letter") | Some("lette") | Some("lett") | Some("let") | Some("le") | Some("l") => match words.next() {
Some(letter_str) => {
match letter_str.len() {

Метода len на str не брои символи, брои байтове :). Ето документацията: https://doc.rust-lang.org/std/primitive.str.html#method.len

Това може би е нещо, което трябваше да кажем експлицитно в някакъв момент. Все пак, можеше да го изтестваш с кирилица. Това е бъга, който ти коства 2 точки. Вярната проверка би била letter_str.chars().count().

1 => {
let letter = letter_str.chars().next().unwrap();
if letter.is_alphabetic() {
Ok(Command::TryLetter(letter))
} else {
Err(GameError::ParseError(format!("`try letter {}`: invalid (i.e. non-alphabetic) character passed as letter argument to `try letter` command", letter_str)))
}
},
_ => Err(GameError::ParseError(format!("`try letter {}`: more than one character passed as letter argument to `try letter` command", letter_str))),
}
},
None => Err(GameError::ParseError(String::from("`try letter`: missing letter argument to `try letter` command"))),
},
Some("word") | Some("wor") | Some("wo") | Some("w") => match words.next() {
Some(word) => Ok(Command::TryWord(String::from(word))),
None => Err(GameError::ParseError(String::from("`try word`: missing word argument to `try word` command"))),
},
Some(command) => Err(GameError::ParseError(format!("`try {}`: invalid argument to `try` command", command))),
None => Err(GameError::ParseError(String::from("`try`: not enough arguments to `try` command"))),
},
Some(command) => Err(GameError::ParseError(format!("`{}`: invalid command", command))),
None => Err(GameError::ParseError(String::from("command line is empty"))),
}
}
}
use std::collections::HashSet;
pub struct Game {
/// Букви, които вече са били пробвани
pub attempted_letters: HashSet<char>,
/// Думи, които вече са били пробвани
pub attempted_words: HashSet<String>,
/// Брой на оставащите опити.
pub attempts_remaining: u32,
/// Всички познати букви от решението.
pub guessed_letters: HashSet<char>,
/// Дали е била позната думата отведнъж.
pub is_guessed_word: bool,
/// Самото решение.
pub solution_word: String,
}
impl Game {
/// Първия аргумент е думата, която ще е правилния отговор на играта. Вижте по-долу за
/// ограниченията, които трябва да спазва.
///
/// Втория аргумент е броя опити, които има играча да познае думата. Когато ударят 0, играта
/// минава в състояние на "загуба". Запишете тази стойност в `self.attempts_remaining`.
///
pub fn new(solution: &str, attempts: u32) -> Result<Self, GameError> {
if solution.is_empty() {
Err(GameError::InvalidSolution(String::from("solution word is empty")))
} else if solution.chars().any(|c| !c.is_alphabetic()) {
Err(GameError::InvalidSolution(format!("{}: there are non-alphabetic characters in solution word", solution)))
} else {
Ok(Self {
attempted_letters: HashSet::new(),
attempted_words: HashSet::new(),
attempts_remaining: attempts,
guessed_letters: HashSet::new(),
is_guessed_word: false,
solution_word: solution.to_lowercase().to_string(),
})
}
}
/// Приема символ, проверява дали този символ присъства в решението:
///
/// - Ако да, връща `Ok(true)`
/// - Ако не, връща `Ok(false)` и намалява `self.attempts_remaining` с 1.
///
/// Ако символа вече е бил пробван, връща `Err(GameError::BadGuess)` с каквото съобщение пожелаете.
///
/// Ако `self.attempts_remaining` е 0, играта е приключила със загуба. По-долу ще видите как
/// това става ясно за потребителя.
///
/// Ако думата е позната (всичките ѝ символи са "разкрити"), играта е приключила с победа.
/// По-долу ще видите как това става ясно за потребителя.
///
pub fn guess_letter(&mut self, guess: char) -> Result<bool, GameError> {
let guess_lowercase = guess.to_lowercase().next().unwrap();
if self.is_over() {
Err(GameError::GameOver)
} else if self.attempted_letters.contains(&guess_lowercase) {
Err(GameError::BadGuess(String::from("letter was already attempted")))
} else if self.solution_word.contains(guess_lowercase) {
self.guessed_letters.insert(guess_lowercase);
self.attempted_letters.insert(guess_lowercase);
Ok(true)
} else {
self.attempts_remaining -= 1;
self.attempted_letters.insert(guess_lowercase);
Ok(false)
}
}
/// Приема дума, проверява дали тази дума присъства в решението:
///
/// - Ако да, връща `Ok(true)`
/// - Ако не, връща `Ok(false)` и намалява `self.attempts_remaining` с 1.
///
/// Ако думата вече е бил пробвана, връща `Err(GameError::BadGuess)` с каквото съобщение пожелаете.
///
/// Ако `self.attempts_remaining` е 0, играта е приключила със загуба. По-долу ще видите как
/// това става ясно за потребителя.
///
/// Ако думата е позната, играта е приключила с победа. По-долу ще видите как това става ясно
/// за потребителя.
///
pub fn guess_word(&mut self, guess: &str) -> Result<bool, GameError> {
let guess_lowercase = guess.to_lowercase();
if self.is_over() {
Err(GameError::GameOver)
} else if self.attempted_words.contains(&guess_lowercase) {
Err(GameError::BadGuess(String::from("word was already attempted")))
} else if self.solution_word == guess_lowercase {
self.is_guessed_word = true;
self.attempted_words.insert(guess_lowercase);
Ok(true)
} else {
self.attempts_remaining -= 1;
self.attempted_words.insert(guess_lowercase);
Ok(false)
}
}
/// Връща `true`, ако играта е приключила, по един или друг начин. Иначе връща `false`.
///
pub fn is_over(&self) -> bool {
self.is_lost() || self.is_won()
}
/// Връща `true`, ако играчът е познал думата. Иначе връща `false`.
pub fn is_won(&self) -> bool {
self.is_guessed_word || self.solution_word.chars().all(|ref letter| self.guessed_letters.contains(letter))
}
/// Връща `true`, ако играта е приключила, без играчът да познае думата. Иначе връща `false`.
pub fn is_lost(&self) -> bool {
self.attempts_remaining == 0
}
}
impl Display for Game {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.is_won() {
f.write_fmt(format_args!("You won! ^_^\nThe word was: {}", self.solution_word))
} else if self.is_lost() {
f.write_fmt(format_args!("You lost! :/\nThe word was: {}", self.solution_word))
} else {
try!(f.write_fmt(format_args!("Attempts remaining: {}\nGuess:", self.attempts_remaining)));
for letter in self.solution_word.chars() {
if self.guessed_letters.contains(&letter) {
try!(f.write_fmt(format_args!(" {}", letter)));
} else {
try!(f.write_str(" _"));

Аз направих специален случай за първия символ, но, както добре си се досетил, спокойно можеш да си добавиш интервал преди всеки символ/подчертавка, без това преди тази част имаш "Guess:". Добра работа :).

}
}
f.write_str("\n")
}
}
}

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

Compiling solution v0.1.0 (file:///tmp/d20171210-6053-wne7m2/solution)
    Finished dev [unoptimized + debuginfo] target(s) in 5.58 secs
     Running target/debug/deps/solution-3f98bfa5c86a5dd9

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

     Running target/debug/deps/solution_test-3d9e4ea2eafbbc82

running 15 tests
test solution_test::test_command_parsing_cyrillic ... FAILED
test solution_test::test_command_parsing_extra_stuff ... ok
test solution_test::test_command_parsing_full_words ... ok
test solution_test::test_command_parsing_partial_words ... ok
test solution_test::test_command_parsing_spacing ... ok
test solution_test::test_command_parsing_special ... FAILED
test solution_test::test_game_basic ... ok
test solution_test::test_game_cyrillic ... ok
test solution_test::test_game_display ... ok
test solution_test::test_game_error ... ok
test solution_test::test_game_guess_basic ... ok
test solution_test::test_game_guess_state_lose ... ok
test solution_test::test_game_guess_state_won ... ok
test solution_test::test_game_guess_word ... ok
test solution_test::test_game_over_guesses ... ok

failures:

---- solution_test::test_command_parsing_cyrillic stdout ----
	thread 'solution_test::test_command_parsing_cyrillic' panicked at 'Expected Ok(Command::TryLetter('\u{44f}')) to match Err(ParseError("`try letter я`: more than one character passed as letter argument to `try letter` command"))', tests/solution_test.rs:235:4
note: Run with `RUST_BACKTRACE=1` for a backtrace.

---- solution_test::test_command_parsing_special stdout ----
	thread 'solution_test::test_command_parsing_special' panicked at 'Expected Ok(Command::TryLetter('\u{44f}')) to match Err(ParseError("`try letter я`: more than one character passed as letter argument to `try letter` command"))', tests/solution_test.rs:194:4


failures:
    solution_test::test_command_parsing_cyrillic
    solution_test::test_command_parsing_special

test result: FAILED. 13 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out

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

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

Радослав качи решение на 08.12.2017 12:41 (преди почти 8 години)

use std::fmt::{self, Display};
#[derive(Debug)]
pub enum GameError {
ParseError(String),
BadGuess(String),
InvalidSolution(String),
GameOver,
}
impl Display for GameError {
/// Имплементацията на този метод може да върне какъвто низ искате, но типа `GameError` трябва
/// да имплементира trait-а. Чувствайте се свободни да бъдете креативни със съобщенията, или не.
///
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&GameError::ParseError(ref msg) => f.write_fmt(format_args!("Syntax error: parsing command line failed: {}!", msg)),
&GameError::BadGuess(ref msg) => f.write_fmt(format_args!("Bad guess: {}!", msg)),
&GameError::InvalidSolution(ref msg) => f.write_fmt(format_args!("Argument error: invalid solution word: {}!", msg)),
&GameError::GameOver => f.write_str("Game over."),
}
}
}
use std::str::FromStr;
#[derive(Debug)]
pub enum Command {
TryLetter(char),
TryWord(String),
Info,
Help,
Quit,
}
impl FromStr for Command {
type Err = GameError;
/// Този метод ще приеме string slice, и ще върне нова команда, която му съответства. Правилата
/// за това кои низове се превръщат в какви команди са по-долу.
///
/// В случай на грешка, винаги ще се върне `GameError::ParseError`. С какъвто низ искате -- било то
/// само входния низ, или пълно съобщение за грешка.
///
fn from_str(s: &str) -> Result<Self, Self::Err> {
let command_line = s.to_lowercase();
let mut words = command_line.split_whitespace();
match words.next() {
Some("help") | Some("hel") | Some("he") | Some("h") => Ok(Command::Help),
Some("info") | Some("inf") | Some("in") | Some("i") => Ok(Command::Info),
Some("quit") | Some("qui") | Some("qu") | Some("q") => Ok(Command::Quit),
Some("try") | Some("tr") | Some("t") => match words.next() {
Some("letter") | Some("lette") | Some("lett") | Some("let") | Some("le") | Some("l") => match words.next() {
Some(letter_str) => {
match letter_str.len() {

Метода len на str не брои символи, брои байтове :). Ето документацията: https://doc.rust-lang.org/std/primitive.str.html#method.len

Това може би е нещо, което трябваше да кажем експлицитно в някакъв момент. Все пак, можеше да го изтестваш с кирилица. Това е бъга, който ти коства 2 точки. Вярната проверка би била letter_str.chars().count().

1 => {
let letter = letter_str.chars().next().unwrap();
if letter.is_alphabetic() {
Ok(Command::TryLetter(letter))
} else {
Err(GameError::ParseError(format!("`try letter {}`: invalid (i.e. non-alphabetic) character passed as letter argument to `try letter` command", letter_str)))
}
},
_ => Err(GameError::ParseError(format!("`try letter {}`: more than one character passed as letter argument to `try letter` command", letter_str))),
}
},
None => Err(GameError::ParseError(String::from("`try letter`: missing letter argument to `try letter` command"))),
},
Some("word") | Some("wor") | Some("wo") | Some("w") => match words.next() {
Some(word) => Ok(Command::TryWord(String::from(word))),
- None => Err(GameError::ParseError(String::from("`try word`: not enough arguments to `try word` command"))),
+ None => Err(GameError::ParseError(String::from("`try word`: missing word argument to `try word` command"))),
},
Some(command) => Err(GameError::ParseError(format!("`try {}`: invalid argument to `try` command", command))),
None => Err(GameError::ParseError(String::from("`try`: not enough arguments to `try` command"))),
},
Some(command) => Err(GameError::ParseError(format!("`{}`: invalid command", command))),
None => Err(GameError::ParseError(String::from("command line is empty"))),
}
}
}
use std::collections::HashSet;
pub struct Game {
/// Букви, които вече са били пробвани
pub attempted_letters: HashSet<char>,
/// Думи, които вече са били пробвани
pub attempted_words: HashSet<String>,
/// Брой на оставащите опити.
pub attempts_remaining: u32,
/// Всички познати букви от решението.
pub guessed_letters: HashSet<char>,
/// Дали е била позната думата отведнъж.
pub is_guessed_word: bool,
/// Самото решение.
pub solution_word: String,
}
impl Game {
/// Първия аргумент е думата, която ще е правилния отговор на играта. Вижте по-долу за
/// ограниченията, които трябва да спазва.
///
/// Втория аргумент е броя опити, които има играча да познае думата. Когато ударят 0, играта
/// минава в състояние на "загуба". Запишете тази стойност в `self.attempts_remaining`.
///
pub fn new(solution: &str, attempts: u32) -> Result<Self, GameError> {
- // ...
if solution.is_empty() {
Err(GameError::InvalidSolution(String::from("solution word is empty")))
} else if solution.chars().any(|c| !c.is_alphabetic()) {
Err(GameError::InvalidSolution(format!("{}: there are non-alphabetic characters in solution word", solution)))
} else {
Ok(Self {
attempted_letters: HashSet::new(),
attempted_words: HashSet::new(),
attempts_remaining: attempts,
guessed_letters: HashSet::new(),
is_guessed_word: false,
- solution_word: solution.to_string(),
+ solution_word: solution.to_lowercase().to_string(),
})
}
}
/// Приема символ, проверява дали този символ присъства в решението:
///
/// - Ако да, връща `Ok(true)`
/// - Ако не, връща `Ok(false)` и намалява `self.attempts_remaining` с 1.
///
/// Ако символа вече е бил пробван, връща `Err(GameError::BadGuess)` с каквото съобщение пожелаете.
///
/// Ако `self.attempts_remaining` е 0, играта е приключила със загуба. По-долу ще видите как
/// това става ясно за потребителя.
///
/// Ако думата е позната (всичките ѝ символи са "разкрити"), играта е приключила с победа.
/// По-долу ще видите как това става ясно за потребителя.
///
pub fn guess_letter(&mut self, guess: char) -> Result<bool, GameError> {
+ let guess_lowercase = guess.to_lowercase().next().unwrap();
if self.is_over() {
Err(GameError::GameOver)
- } else if self.attempted_letters.contains(&guess) {
+ } else if self.attempted_letters.contains(&guess_lowercase) {
Err(GameError::BadGuess(String::from("letter was already attempted")))
- } else if self.solution_word.contains(guess) {
- self.guessed_letters.insert(guess);
- self.attempted_letters.insert(guess);
+ } else if self.solution_word.contains(guess_lowercase) {
+ self.guessed_letters.insert(guess_lowercase);
+ self.attempted_letters.insert(guess_lowercase);
Ok(true)
} else {
self.attempts_remaining -= 1;
- self.attempted_letters.insert(guess);
+ self.attempted_letters.insert(guess_lowercase);
Ok(false)
}
}
/// Приема дума, проверява дали тази дума присъства в решението:
///
/// - Ако да, връща `Ok(true)`
/// - Ако не, връща `Ok(false)` и намалява `self.attempts_remaining` с 1.
///
/// Ако думата вече е бил пробвана, връща `Err(GameError::BadGuess)` с каквото съобщение пожелаете.
///
/// Ако `self.attempts_remaining` е 0, играта е приключила със загуба. По-долу ще видите как
/// това става ясно за потребителя.
///
/// Ако думата е позната, играта е приключила с победа. По-долу ще видите как това става ясно
/// за потребителя.
///
pub fn guess_word(&mut self, guess: &str) -> Result<bool, GameError> {
+ let guess_lowercase = guess.to_lowercase();
if self.is_over() {
Err(GameError::GameOver)
- } else if self.attempted_words.contains(guess) {
+ } else if self.attempted_words.contains(&guess_lowercase) {
Err(GameError::BadGuess(String::from("word was already attempted")))
- } else if self.solution_word == guess {
+ } else if self.solution_word == guess_lowercase {
self.is_guessed_word = true;
- self.attempted_words.insert(guess.to_string());
+ self.attempted_words.insert(guess_lowercase);
Ok(true)
} else {
self.attempts_remaining -= 1;
- self.attempted_words.insert(guess.to_string());
+ self.attempted_words.insert(guess_lowercase);
Ok(false)
}
}
/// Връща `true`, ако играта е приключила, по един или друг начин. Иначе връща `false`.
///
pub fn is_over(&self) -> bool {
self.is_lost() || self.is_won()
}
/// Връща `true`, ако играчът е познал думата. Иначе връща `false`.
pub fn is_won(&self) -> bool {
self.is_guessed_word || self.solution_word.chars().all(|ref letter| self.guessed_letters.contains(letter))
}
/// Връща `true`, ако играта е приключила, без играчът да познае думата. Иначе връща `false`.
pub fn is_lost(&self) -> bool {
self.attempts_remaining == 0
}
}
impl Display for Game {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.is_won() {
f.write_fmt(format_args!("You won! ^_^\nThe word was: {}", self.solution_word))
} else if self.is_lost() {
f.write_fmt(format_args!("You lost! :/\nThe word was: {}", self.solution_word))
} else {
try!(f.write_fmt(format_args!("Attempts remaining: {}\nGuess:", self.attempts_remaining)));
for letter in self.solution_word.chars() {
if self.guessed_letters.contains(&letter) {
try!(f.write_fmt(format_args!(" {}", letter)));
} else {
try!(f.write_str(" _"));

Аз направих специален случай за първия символ, но, както добре си се досетил, спокойно можеш да си добавиш интервал преди всеки символ/подчертавка, без това преди тази част имаш "Guess:". Добра работа :).

}
}
f.write_str("\n")
}
}
}