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

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

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

Резултати

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

Код

use std::str::FromStr;
use std::fmt::{self, Display, Write};
use std::collections::HashSet;
use std::iter::FromIterator;
#[derive(Debug, PartialEq)]
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 message) => write!(f, "Could not parse command '{}'", message),
GameError::BadGuess(ref message) => write!(f, "Bad guess: {}", message),
GameError::InvalidSolution(ref solution) => write!(f, "'{}' is not a valid solution", solution),
GameError::GameOver => write!(f, "The game is over!")
}
}
}
#[derive(Debug, PartialEq)]
pub enum Command {
TryLetter(char),
TryWord(String),
Info,
Help,
Quit,
}
impl FromStr for Command {
type Err = GameError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
parse_command(s).ok_or_else( || GameError::ParseError(s.into()))
}
}
fn parse_command(s: &str) -> Option<Command> {
let string = s.to_lowercase();
let mut words = string.trim().split_whitespace().into_iter();
let first_word = words.next()?;
if match_word(first_word, "help") {
Some(Command::Help)
} else if match_word(first_word, "info") {
Some(Command::Info)
} else if match_word(first_word, "quit") {
Some(Command::Quit)
} else if match_word(first_word, "try") {
let second_word = words.next()?;
if match_word(second_word, "letter") {
let letter = words.next()?;
if letter.chars().count() == 1 {
Some(Command::TryLetter(letter.chars().next().unwrap()))
} else {
None
}
} else {
Some(Command::TryWord(words.next()?.into()))
}
} else {
None
}
}

Добре измислено с отделния метод, който връща Option, позволява ти много удобно да използваш ?. Аз опростих нещата с макрос за грешката в моето решение, но това улеснява дори повече нещата.

Изличането на match_word е също смислено. Мисля, че можеш да опростиш имплементацията му с метода contains на str: https://doc.rust-lang.org/std/primitive.str.html#method.contains

Всъщност да, сега осъзнавам, че contains може да е малко misleading :). Жалко, че не съм измислил тест, който да го хване (видях употреба на contains в друго решение).

fn match_word(word: &str, command: &str) -> bool {
if word.is_empty() {
return false;
}
word == command.chars().take(word.len()).collect::<String>()
}
#[derive(Debug)]
pub struct Game {
solution: String,
solution_letters: HashSet<char>,
pub attempted_letters: HashSet<char>,
pub attempted_words: HashSet<String>,
pub attempts_remaining: u32,
}
impl Game {
pub fn new(solution: &str, attempts: u32) -> Result<Self, GameError> {
let contains_non_alphabetic_symbols = solution.chars().any( |c| !c.is_alphabetic() );
if solution.is_empty() || contains_non_alphabetic_symbols {
return Err(GameError::InvalidSolution(solution.into()));
}
Ok(Self {
solution: solution.into(),
solution_letters: HashSet::from_iter(solution.chars()),
attempted_letters: HashSet::new(),
attempted_words: HashSet::new(),
attempts_remaining: attempts,
})
}
pub fn guess_letter(&mut self, guess: char) -> Result<bool, GameError> {
if self.attempted_letters.contains(&guess) {
Err(GameError::BadGuess(guess.to_string()))
} else if self.solution_letters.contains(&guess) {
self.attempted_letters.insert(guess);
Ok(true)
} else if self.attempts_remaining == 0 {
Err(GameError::GameOver)

Тук малко са се объркали нещата заради приоритети. След въпроса ти във форумите, обнових описанието, и специфично ги наредих -- първо, ако играта е свършила, и викаме този метод, връщаме грешка, че е свършила. Второ, ако вече си пробвал тази буква, се връща друга грешка. Признавам, че е малко пипкаво да се реши кое по-напред, но мисля, че така е и малко по-логически консистентно.

Честно казано, на практика бих обмислил даже guess_letter да panic-ва, ако се викне на свършила игра -- на теория, интерфейса не би трябвало да го позволява. Но вероятно може да се поспори по въпроса.

} else {
self.attempts_remaining -= 1;
self.attempted_letters.insert(guess);
Ok(false)
}
}
pub fn guess_word(&mut self, guess: &str) -> Result<bool, GameError> {
if self.attempted_words.contains(guess) {
Err(GameError::BadGuess(guess.into()))
} else if self.solution == guess {
self.attempted_words.insert(guess.into());
Ok(true)
} else if self.attempts_remaining == 0 {
Err(GameError::GameOver)
} else {
self.attempts_remaining -= 1;
self.attempted_words.insert(guess.into());
Ok(false)
}
}
fn is_game_won(&self) -> bool {
let guessed_word = self.attempted_words.contains(&self.solution);
let guessed_letters = self.solution_letters.is_subset(&self.attempted_letters);
guessed_word || guessed_letters
}
pub fn is_over(&self) -> bool {
let has_no_more_attempts = self.attempts_remaining == 0;
self.is_game_won() || has_no_more_attempts
}
}
impl Display for Game {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.is_over() {
if self.is_game_won() {
writeln!(f, "You won! ^_^")?;
write!(f, "The word was: {}", self.solution)?;
} else {
writeln!(f, "You lost! :/")?;
write!(f, "The word was: {}", self.solution)?;
}
} else {
writeln!(f, "Attempts remaining: {}", self.attempts_remaining)?;
write!(f, "Guess:")?;
for letter in self.solution.chars() {
if self.attempted_letters.contains(&letter) {
write!(f, " {}", letter)?;
} else {
write!(f, " _")?;
}
}
}
Ok(())
}
}

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

Compiling solution v0.1.0 (file:///tmp/d20171210-6053-gl5pox/solution)
warning: unused import: `Write`
 --> src/lib.rs:2:31
  |
2 | use std::fmt::{self, Display, Write};
  |                               ^^^^^
  |
  = note: #[warn(unused_imports)] on by default

warning: unused import: `Write`
 --> src/lib.rs:2:31
  |
2 | use std::fmt::{self, Display, Write};
  |                               ^^^^^
  |
  = note: #[warn(unused_imports)] on by default

    Finished dev [unoptimized + debuginfo] target(s) in 5.74 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 ... ok
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 ... FAILED
test solution_test::test_command_parsing_spacing ... ok
test solution_test::test_command_parsing_special ... ok
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 ... FAILED

failures:

---- solution_test::test_command_parsing_partial_words stdout ----
	thread 'solution_test::test_command_parsing_partial_words' panicked at 'Expected Err(GameError::ParseError(_)) to match Ok(TryWord("t"))', tests/solution_test.rs:222:4
note: Run with `RUST_BACKTRACE=1` for a backtrace.

---- solution_test::test_game_over_guesses stdout ----
	thread 'solution_test::test_game_over_guesses' panicked at 'Expected Err(GameError::GameOver) to match Err(BadGuess("f"))', tests/solution_test.rs:157:4


failures:
    solution_test::test_command_parsing_partial_words
    solution_test::test_game_over_guesses

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

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

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

Георги качи първо решение на 03.12.2017 21:29 (преди почти 8 години)