Решение на Hangman от Димитър Узунов

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

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

Резултати

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

Код

use std::str::FromStr;
use std::str::SplitWhitespace;
use std::collections::HashSet;
use std::fmt::{self, 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 reason) => write!(f, "ParseError({})", reason),
&GameError::BadGuess(ref reason) => write!(f, "BadGuess({})", reason),
&GameError::InvalidSolution(ref reason) => write!(f, "InvalidSolution({})", reason),
&GameError::GameOver => write!(f, "GameOver")
}
}
}
#[derive(Debug)]
pub enum Command {
TryLetter(char),
TryWord(String),
Info,
Help,
Quit,
}
impl Command {
fn try(s: &str, mut words_iter: SplitWhitespace) -> Result<Self, <Self as FromStr>::Err> {
match words_iter.next() {
Some(p) if "letter".starts_with(p) => Self::try_letter(s, words_iter),
Some(p) if "word".starts_with(p) => Self::try_word(s, words_iter),
_ => Err(GameError::ParseError(String::from(s))),
}
}
fn try_letter(s: &str, mut words_iter: SplitWhitespace) -> Result<Self, <Self as FromStr>::Err> {
match words_iter.next() {
Some(p) if p.chars().count() == 1 => Ok(Command::TryLetter(p.chars().next().unwrap())),
_ => Err(GameError::ParseError(String::from(s))),
}
}
fn try_word(s: &str, mut words_iter: SplitWhitespace) -> Result<Self, <Self as FromStr>::Err> {
match words_iter.next() {
Some(word) => Ok(Command::TryWord(String::from(word))),
None => Err(GameError::ParseError(String::from(s))),
}
}
}

Готин начин за организация на кода, харесва ми :). Нещо малко, което можеш да направиш, за да съкратиш return типа, е да направиш type alias:

type CommandResult = Result<Command, <Command as FromStr>::Err>;

Това трябва да стане извън impl блока, иначе компилатора се оплаква за асоциирани типове. Слага малко индирекция, но може да опрости нещата. Ако командите бяха в свой модул, можеше директно да си дефинираш тип Result, както се прави в io модула.

impl FromStr for Command {
type Err = GameError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase();
let mut words_iter = s.split_whitespace();
match words_iter.next() {
Some(p) if "info".starts_with(p) => Ok(Command::Info),
Some(p) if "help".starts_with(p) => Ok(Command::Help),
Some(p) if "quit".starts_with(p) => Ok(Command::Quit),
Some(p) if "try".starts_with(p) => Self::try(&s, words_iter),
_ => Err(GameError::ParseError(String::from(s.clone()))),
}
}
}
pub struct Game<'a> {
pub attempted_letters: HashSet<char>,
pub attempted_words: HashSet<String>,
pub attempts_remaining: u32,
solution: &'a str,
}
impl<'a> Game<'a> {
pub fn new(solution: &'a str, attempts: u32) -> Result<Self, GameError> {
if solution.is_empty() || !solution.chars().any(&char::is_alphabetic) {

Грешен метод -- трябва да върнеш грешка ако !solution.chars().all(&char::is_alphabetic). Амперсанда пред char::is_alphabetic също не ти трябва -- по този начин вземаш reference към глобално достъпния pointer към функция. Самото char::is_alphabetic би трябвало да има размер всичко на всичко 1 pointer, така че не печелиш нищо.

let reason = String::from("Empty or contains non-alphabetic chars.");
return Err(GameError::InvalidSolution(reason));
}
Ok(Game {
solution,
attempts_remaining: attempts,
attempted_letters: HashSet::new(),
attempted_words: HashSet::new(),
})
}
pub fn guess_letter(&mut self, guess: char) -> Result<bool, GameError> {
if self.is_over() {
return Err(GameError::GameOver);
}
if self.attempted_letters.contains(&guess) {
let reason = format!("Letter '{}' already attempted.", guess);
return Err(GameError::BadGuess(reason));
}
let guessed = self.solution.chars().any(|c| c == guess);
self.attempted_letters.insert(guess);
self.attempts_remaining -= if guessed { 0 } else { 1 };
Ok(guessed)
}
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) {
let reason = format!("Word '{}' already attempted.", guess);
return Err(GameError::BadGuess(reason));
}
let guessed = self.solution == guess;
self.attempted_words.insert(String::from(guess));
self.attempts_remaining -= if guessed { 0 } else { 1 };
Ok(guessed)
}
pub fn is_over(&self) -> bool {
self.is_lost() || self.is_won()
}
pub fn is_lost(&self) -> bool {
self.attempts_remaining == 0
}
pub fn is_won(&self) -> bool {
self.is_word_guessed() || self.are_letters_guessed()
}
fn is_word_guessed(&self) -> bool {
self.attempted_words.contains(self.solution)
}
fn are_letters_guessed(&self) -> bool {
self.solution.chars().all(|c| self.is_letter_guessed(c))
}
fn is_letter_guessed(&self, letter: char) -> bool {
self.attempted_letters.contains(&letter)
}
}
impl<'a> Display for Game<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.is_won() {
return write!(f, "You won! ^_^\nThe word was: {}", self.solution)
}
if self.is_lost() {
return write!(f, "You lost! :/\nThe word was: {}", self.solution)
}
let guess = self.solution.chars()
.map(|c| if self.is_letter_guessed(c) { c } else { '_' })
.map(|c| c.to_string())
.collect::<Vec<_>>()
.join(" ");
write!(f, "Attempts remaining: {}\nGuess: {}", self.attempts_remaining, guess)
}
}

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

Compiling solution v0.1.0 (file:///tmp/d20171210-6053-1m006qh/solution)
    Finished dev [unoptimized + debuginfo] target(s) in 5.68 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 ... ok
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 ... FAILED
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_game_error stdout ----
	thread 'solution_test::test_game_error' panicked at 'Expected Some(GameError::InvalidSolution(_)) to match None', tests/solution_test.rs:38:4
note: Run with `RUST_BACKTRACE=1` for a backtrace.


failures:
    solution_test::test_game_error

test result: FAILED. 14 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out

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

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

Димитър качи първо решение на 03.12.2017 20:13 (преди почти 8 години)

Димитър качи решение на 08.12.2017 13:56 (преди почти 8 години)

use std::str::FromStr;
use std::str::SplitWhitespace;
use std::collections::HashSet;
use std::fmt::{self, 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 reason) => write!(f, "ParseError({})", reason),
&GameError::BadGuess(ref reason) => write!(f, "BadGuess({})", reason),
&GameError::InvalidSolution(ref reason) => write!(f, "InvalidSolution({})", reason),
&GameError::GameOver => write!(f, "GameOver")
}
}
}
#[derive(Debug)]
pub enum Command {
TryLetter(char),
TryWord(String),
Info,
Help,
Quit,
}
impl Command {
fn try(s: &str, mut words_iter: SplitWhitespace) -> Result<Self, <Self as FromStr>::Err> {
match words_iter.next() {
Some(p) if "letter".starts_with(p) => Self::try_letter(s, words_iter),
Some(p) if "word".starts_with(p) => Self::try_word(s, words_iter),
- Some(_) | None => Err(GameError::ParseError(String::from(s))),
+ _ => Err(GameError::ParseError(String::from(s))),
}
}
fn try_letter(s: &str, mut words_iter: SplitWhitespace) -> Result<Self, <Self as FromStr>::Err> {
match words_iter.next() {
Some(p) if p.chars().count() == 1 => Ok(Command::TryLetter(p.chars().next().unwrap())),
- Some(_) | None => Err(GameError::ParseError(String::from(s))),
+ _ => Err(GameError::ParseError(String::from(s))),
}
}
fn try_word(s: &str, mut words_iter: SplitWhitespace) -> Result<Self, <Self as FromStr>::Err> {
match words_iter.next() {
Some(word) => Ok(Command::TryWord(String::from(word))),
None => Err(GameError::ParseError(String::from(s))),
}
}
}

Готин начин за организация на кода, харесва ми :). Нещо малко, което можеш да направиш, за да съкратиш return типа, е да направиш type alias:

type CommandResult = Result<Command, <Command as FromStr>::Err>;

Това трябва да стане извън impl блока, иначе компилатора се оплаква за асоциирани типове. Слага малко индирекция, но може да опрости нещата. Ако командите бяха в свой модул, можеше директно да си дефинираш тип Result, както се прави в io модула.

impl FromStr for Command {
type Err = GameError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase();
let mut words_iter = s.split_whitespace();
match words_iter.next() {
Some(p) if "info".starts_with(p) => Ok(Command::Info),
Some(p) if "help".starts_with(p) => Ok(Command::Help),
Some(p) if "quit".starts_with(p) => Ok(Command::Quit),
Some(p) if "try".starts_with(p) => Self::try(&s, words_iter),
- Some(_) | None => Err(GameError::ParseError(String::from(s.clone()))),
+ _ => Err(GameError::ParseError(String::from(s.clone()))),
}
}
}
pub struct Game<'a> {
pub attempted_letters: HashSet<char>,
pub attempted_words: HashSet<String>,
pub attempts_remaining: u32,
solution: &'a str,
}
impl<'a> Game<'a> {
pub fn new(solution: &'a str, attempts: u32) -> Result<Self, GameError> {
if solution.is_empty() || !solution.chars().any(&char::is_alphabetic) {

Грешен метод -- трябва да върнеш грешка ако !solution.chars().all(&char::is_alphabetic). Амперсанда пред char::is_alphabetic също не ти трябва -- по този начин вземаш reference към глобално достъпния pointer към функция. Самото char::is_alphabetic би трябвало да има размер всичко на всичко 1 pointer, така че не печелиш нищо.

let reason = String::from("Empty or contains non-alphabetic chars.");
return Err(GameError::InvalidSolution(reason));
}
Ok(Game {
solution,
attempts_remaining: attempts,
attempted_letters: HashSet::new(),
attempted_words: HashSet::new(),
})
}
pub fn guess_letter(&mut self, guess: char) -> Result<bool, GameError> {
+ if self.is_over() {
+ return Err(GameError::GameOver);
+ }
+
if self.attempted_letters.contains(&guess) {
let reason = format!("Letter '{}' already attempted.", guess);
return Err(GameError::BadGuess(reason));
}
let guessed = self.solution.chars().any(|c| c == guess);
self.attempted_letters.insert(guess);
self.attempts_remaining -= if guessed { 0 } else { 1 };
Ok(guessed)
}
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) {
let reason = format!("Word '{}' already attempted.", guess);
return Err(GameError::BadGuess(reason));
}
let guessed = self.solution == guess;
self.attempted_words.insert(String::from(guess));
self.attempts_remaining -= if guessed { 0 } else { 1 };
Ok(guessed)
}
pub fn is_over(&self) -> bool {
- self.is_won() || self.is_lost()
+ self.is_lost() || self.is_won()
}
- pub fn is_won(&self) -> bool {
- self.is_word_guessed() || self.are_letters_guessed()
- }
-
pub fn is_lost(&self) -> bool {
self.attempts_remaining == 0
+ }
+
+ pub fn is_won(&self) -> bool {
+ self.is_word_guessed() || self.are_letters_guessed()
}
fn is_word_guessed(&self) -> bool {
self.attempted_words.contains(self.solution)
}
fn are_letters_guessed(&self) -> bool {
self.solution.chars().all(|c| self.is_letter_guessed(c))
}
fn is_letter_guessed(&self, letter: char) -> bool {
self.attempted_letters.contains(&letter)
}
}
impl<'a> Display for Game<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.is_won() {
return write!(f, "You won! ^_^\nThe word was: {}", self.solution)
}
if self.is_lost() {
return write!(f, "You lost! :/\nThe word was: {}", self.solution)
}
let guess = self.solution.chars()
.map(|c| if self.is_letter_guessed(c) { c } else { '_' })
.map(|c| c.to_string())
.collect::<Vec<_>>()
.join(" ");
write!(f, "Attempts remaining: {}\nGuess: {}", self.attempts_remaining, guess)
}
}