diff --git a/src/day2.rs b/src/day2.rs new file mode 100644 index 0000000..6737f2b --- /dev/null +++ b/src/day2.rs @@ -0,0 +1,171 @@ +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); + } +} diff --git a/src/lib.rs b/src/lib.rs index 8dee38d..9cca356 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +mod day2; mod day1; use aoc_runner_derive::aoc_lib;