Решение на Hangman от Мартин Георгиев

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

Към профила на Мартин Георгиев

Резултати

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

Код

use std::fmt;
use std::fmt::Display;
#[derive(Debug)]
pub enum GameError {
ParseError(String),
BadGuess(String),
InvalidSolution(String),
GameOver,
}
impl Display for GameError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&GameError::ParseError(ref string) => write!(f, "Parse error on command: {}!", string),
&GameError::BadGuess(ref string) => write!(f, "Bad guess for word: {}", string),
&GameError::InvalidSolution(ref string) => write!(f, "Ivalid solution {} given!", string),
&GameError::GameOver => write!(f, "Game Over!")
}
}
}
use std::str::FromStr;
#[derive(Debug)]
pub enum Command {
TryLetter(char),
TryWord(String),
Info,
Help,
Quit,
}
fn cmp_strs_beginings(s1: &str, s2: &str) -> bool {
for (ch1, ch2) in s1.chars().zip(s2.chars()) {
if ch1 != ch2 {
return false;
}
}
return true;
}
impl FromStr for Command {
type Err = GameError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let normalized_s: String = s.to_lowercase();
let normalized_s: Vec<&str> = normalized_s.trim().split(' ').collect();
let normalized_s: Vec<&str> = normalized_s.iter().filter(|&x| x != &"").map(|&x| x).collect();
if normalized_s.iter().zip(vec!("help")).all(|(s1, s2)| cmp_strs_beginings(s1, s2)) {
return Ok(Command::Help);
}
if normalized_s.iter().zip(vec!("info")).all(|(s1, s2)| cmp_strs_beginings(s1, s2)) {
return Ok(Command::Info);
}
if normalized_s.iter().zip(vec!("quit")).all(|(s1, s2)| cmp_strs_beginings(s1, s2)) {
return Ok(Command::Quit);
}
if normalized_s.len() >= 3 &&
normalized_s.iter().zip(vec!("try", "word")).all(|(s1, s2)| cmp_strs_beginings(s1, s2)) {
return Ok(Command::TryWord(String::from(normalized_s[2])));
}
if normalized_s.len() >= 3 &&
normalized_s[2].len() == 1 &&

Метода len брои байтове, не char-ове: https://doc.rust-lang.org/std/primitive.str.html#method.len

Малко gotcha, но ако беше изтествал с кирилица, щеше да го хванеш :).

On an unrelated note, добре щеше да е да използваш различни имена за променливите. В началото, normalized_s е низ, което изглежда plausible спрямо името ("normalized string"). После, обаче, го превръщаш във вектор, което малко затруднява четенето на метода. Променлива с име words или parts щеше да е доста по-четима.

normalized_s.iter().zip(vec!("try", "letter")).all(|(s1, s2)| cmp_strs_beginings(s1, s2)) {
return Ok(Command::TryLetter(normalized_s[2].chars().nth(0).unwrap()));
}
Err(GameError::ParseError(String::from(s)))
}
}
use std::collections::HashSet;
#[derive(Debug)]
pub struct Game {
pub attempted_letters: HashSet<char>,
pub attempted_words: HashSet<String>,
pub attempts_remaining: u32,
pub solution: String,
player_won: bool
}
impl Game {
pub fn new(solution: &str, attempts: u32) -> Result<Self, GameError> {
if solution == "" || !solution.chars().all(|x| x.is_alphabetic())
{
return Err(GameError::InvalidSolution(String::from(solution)));
}
Ok(Game {
attempted_letters: HashSet::new(),
attempted_words: HashSet::new(),
solution: String::from(solution),
attempts_remaining: attempts,
player_won: false
})
}
}
impl Game {
pub fn guess_letter(&mut self, guess: char) -> Result<bool, GameError> {
let guess = guess.to_lowercase().next().unwrap();
if self.is_over() {
return Err(GameError::GameOver);
}
if self.attempted_letters.contains(&guess) {
return Err(GameError::BadGuess(guess.to_string()));
}
self.attempted_letters.insert(guess);
if self.solution.contains(guess) {
if self.solution.chars().all(|x| self.attempted_letters.contains(&x)) {
self.player_won = true;
}
return Ok(true);
}
self.attempts_remaining -= 1;
Ok(false)
}
pub fn guess_word(&mut self, guess: &str) -> Result<bool, GameError> {
if self.is_over() {
return Err(GameError::GameOver);
}
if self.attempted_words.contains(&guess.to_lowercase()) {
return Err(GameError::BadGuess(String::from(guess)));
}
self.attempted_words.insert(String::from(guess.to_lowercase()));
if self.solution == guess.to_lowercase()
{
self.player_won = true;
return Ok(true);
}
self.attempts_remaining -= 1;
Ok(false)
}
pub fn is_over(&self) -> bool {
self.player_won || self.attempts_remaining == 0
}
}
impl Display for Game {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.player_won {
return write!(f, "You won!\nThe word was {}!", self.solution);
}
if self.attempts_remaining == 0 {
return write!(f, "You lost!\nThe word was {}!", self.solution);
}
let mut curr_word: String = String::new();
for ch in self.solution.chars() {
if self.attempted_letters.contains(&ch) {
curr_word.push(' ');
curr_word.push(ch)
} else {
curr_word.push(' ');
curr_word.push('_');
}
}
write!(f, "Attempts remaining: {}\n Guess:{}", self.attempts_remaining, curr_word)
}
}

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

Compiling solution v0.1.0 (file:///tmp/d20171210-6053-c693p9/solution)
    Finished dev [unoptimized + debuginfo] target(s) in 5.98 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 я"))', 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 я"))', 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 версии и 2 коментара)

Мартин качи решение на 08.12.2017 16:16 (преди почти 8 години)

use std::fmt;
use std::fmt::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 string) => write!(f, "Parse error on command: {}!", string),
&GameError::BadGuess(ref string) => write!(f, "Bad guess for word: {}", string),
&GameError::InvalidSolution(ref string) => write!(f, "Ivalid solution {} given!", string),
&GameError::GameOver => write!(f, "Game Over!")
}
}
}
-
use std::str::FromStr;
-
#[derive(Debug)]
pub enum Command {
TryLetter(char),
TryWord(String),
Info,
Help,
Quit,
}
-
fn cmp_strs_beginings(s1: &str, s2: &str) -> bool {
for (ch1, ch2) in s1.chars().zip(s2.chars()) {
if ch1 != ch2 {
return false;
}
}
return true;
}
-
impl FromStr for Command {
type Err = GameError;
-
- /// Този метод ще приеме string slice, и ще върне нова команда, която му съответства. Правилата
- /// за това кои низове се превръщат в какви команди са по-долу.
- ///
- /// В случай на грешка, винаги ще се върне `GameError::ParseError`. С какъвто низ искате -- било то
- /// само входния низ, или пълно съобщение за грешка.
- ///
fn from_str(s: &str) -> Result<Self, Self::Err> {
-
let normalized_s: String = s.to_lowercase();
let normalized_s: Vec<&str> = normalized_s.trim().split(' ').collect();
let normalized_s: Vec<&str> = normalized_s.iter().filter(|&x| x != &"").map(|&x| x).collect();
-
if normalized_s.iter().zip(vec!("help")).all(|(s1, s2)| cmp_strs_beginings(s1, s2)) {
return Ok(Command::Help);
}
if normalized_s.iter().zip(vec!("info")).all(|(s1, s2)| cmp_strs_beginings(s1, s2)) {
return Ok(Command::Info);
}
if normalized_s.iter().zip(vec!("quit")).all(|(s1, s2)| cmp_strs_beginings(s1, s2)) {
return Ok(Command::Quit);
}
if normalized_s.len() >= 3 &&
normalized_s.iter().zip(vec!("try", "word")).all(|(s1, s2)| cmp_strs_beginings(s1, s2)) {
return Ok(Command::TryWord(String::from(normalized_s[2])));
}
if normalized_s.len() >= 3 &&
normalized_s[2].len() == 1 &&

Метода len брои байтове, не char-ове: https://doc.rust-lang.org/std/primitive.str.html#method.len

Малко gotcha, но ако беше изтествал с кирилица, щеше да го хванеш :).

On an unrelated note, добре щеше да е да използваш различни имена за променливите. В началото, normalized_s е низ, което изглежда plausible спрямо името ("normalized string"). После, обаче, го превръщаш във вектор, което малко затруднява четенето на метода. Променлива с име words или parts щеше да е доста по-четима.

normalized_s.iter().zip(vec!("try", "letter")).all(|(s1, s2)| cmp_strs_beginings(s1, s2)) {
return Ok(Command::TryLetter(normalized_s[2].chars().nth(0).unwrap()));
}
-
Err(GameError::ParseError(String::from(s)))
}
-
}
-
use std::collections::HashSet;
-
#[derive(Debug)]
pub struct Game {
- /// Букви, които вече са били пробвани
pub attempted_letters: HashSet<char>,
-
- /// Думи, които вече са били пробвани
pub attempted_words: HashSet<String>,
-
- /// Брой на оставащите опити.
pub attempts_remaining: u32,
-
pub solution: String,
-
player_won: bool
}
-
impl Game {
- /// Първия аргумент е думата, която ще е правилния отговор на играта. Вижте по-долу за
- /// ограниченията, които трябва да спазва.
- ///
- /// Втория аргумент е броя опити, които има играча да познае думата. Когато ударят 0, играта
- /// минава в състояние на "загуба". Запишете тази стойност в `self.attempts_remaining`.
- ///
pub fn new(solution: &str, attempts: u32) -> Result<Self, GameError> {
if solution == "" || !solution.chars().all(|x| x.is_alphabetic())
{
return Err(GameError::InvalidSolution(String::from(solution)));
}
-
Ok(Game {
attempted_letters: HashSet::new(),
attempted_words: HashSet::new(),
solution: String::from(solution),
attempts_remaining: attempts,
player_won: false
})
}
}
-
impl Game {
- /// Приема символ, проверява дали този символ присъства в решението:
- ///
- /// - Ако да, връща `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 = guess.to_lowercase().next().unwrap();
if self.is_over() {
return Err(GameError::GameOver);
}
if self.attempted_letters.contains(&guess) {
return Err(GameError::BadGuess(guess.to_string()));
}
-
self.attempted_letters.insert(guess);
if self.solution.contains(guess) {
if self.solution.chars().all(|x| self.attempted_letters.contains(&x)) {
self.player_won = true;
}
return Ok(true);
}
-
self.attempts_remaining -= 1;
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> {
if self.is_over() {
return Err(GameError::GameOver);
}
-
- if self.attempted_words.contains(guess) {
+ if self.attempted_words.contains(&guess.to_lowercase()) {
return Err(GameError::BadGuess(String::from(guess)));
}
-
- self.attempted_words.insert(String::from(guess));
- if self.solution == guess
+ self.attempted_words.insert(String::from(guess.to_lowercase()));
+ if self.solution == guess.to_lowercase()
{
self.player_won = true;
return Ok(true);
}
-
self.attempts_remaining -= 1;
Ok(false)
}
-
- /// Връща `true`, ако играта е приключила, по един или друг начин. Иначе връща `false`.
- ///
pub fn is_over(&self) -> bool {
self.player_won || self.attempts_remaining == 0
}
}
-
impl Display for Game {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.player_won {
return write!(f, "You won!\nThe word was {}!", self.solution);
}
if self.attempts_remaining == 0 {
return write!(f, "You lost!\nThe word was {}!", self.solution);
}
- // Attempts remaining: 10
- // Guess: к _ о к о _ _ _
let mut curr_word: String = String::new();
for ch in self.solution.chars() {
if self.attempted_letters.contains(&ch) {
curr_word.push(' ');
curr_word.push(ch)
} else {
curr_word.push(' ');
curr_word.push('_');
}
}
write!(f, "Attempts remaining: {}\n Guess:{}", self.attempts_remaining, curr_word)
}
}