use std::str::FromStr; use aoc_2022::*; use color_eyre::eyre::Result; use thiserror::Error; use tracing::*; use tracing_subscriber::EnvFilter; #[derive(Debug, Clone, Copy)] enum Shape { Rock, Paper, Scissors, } impl Shape { fn score(&self) -> i32 { match self { Shape::Rock => 1, Shape::Paper => 2, Shape::Scissors => 3, } } } #[derive(Error, Debug)] enum ShapeError { #[error("empty string given")] Empty, #[error("unknown shape ‹{0}›")] UnknownShape(String), } impl FromStr for Shape { type Err = ShapeError; fn from_str(s: &str) -> Result { if s.is_empty() { return Err(ShapeError::Empty); } match s { "A" | "X" => Ok(Shape::Rock), "B" | "Y" => Ok(Shape::Paper), "C" | "Z" => Ok(Shape::Scissors), _ => Err(ShapeError::UnknownShape(s.to_string())), } } } #[derive(Debug, Clone, Copy)] enum Outcome { Lose, Draw, Win, } #[derive(Debug, Clone, Copy)] struct Round { opponent: Shape, me: Shape, } #[derive(Error, Debug)] enum RoundError { #[error("invalid shape ‹{0}›")] InvalidShape(#[from] ShapeError), } impl FromStr for Round { type Err = RoundError; fn from_str(s: &str) -> Result { let mut split_str = s.split(' '); let opponent = split_str.next().unwrap().parse::(); let me = split_str.next().unwrap().parse::(); Ok(Round { opponent: opponent?, me: me?, }) } } impl Round { fn score(&self) -> i32 { let shape_score = self.me.score(); let result_score = match (self.me, self.opponent) { (Shape::Scissors, Shape::Paper) => 6, (Shape::Paper, Shape::Rock) => 6, (Shape::Rock, Shape::Scissors) => 6, (Shape::Paper, Shape::Scissors) => 0, (Shape::Rock, Shape::Paper) => 0, (Shape::Scissors, Shape::Rock) => 0, _ => 3, }; shape_score + result_score } fn expected_outcome(&self) -> Outcome { match self.me { Shape::Rock => Outcome::Lose, Shape::Paper => Outcome::Draw, Shape::Scissors => Outcome::Win, } } fn expected_score(&self) -> i32 { let result_score = match self.expected_outcome() { Outcome::Lose => 0, Outcome::Draw => 3, Outcome::Win => 6, }; let shape_score = match (self.expected_outcome(), self.opponent) { (Outcome::Lose, Shape::Rock) => Shape::Scissors.score(), (Outcome::Lose, Shape::Paper) => Shape::Rock.score(), (Outcome::Lose, Shape::Scissors) => Shape::Paper.score(), (Outcome::Win, Shape::Rock) => Shape::Paper.score(), (Outcome::Win, Shape::Paper) => Shape::Scissors.score(), (Outcome::Win, Shape::Scissors) => Shape::Rock.score(), _ => self.opponent.score(), }; result_score + shape_score } } type Input = Vec; type Output = i32; fn part_1(input: &Input) -> Output { input.iter().map(Round::score).sum() } fn part_2(input: &Input) -> Output { input.iter().map(Round::expected_score).sum() } fn parse_input(pathname: &str) -> Input { file_to_structs(pathname) } fn main() -> Result<()> { tracing_subscriber::fmt() .with_env_filter(EnvFilter::from_default_env()) .with_target(false) .with_file(true) .with_line_number(true) .without_time() .compact() .init(); color_eyre::install()?; let input = parse_input("inputs/day02.txt"); info!("Part 1: {}", part_1(&input)); info!("Part 2: {}", part_2(&input)); Ok(()) } #[cfg(test)] mod tests { use super::*; #[test] fn test_part_1() { let sample = parse_input("samples/day02.txt"); assert_eq!(part_1(&sample), 15); } #[test] fn test_part_2() { let sample = parse_input("samples/day02.txt"); assert_eq!(part_2(&sample), 12); } }