From 14918f6f5cd9a25be0b6a982355dfde819bc0f5f Mon Sep 17 00:00:00 2001 From: Burnus Date: Fri, 12 May 2023 19:49:48 +0200 Subject: [PATCH] Cleanup for 2022 day 2: Turned into a lib and introduced parse errors --- 2022/day02-rock_paper_scissors/src/lib.rs | 138 ++++++++++++++++++ 2022/day02-rock_paper_scissors/src/main.rs | 107 -------------- .../tests/{input => challenge_input} | 0 3 files changed, 138 insertions(+), 107 deletions(-) create mode 100644 2022/day02-rock_paper_scissors/src/lib.rs delete mode 100644 2022/day02-rock_paper_scissors/src/main.rs rename 2022/day02-rock_paper_scissors/tests/{input => challenge_input} (100%) diff --git a/2022/day02-rock_paper_scissors/src/lib.rs b/2022/day02-rock_paper_scissors/src/lib.rs new file mode 100644 index 0000000..ee543bb --- /dev/null +++ b/2022/day02-rock_paper_scissors/src/lib.rs @@ -0,0 +1,138 @@ +use core::fmt::Display; + +#[derive(Debug, PartialEq, Eq)] +pub enum ParseError { + UnexpectedToken(Option), +} + +impl Display for ParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::UnexpectedToken(Some(c)) => write!(f, "Unexpected Hand encountered: {c}"), + Self::UnexpectedToken(None) => write!(f, "Hand doesn't contain enough parameters"), + } + } +} + +#[derive(Clone, Copy)] +enum Strategy { + One, + Two, +} + +#[derive(Copy, Clone)] +enum Hand { + Rock = 1, + Paper = 2, + Scissors = 3, +} + +impl TryFrom for Hand { + type Error = ParseError; + + fn try_from(value: u8) -> Result { + match value { + 1 => Ok(Self::Rock), + 2 => Ok(Self::Paper), + 3 => Ok(Self::Scissors), + n => Err(Self::Error::UnexpectedToken(Some(n as char))), + } + } +} + +impl Hand { + fn that_beats(other: Self) -> Self { + Hand::try_from((other as u8) % 3 + 1).unwrap() + } + fn that_is_beaten_by(other: Self) -> Self { + Hand::try_from((other as u8 + 1) % 3 + 1).unwrap() + } +} + +enum Outcome { Win, Loss, Draw } + +struct Round { + opponent_hand: Hand, + player_hand: Hand, +} + +impl Round { + fn outcome(&self) -> Outcome { + match self.player_hand as i8 - self.opponent_hand as i8 { + 0 => Outcome::Draw, + 1 | -2 => Outcome::Win, + _ => Outcome::Loss, + } + } + fn points(&self) -> u32 { + self.player_hand as u32 + match self.outcome() { + Outcome::Loss => 0, + Outcome::Draw => 3, + Outcome::Win => 6, + } + } +} + +fn try_parse_round(line: &str, strat: Strategy) -> Result { + let mut line = line.chars(); + let opponent_hand = match line.next() { + Some('A') => Ok(Hand::Rock), + Some('B') => Ok(Hand::Paper), + Some('C') => Ok(Hand::Scissors), + c => Err(ParseError::UnexpectedToken(c)), + }?; + let player_hand = match strat { + Strategy::One => { + match line.nth(1) { + Some('X') => Ok(Hand::Rock), + Some('Y') => Ok(Hand::Paper), + Some('Z') => Ok(Hand::Scissors), + c => Err(ParseError::UnexpectedToken(c)), + } + }, + Strategy::Two => { + match line.nth(1) { + Some('X') => Ok(Hand::that_is_beaten_by(opponent_hand)), + Some('Y') => Ok(opponent_hand), + Some('Z') => Ok(Hand::that_beats(opponent_hand)), + c => Err(ParseError::UnexpectedToken(c)), + } + }, + }?; + Ok(Round { opponent_hand, player_hand }) +} + +fn get_tally(moves: &str, strat: Strategy) -> Result { + moves.lines() + .filter(|l| l.len() == 3) + .map(|l| try_parse_round(l, strat).map(|r| r.points())) + .sum::>() +} + +pub fn run(input: &str) -> Result<(u32, u32), ParseError> { + let first = get_tally(input, Strategy::One)?; + let second = get_tally(input, Strategy::Two)?; + Ok((first, second)) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs::read_to_string; + + fn read_file(name: &str) -> String { + read_to_string(name).expect(&format!("Unable to read file: {name}")[..]).trim().to_string() + } + + #[test] + fn test_sample() { + let sample_input = read_file("tests/sample_input"); + assert_eq!(run(&sample_input), Ok((15, 12))); + } + + #[test] + fn test_challenge() { + let challenge_input = read_file("tests/challenge_input"); + assert_eq!(run(&challenge_input), Ok((12458, 12683))); + } +} diff --git a/2022/day02-rock_paper_scissors/src/main.rs b/2022/day02-rock_paper_scissors/src/main.rs deleted file mode 100644 index ca259e4..0000000 --- a/2022/day02-rock_paper_scissors/src/main.rs +++ /dev/null @@ -1,107 +0,0 @@ -use std::fs; - -#[derive(Copy, Clone)] -enum Hand { - Rock = 1, - Paper = 2, - Scissors = 3, -} - -impl Hand { - fn that_beats(other: Self) -> Self { - match other { - Self::Rock => Self::Paper, - Self::Paper => Self::Scissors, - Self::Scissors => Self::Rock, - } - } - fn that_is_beaten_by(other: Self) -> Self { - Self::that_beats(Self::that_beats(other)) - } -} - -enum Outcome { Win, Loss, Draw } - -struct Round { - opponent_hand: Hand, - player_hand: Hand, -} - -impl Round { - fn outcome(&self) -> Outcome { - match self.player_hand as i8 - self.opponent_hand as i8 { - 0 => Outcome::Draw, - 1 | -2 => Outcome::Win, - _ => Outcome::Loss, - } - } - fn points(&self) -> u32 { - self.player_hand as u32 + match self.outcome() { - Outcome::Loss => 0, - Outcome::Draw => 3, - Outcome::Win => 6, - } - } -} - -fn read_file(path: &str) -> String { - fs::read_to_string(path) - .expect("File not Found") -} - -fn parse_round(line: &str, strat: u8) -> Round { - let line = line.as_bytes(); - let opponent_hand = match line[0] { - b'A' => Hand::Rock, - b'B' => Hand::Paper, - b'C' => Hand::Scissors, - _ => panic!("Unexpected Token"), - }; - let player_hand = match strat { - 1 => { - match line[2] { - b'X' => Hand::Rock, - b'Y' => Hand::Paper, - b'Z' => Hand::Scissors, - _ => panic!("Unexpected Token"), - } - }, - 2 => { - match line[2] { - b'X' => Hand::that_is_beaten_by(opponent_hand), - b'Y' => opponent_hand, - b'Z' => Hand::that_beats(opponent_hand), - _ => panic!("Unexpected Token"), - } - }, - _ => panic!("Unexpected Strat"), - }; - Round { opponent_hand, player_hand } -} - -fn get_tally(moves: &str, strat: u8) -> u32 { - moves.lines() - .filter(|l| l.len() == 3) - .map(|l| parse_round(l, strat).points()) - .sum() -} - -fn main() { - let contents = read_file("input"); - println!("Total Points (Strat 1): {}", get_tally(&contents, 1)); - println!("Total Points (Strat 2): {}", get_tally(&contents, 2)); -} - -#[test] -fn sample_input() { - let contents = read_file("tests/sample_input"); - assert_eq!(get_tally(&contents, 1), 15); - assert_eq!(get_tally(&contents, 2), 12); -} - -#[test] -fn challenge_input() { - let contents = read_file("tests/input"); - assert_eq!(get_tally(&contents, 1), 12458); - assert_eq!(get_tally(&contents, 2), 12683); -} diff --git a/2022/day02-rock_paper_scissors/tests/input b/2022/day02-rock_paper_scissors/tests/challenge_input similarity index 100% rename from 2022/day02-rock_paper_scissors/tests/input rename to 2022/day02-rock_paper_scissors/tests/challenge_input