use anyhow::Result; use nom::character::complete::newline; use nom::combinator::map_res; use nom::multi::many0; use nom::sequence::separated_pair; use nom::sequence::terminated; use nom::IResult; use crate::common::parse_input; #[derive(Copy, Clone, Eq, PartialEq)] enum Rps { Rock, Paper, Scissors, } impl Rps { /// Score we get by playing this move fn score(self) -> u32 { match self { Rps::Rock => 1, Rps::Paper => 2, Rps::Scissors => 3, } } /// Score we get from the result from playing given other fn score_against(self, other: Self) -> u32 { match (self, other) { (a, b) if a == b => 3, (Rps::Rock, Rps::Paper) | (Rps::Paper, Rps::Scissors) | (Rps::Scissors, Rps::Rock) => 0, _ => 6, } } /// Score if the result is according to the instruction fn score_result(self) -> u32 { match self { Rps::Rock => 0, // Rock is lose Rps::Paper => 3, // Paper is draw Rps::Scissors => 6, // Scissors is win } } /// Move we need to achieve the result indicated by self fn needed(self, other: Self) -> Self { match (self, other) { (Rps::Paper, other) => other, (Rps::Rock, Rps::Rock) => Rps::Scissors, (Rps::Rock, Rps::Paper) => Rps::Rock, (Rps::Rock, Rps::Scissors) => Rps::Paper, (Rps::Scissors, Rps::Rock) => Rps::Paper, (Rps::Scissors, Rps::Paper) => Rps::Scissors, (Rps::Scissors, Rps::Scissors) => Rps::Rock, } } } impl TryFrom for Rps { type Error = anyhow::Error; fn try_from(value: u8) -> Result { match value { b'A' | b'X' => Ok(Rps::Rock), b'B' | b'Y' => Ok(Rps::Paper), b'C' | b'Z' => Ok(Rps::Scissors), _ => Err(anyhow::anyhow!("Invalid RPS: {value}")), } } } fn parse_plan(input: &[u8]) -> IResult<&[u8], Vec<(Rps, Rps)>> { fn parse_rps(input: &[u8]) -> IResult<&[u8], Rps> { // Note: alpha1 also sort of works but is significantly slower map_res(nom::bytes::complete::take(1usize), |v: &[u8]| { Rps::try_from(v[0]) })(input) } fn parse_line(input: &[u8]) -> IResult<&[u8], (Rps, Rps)> { separated_pair(parse_rps, nom::character::complete::char(' '), parse_rps)(input) } many0(terminated(parse_line, newline))(input) } pub fn part1(input: &[u8]) -> Result { let plan = parse_input(input, parse_plan)?; let result: u32 = plan .into_iter() .map(|(them, us)| us.score() + us.score_against(them)) .sum(); Ok(result.to_string()) } pub fn part2(input: &[u8]) -> Result { let plan = parse_input(input, parse_plan)?; let result: u32 = plan .into_iter() .map(|(them, us)| us.score_result() + us.needed(them).score()) .sum(); Ok(result.to_string()) } #[cfg(test)] mod tests { use super::*; const SAMPLE: &[u8] = include_bytes!("samples/02.txt"); #[test] fn sample_part1() { assert_eq!(part1(SAMPLE).unwrap(), "15") } #[test] fn sample_part2() { assert_eq!(part2(SAMPLE).unwrap(), "12") } }