use std::{io::BufRead, str::FromStr}; use aoc_runner_derive::{aoc, aoc_generator}; #[derive(Eq, PartialEq, Clone, Copy)] pub enum Shape { Rock, Paper, Scissors, } impl FromStr for Shape { type Err = Box; fn from_str(value: &str) -> Result { match value { "A" | "X" => Ok(Self::Rock), "B" | "Y" => Ok(Self::Paper), "C" | "Z" => Ok(Self::Scissors), c => Err(format!("Invalid shape `{c:?}`").into()), } } } impl Shape { fn fight(&self, opponent: &Shape) -> Outcome { match (self, opponent) { (Self::Rock, Self::Scissors) | (Self::Scissors, Self::Paper) | (Self::Paper, Self::Rock) => Outcome::Win, _ if self == opponent => Outcome::Tie, _ => Outcome::Lose, } } fn score(&self, opponent: &Shape) -> u64 { self.points() + self.fight(opponent).points() } fn points(&self) -> u64 { match self { Shape::Rock => 1, Shape::Paper => 2, Shape::Scissors => 3, } } // Return the Shape that will produce the desired outcome against us for the opponent fn fix_round(&self, goal: &Outcome) -> Shape { match goal { Outcome::Tie => *self, Outcome::Win => match self { Shape::Rock => Shape::Paper, Shape::Paper => Shape::Scissors, Shape::Scissors => Shape::Rock, }, Outcome::Lose => match self { Shape::Rock => Shape::Scissors, Shape::Paper => Shape::Rock, Shape::Scissors => Shape::Paper, }, } } } pub enum Outcome { Win, Lose, Tie, } impl Outcome { fn points(&self) -> u64 { match self { Self::Win => 6, Self::Tie => 3, Self::Lose => 0, } } } impl FromStr for Outcome { type Err = Box; fn from_str(s: &str) -> Result { match s { "X" => Ok(Self::Lose), "Y" => Ok(Self::Tie), "Z" => Ok(Self::Win), _ => Err(format!("Invalid outcome : `{s}").into()), } } } pub struct Move { ours: Shape, theirs: Shape, } impl FromStr for Move { type Err = Box; fn from_str(value: &str) -> Result { let (theirs, ours) = value.split_once(&" ").ok_or("Unable to split")?; Ok(Move { ours: ours.parse()?, theirs: theirs.parse()?, }) } } impl Move { fn score(&self) -> u64 { self.ours.score(&self.theirs) } } pub struct MoveGoal { theirs: Shape, goal: Outcome, } impl FromStr for MoveGoal { type Err = Box; fn from_str(s: &str) -> Result { let (theirs, goal) = s.split_once(&" ").ok_or("Unable to split")?; Ok(MoveGoal { theirs: theirs.parse()?, goal: goal.parse()?, }) } } impl MoveGoal { fn get_move(&self) -> Move { Move { theirs: self.theirs, ours: self.theirs.fix_round(&self.goal), } } } #[aoc_generator(day2, part1)] fn parse_part1(input: &[u8]) -> Vec { input.lines().map(|l| l.unwrap().parse().unwrap()).collect() } #[aoc_generator(day2, part2)] fn parse_part2(input: &[u8]) -> Vec { input.lines().map(|l| l.unwrap().parse().unwrap()).collect() } #[aoc(day2, part1)] fn part1(input: &Vec) -> u64 { input.iter().map(|m| m.score()).sum() } #[aoc(day2, part2)] fn part2(input: &Vec) -> u64 { input.iter().map(|mg| mg.get_move().score()).sum() } #[cfg(test)] mod tests { use super::*; const EXAMPLE: &[u8] = b"A Y B X C Z"; #[test] fn part1_example() { assert_eq!(part1(&parse_part1(EXAMPLE)), 15); } #[test] fn part2_example() { assert_eq!(part2(&parse_part2(EXAMPLE)), 12); } }