use std::cmp::Ordering; use crate::common::file::split_lines; use super::template::{DayTrait, ResultType}; use thiserror::Error; const DAY_NUMBER: usize = 2; pub struct Day; impl DayTrait for Day { fn get_day_number(&self) -> usize { DAY_NUMBER } fn part1(&self, lines: &str) -> anyhow::Result { let sum = split_lines(lines) .map(|line| Rps::parse_line(line)) .collect::, _>>()? .into_iter() .map(|(first, second)| second.asses_pair(&first)) .sum(); Ok(ResultType::Integer(sum)) } fn part2(&self, lines: &str) -> anyhow::Result { let sum = split_lines(lines) .map(|line| Strategy::parse_line(line)) .collect::, _>>()? .into_iter() .map(|(first, second)| second.fullfill(&first).asses_pair(&first)) .sum(); Ok(ResultType::Integer(sum)) } } #[derive(Debug, Error)] enum RPSError { #[error("No a valid RPS: {0}")] ParseError(String), #[error("Not a logal RPS line: {0}")] IllegalLine(String), } #[derive(Debug, PartialEq, Eq, Copy, Clone)] enum Rps { Rock, Paper, Scissors, } impl Rps { pub fn parse_line(line: &str) -> Result<(Self, Self), RPSError> { let mut parts = line.split(' '); let (Some(first), Some(second)) = (parts.next(), parts.next()) else { Err(RPSError::IllegalLine(line.to_owned()))? }; let first = Rps::try_from(first)?; let second = Rps::try_from(second)?; Ok((first, second)) } pub fn value(&self) -> i64 { match self { Rps::Rock => 1, Rps::Paper => 2, Rps::Scissors => 3, } } pub fn asses_pair(&self, other: &Self) -> i64 { let outcome = match self.partial_cmp(other) { Some(Ordering::Less) => 0, Some(Ordering::Equal) => 3, Some(Ordering::Greater) => 6, None => unreachable!(), }; outcome + self.value() } } impl PartialOrd for Rps { fn partial_cmp(&self, other: &Self) -> Option { match (self, other) { (Rps::Rock, Rps::Paper) | (Rps::Paper, Rps::Scissors) | (Rps::Scissors, Rps::Rock) => { Some(Ordering::Less) } (Rps::Rock, Rps::Scissors) | (Rps::Paper, Rps::Rock) | (Rps::Scissors, Rps::Paper) => { Some(Ordering::Greater) } _ => Some(Ordering::Equal), } } } impl TryFrom<&str> for Rps { type Error = RPSError; fn try_from(value: &str) -> std::result::Result { match value { "A" | "X" => Ok(Rps::Rock), "B" | "Y" => Ok(Rps::Paper), "C" | "Z" => Ok(Rps::Scissors), _ => Err(RPSError::ParseError(value.to_owned())), } } } #[derive(Debug, Eq, PartialEq, Clone, Copy)] enum Strategy { Loose, Draw, Win, } impl Strategy { pub fn parse_line(line: &str) -> Result<(Rps, Self), RPSError> { let mut parts = line.split(' '); let (Some(first), Some(second)) = (parts.next(), parts.next()) else { Err(RPSError::IllegalLine(line.to_owned()))? }; let first = Rps::try_from(first)?; let second = Strategy::try_from(second)?; Ok((first, second)) } pub fn fullfill(&self, other: &Rps) -> Rps { match (other, self) { (_, Strategy::Draw) => *other, (Rps::Rock, Strategy::Win) | (Rps::Scissors, Strategy::Loose) => Rps::Paper, (Rps::Paper, Strategy::Win) | (Rps::Rock, Strategy::Loose) => Rps::Scissors, (Rps::Scissors, Strategy::Win) | (Rps::Paper, Strategy::Loose) => Rps::Rock, } } } impl TryFrom<&str> for Strategy { type Error = RPSError; fn try_from(value: &str) -> std::result::Result { match value { "X" => Ok(Strategy::Loose), "Y" => Ok(Strategy::Draw), "Z" => Ok(Strategy::Win), _ => Err(RPSError::ParseError(value.to_owned())), } } } #[cfg(test)] mod test { use super::*; use crate::common::file::read_string; use anyhow::Result; #[test] fn test_parse() -> Result<()> { let input = "A Y"; let expected = (Rps::Rock, Rps::Paper); let result = Rps::parse_line(input)?; assert_eq!(result, expected); Ok(()) } #[test] fn test_assess() -> Result<()> { let first = Rps::Scissors; let second = Rps::Paper; let expected = 9; let result = first.asses_pair(&second); assert_eq!(result, expected); Ok(()) } #[test] fn test_part1() -> Result<()> { let day = Day {}; let lines = read_string(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(15); let result = day.part1(&lines)?; assert_eq!(result, expected); Ok(()) } #[test] fn test_parse_strategy() -> Result<()> { let input = "A Y"; let expected = (Rps::Rock, Strategy::Draw); let result = Strategy::parse_line(input)?; assert_eq!(result, expected); Ok(()) } #[test] fn test_assess_stragety() -> Result<()> { let first = Rps::Scissors; let second = Strategy::Win; let expected = Rps::Rock; let result = second.fullfill(&first); assert_eq!(result, expected); Ok(()) } #[test] fn test_part2() -> Result<()> { let day = Day {}; let lines = read_string(day.get_day_number(), "example01.txt")?; let expected = ResultType::Integer(12); let result = day.part2(&lines)?; assert_eq!(result, expected); Ok(()) } }