use anyhow::anyhow; use std::{ env, fs, ops::{Add, Sub}, str::FromStr, }; #[derive(Debug, PartialEq, Eq)] enum Rotation { Left(u32), Right(u32), } #[derive(Debug)] struct ParseRotationError; impl FromStr for Rotation { type Err = ParseRotationError; fn from_str(s: &str) -> Result { let mut chars = s.chars(); let lr = chars.next().ok_or(ParseRotationError)?; let n: u32 = chars .collect::() .parse() .map_err(|_| ParseRotationError)?; match lr { 'L' => Ok(Self::Left(n)), 'R' => Ok(Self::Right(n)), _ => Err(ParseRotationError), } } } #[derive(Clone, Copy, PartialEq, Eq, Debug)] struct Dial(u8); struct RotateResult(u32, Dial); impl Dial { fn rotate(self, rot: &Rotation) -> RotateResult { let pos = self.0 as u32; match rot { Rotation::Left(n) => { let quot: u32 = n / 100; RotateResult( quot + if pos > 0 && n % 100 >= pos { 1 } else { 0 }, self - *n, ) } Rotation::Right(n) => { if n + pos >= 100 { let quot = n / 100 + (if pos + n % 100 >= 100 { 1 } else { 0 }); RotateResult(quot, self + *n) } else { RotateResult(0, self + *n) } } } } } impl From for Dial { fn from(value: u8) -> Self { Self(value) } } impl Add for Dial { type Output = Self; fn add(self, rhs: u32) -> Self::Output { let n = self.0 as u32; let m = (n + rhs) % 100; Self(m as u8) } } impl Sub for Dial { type Output = Self; fn sub(self, rhs: u32) -> Self::Output { let n = self.0 as u32; let m = (n + 100 - rhs % 100) % 100; Self(m as u8) } } impl PartialEq for Dial { fn eq(&self, other: &u8) -> bool { self.0 == *other } } fn main() -> anyhow::Result<()> { let mut args = env::args(); let input_path = args.nth(1).ok_or_else(|| anyhow!("usage: day1 INPUT"))?; let input = fs::read_to_string(input_path)?; let rotations: Vec = input .split_whitespace() .map(|s| s.parse().map_err(|_| anyhow!("bad rotation: {}", s))) .collect::>()?; let mut dial = Dial::from(50); let mut exact_zeros = 0; let mut total_zeros = 0; for rot in &rotations { let RotateResult(zeros, new_dial) = dial.rotate(rot); if new_dial == 0 { exact_zeros += 1; } total_zeros += zeros; dial = new_dial; eprintln!("rotation: {:?}, dial: {}, zeros: {}", rot, dial.0, zeros); } println!("Password: {}", exact_zeros); println!("Password (method 0x434C49434B): {}", total_zeros); Ok(()) } #[cfg(test)] mod tests { use super::*; #[test] fn parse_rotation() { assert!("L2" .parse::() .is_ok_and(|rot| rot == Rotation::Left(2))); assert!("R929" .parse::() .is_ok_and(|rot| rot == Rotation::Right(929))); assert!("M1".parse::().is_err()); assert!("L".parse::().is_err()); assert!("2".parse::().is_err()); } #[test] fn dial_ops() { let dial = Dial::from(50); assert_eq!(dial + 1, 51); assert_eq!(dial + 50, 0); assert_eq!(dial + 100, 50); assert_eq!(dial + 150, 0); assert_eq!(dial + 2 + 48, 0); assert_eq!(dial + 45 + 60, 55); assert_eq!(dial - 45, 5); assert_eq!(dial - 45 - 10, 95); assert_eq!(dial - 1, 49); assert_eq!(dial - 68, 82); assert_eq!(dial - 68 - 30, 52); assert_eq!(dial - 100, 50); assert_eq!(dial - 998, 52); assert_eq!(dial - 150, 0); assert_eq!(dial - 51, 99); assert_eq!(dial - 50, 0); } #[test] fn dial_rotate() { let mut dial = Dial::from(50); let rotations = vec![ // Example data (Rotation::Left(68), 1, 82), (Rotation::Left(30), 0, 52), (Rotation::Right(48), 1, 0), (Rotation::Left(5), 0, 95), (Rotation::Right(60), 1, 55), (Rotation::Left(55), 1, 0), (Rotation::Left(1), 0, 99), (Rotation::Left(99), 1, 0), (Rotation::Right(14), 0, 14), (Rotation::Left(82), 1, 32), // Additional data (Rotation::Left(200), 2, 32), (Rotation::Right(200), 2, 32), (Rotation::Left(30), 0, 2), (Rotation::Left(10), 1, 92), (Rotation::Right(10), 1, 2), (Rotation::Left(110), 2, 92), (Rotation::Right(210), 3, 2), (Rotation::Right(48), 0, 50), (Rotation::Right(1000), 10, 50), (Rotation::Right(48), 0, 98), (Rotation::Left(651), 6, 47), (Rotation::Right(651), 6, 98), (Rotation::Right(203), 3, 1), (Rotation::Left(1), 1, 0), (Rotation::Left(100), 1, 0), (Rotation::Right(100), 1, 0), ]; for (rot, expected_zeros, expected_dial) in &rotations { let RotateResult(zeros, new_dial) = dial.rotate(rot); assert_eq!( &zeros, expected_zeros, "rotating from {} with {:?}", dial.0, rot ); assert_eq!( &new_dial, expected_dial, "rotating from {} with {:?}", dial.0, rot ); dial = new_dial; } } }