Решение на Hangman от Николай Коцев

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

Към профила на Николай Коцев

Резултати

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

Код

use std::fmt::{self, Display, Write};
use std::str::FromStr;
use std::collections::HashSet;
// use std::error::Error;
#[derive(Debug)]
pub enum GameError {
ParseError(String),
BadGuess(String),
InvalidSolution(String),
GameOver,
}
impl Display for GameError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self {
GameError::ParseError(ref s) => write!(f, "Unable to parse command: {}", s),
GameError::BadGuess(ref s) => write!(f, "{} isn't the correct word", s),
GameError::InvalidSolution(ref s) => write!(f, "{} is not valid", s),
GameError::GameOver => write!(f, "Game Over"),
}
}
}
#[derive(Debug)]
pub enum Command {
TryLetter(char),
TryWord(String),
Info,
Help,
Quit,
}
impl Command {
pub fn parse_command(string: String) -> Option<Self> {
let string_clone = string.clone();
let mut word_iter = string_clone.split_whitespace().peekable();
let first_word = match word_iter.next(){
Some(word) => word,
None => return None,
};
match word_iter.peek() {
None => return Command::handle_simple(first_word),
_ => {
if first_word.starts_with('t') {
Command::parse_try(word_iter)
}
else {
None
}
}
}
}
fn handle_simple(first_word: &str) -> Option<Self> {
match first_word.chars().nth(0) {
Some('q') => Some(Command::Quit),
Some('i') => Some(Command::Info),
Some('h') => Some(Command::Help),
_ => None
}
}
fn parse_try(mut word_iter: std::iter::Peekable<std::str::SplitWhitespace>
) -> Option<Self> {
let second_word = word_iter.next().unwrap(); // Peeked
match second_word.chars().nth(0) {
Some('l') => Command::handle_letter(word_iter),
Some('w') => Command::handle_word(word_iter),
_ => None
}
}
fn handle_letter(mut word_iter: std::iter::Peekable<std::str::SplitWhitespace>
) -> Option<Self> {
match word_iter.next() {
Some(string) => {
if string.len() == 1 {
Some(Command::TryLetter(string.chars().nth(0).unwrap()))
}
else {
None
}
},
None => None
}
}
fn handle_word(mut word_iter: std::iter::Peekable<std::str::SplitWhitespace>
) -> Option<Self> {
match word_iter.next() {
Some(string) => Some(Command::TryWord(String::from(string))),
None => None
}
}
// For dem testz
pub fn unwrap(self) -> String {
match self {
Command::TryWord(string) => string,
_ => panic!("There was nothing to unwrap in this command"),
}
}
}
impl FromStr for Command {
type Err = GameError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let lowercased = String::from(s).trim().to_lowercase();
match Command::parse_command(lowercased) {
Some(command) => Ok(command),
None => Err(GameError::ParseError(String::from(s)))
}
}
}
pub struct Game {
pub attempted_letters: HashSet<char>,
pub attempted_words: HashSet<String>,
pub attempts_remaining: u32,
solution: String,
state: GameState,
guess_map: Vec<bool>
}
#[derive(PartialEq)]
enum GameState {
Running,
Won,
Lost
}
impl Game {
pub fn new(solution: &str, attempts: u32) -> Result<Self, GameError> {
let solution = String::from(solution);
if solution.len() != 0 && solution.chars().all(char::is_alphabetic) {
Ok( Game {
attempted_letters: HashSet::new(),
attempted_words: HashSet::new(),
attempts_remaining: attempts,
guess_map: vec![false; solution.len()],
solution: solution,
state: GameState::Running,
})
}
else {
Err(GameError::InvalidSolution(solution))
}
}
pub fn guess_letter(&mut self, guess: char) -> Result<bool , GameError>{
if self.state != GameState::Running { return Err(GameError::GameOver) }
if self.attempted_letters.contains(&guess) {
return Err(GameError::BadGuess(String::from("You already tried this one")))
}
self.attempted_letters.insert(guess);
let map = self.solution.chars().map(|c| c == guess).collect::<Vec<bool>>();
let correct_guess = map != self.guess_map;
// let zipped = self.guess_map.into_iter().zip(map).collect::<Vec<(bool, bool)>>();
// self.guess_map = zipped.iter().map(|&(l, r)| l || r).collect::<Vec<bool>>();
self.set_state();
if correct_guess { Ok(true) } else { Ok(false) }
}
pub fn guess_word(&mut self, guess: &str) -> Result<bool, GameError> {
Err(GameError::GameOver)
}
pub fn is_over(&self) -> bool {
self.state != GameState::Running
}
fn set_state(&mut self) -> () {
if self.guess_map.iter().all(|boolean| *boolean) {
self.state = GameState::Won;
return ();
}
if self.attempts_remaining == 0 {
self.state = GameState::Lost;
}
}
}
impl Display for Game {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "")
}
}
#[cfg(test)]
mod tests {
use Command;
use Game;
use GameError;
#[test]
fn from_str() {
assert!("q".parse::<Command>().is_ok());
assert!("qu".parse::<Command>().is_ok());
assert!("qui".parse::<Command>().is_ok());
assert!("quit".parse::<Command>().is_ok());
assert!(" \t \n QuiT \t ".parse::<Command>().is_ok());
assert!("info".parse::<Command>().is_ok());
assert!("help".parse::<Command>().is_ok());
assert!("quit and something".parse::<Command>().is_err());
assert!("try karagioz".parse::<Command>().is_err());
assert!("try letter".parse::<Command>().is_err());
assert!("try letter karagioz".parse::<Command>().is_err());
assert!("try letter k".parse::<Command>().is_ok());
match "try letter l".parse::<Command>() {
Ok(Command::TryLetter('l')) => {}, // Everything is fine
_ => panic!("Baaad mojo")
}
assert!("try word stuff".parse::<Command>().is_ok());
assert!("try word".parse::<Command>().is_err());
match "try word karagioz".parse::<Command>() {
Ok(command) => {
assert_eq!(command.unwrap(), String::from("karagioz"))
},
_ => panic!("Not gut")
}
}
#[test]
fn new_game() {
assert!(Game::new("baba", 42).is_ok());
assert!(Game::new("", 1024).is_err());
assert!(Game::new(" ", 2048).is_err());
assert!(Game::new("snake_case", 120).is_err());
assert!(Game::new("numb3r54r3B4d", 11).is_err());
}
macro_rules! assert_substring {
($expected:expr, $actual:expr) => {
assert!($actual.contains($expected), "Expected {:?} to contain {:?}", $actual, $expected);
}
}
macro_rules! assert_match {
($pattern:pat, $actual:expr) => {
if let $pattern = $actual {
assert!(true);
} else {
assert!(false, "Expected {} to match {:?}", stringify!($pattern), $actual);
}
}
}
#[test]
fn test_game_basic() {
let mut g = Game::new("foobar", 2).unwrap();
assert!(!g.is_over());
assert_eq!(2, g.attempts_remaining);
assert_eq!(0, g.attempted_letters.len());
assert_eq!(0, g.attempted_words.len());
assert_substring!("_ _ _ _ _ _", format!("{}", g));
assert!(g.guess_letter('o').unwrap());
assert_eq!(1, g.attempted_letters.len());
assert_eq!(0, g.attempted_words.len());
assert_substring!("_ o o _ _ _", format!("{}", g));
assert!(g.guess_word("foobar").unwrap());
assert_eq!(1, g.attempted_letters.len());
assert_eq!(1, g.attempted_words.len());
assert_substring!("foobar", format!("{}", g));
assert_substring!("won", format!("{}", g));
assert!(g.is_over());
}
#[test]
fn test_commands_basic() {
assert_match!(Ok(Command::Help), "help".parse::<Command>());
assert_match!(Ok(Command::Info), "info".parse::<Command>());
assert_match!(Ok(Command::Quit), "quit".parse::<Command>());
assert_match!(Ok(Command::TryLetter('x')), "try letter x".parse::<Command>());
assert_match!(Ok(Command::TryWord(_)), "try word xyzzy".parse::<Command>());
}
#[test]
fn test_errors_basic() {
assert_match!(GameError::ParseError(_), GameError::ParseError(String::from("error!")));
assert_match!(GameError::BadGuess(_), GameError::BadGuess(String::from("error!")));
assert_match!(GameError::InvalidSolution(_), GameError::InvalidSolution(String::from("error!")));
assert_match!(GameError::GameOver, GameError::GameOver);
}
}

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

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

warning: unused variable: `guess`
   --> src/lib.rs:172:34
    |
172 |     pub fn guess_word(&mut self, guess: &str) -> Result<bool, GameError> {
    |                                  ^^^^^
    |
    = note: #[warn(unused_variables)] on by default
    = note: to avoid this warning, consider using `_guess` instead

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

warning: unused variable: `guess`
   --> src/lib.rs:172:34
    |
172 |     pub fn guess_word(&mut self, guess: &str) -> Result<bool, GameError> {
    |                                  ^^^^^
    |
    = note: #[warn(unused_variables)] on by default
    = note: to avoid this warning, consider using `_guess` instead

    Finished dev [unoptimized + debuginfo] target(s) in 5.53 secs
     Running target/debug/deps/solution-3f98bfa5c86a5dd9

running 5 tests
test tests::from_str ... ok
test tests::new_game ... ok
test tests::test_commands_basic ... ok
test tests::test_errors_basic ... ok
test tests::test_game_basic ... FAILED

failures:

---- tests::test_game_basic stdout ----
	thread 'tests::test_game_basic' panicked at 'Expected "" to contain "_ _ _ _ _ _"', src/lib.rs:273:8
note: Run with `RUST_BACKTRACE=1` for a backtrace.


failures:
    tests::test_game_basic

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

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

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

Николай качи първо решение на 08.12.2017 16:54 (преди почти 8 години)

Имаш 0 точки, защото тестовете, които си пуснал с решението, fail-ват. Fun fact: Ако един тестов файл се провали, Rust не продължава да вика останалите. Тоест, "истинските" тестове не се викат, понеже тези във самия файл не работят.

Принципно няма проблеми да включвате такива тестове, но ако ще го правите, има нужда поне да минават. Ако беше изтрил тестовете, които използват Display, щеше да изкараш 5 точки от останалите. Което може би е някаква утеха, as in, така или иначе нямаше да получиш твърде много точки.

Както често казвам, и изглежда ще продължавам често да го казвам -- пускането на домашни по-рано е доста за предпочитане. Защото можех да ти кажа това, и да имаш време поне да махнеш тестовете.

Ще имаш възможност да наваксаш, но следващия път почни доста по-отрано.