use std::str::FromStr; use aoc_2022::*; use color_eyre::eyre::Result; use itertools::Itertools; use thiserror::Error; use tracing::*; use tracing_subscriber::EnvFilter; #[derive(Debug, Clone, Copy, PartialEq, Eq)] 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?, }) } } fn find_strategy

(predicate: P) -> (usize, (Shape, Shape)) where P: FnMut(&(usize, (Shape, Shape))) -> bool, { // R P S // R 3 0 6 // P 6 3 0 // S 0 6 3 vec![ // Loss (Shape::Rock, Shape::Paper), (Shape::Paper, Shape::Scissors), (Shape::Scissors, Shape::Rock), // Draw (Shape::Rock, Shape::Rock), (Shape::Paper, Shape::Paper), (Shape::Scissors, Shape::Scissors), // Win (Shape::Rock, Shape::Scissors), (Shape::Paper, Shape::Rock), (Shape::Scissors, Shape::Paper), ] .into_iter() .enumerate() .find_or_first(predicate) .unwrap() } fn find_result(strategy: &(Shape, Shape)) -> i32 { 3 * (find_strategy(|&(_, st)| st == *strategy).0 as i32 / 3) } fn find_result_for_outcome(opponent: Shape, outcome: Outcome) -> i32 { let range = match outcome { Outcome::Lose => 0..3, Outcome::Draw => 3..6, Outcome::Win => 6..9, }; let (i, (shape, _)) = find_strategy(|&(i, (_, op))| range.contains(&i) && op == opponent); 3 * (i as i32 / 3) + shape.score() } impl Round { fn score(&self) -> i32 { let shape_score = self.me.score(); let result_score = find_result(&(self.me, self.opponent)); 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 { find_result_for_outcome(self.opponent, self.expected_outcome()) } } 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); } }