pub struct DialPosition { pub current_position: i16, pub zero_hits_count: u32, } impl DialPosition { const DIAL_MAX: i16 = 99; const DIAL_COUNT: i16 = 100; pub fn new(current_position: i16) -> Self { Self { current_position, zero_hits_count: 0, } } pub fn rotated_left(&self, rotation: u64) -> Self { let (remaining, reduce_hits_count) = DialPosition::reduce_rotation(rotation); let current_position_u64 = self.current_position; let (result, clamp_hits_count) = self.clamp(current_position_u64 - remaining); Self { current_position: result, zero_hits_count: self.zero_hits_count + clamp_hits_count + reduce_hits_count, } } pub fn rotated_right(&self, rotation: u64) -> Self { let (remaining, reduce_hits_count) = DialPosition::reduce_rotation(rotation); let (result, clamp_hits_count) = self.clamp(self.current_position + remaining); Self { current_position: result, zero_hits_count: self.zero_hits_count + clamp_hits_count + reduce_hits_count, } } fn reduce_rotation(rotation: u64) -> (i16, u32) { let mut remaining = rotation; let mut zero_hits_count = 0; while remaining > u64::try_from(DialPosition::DIAL_MAX).unwrap() { remaining -= u64::try_from(DialPosition::DIAL_COUNT).unwrap(); zero_hits_count += 1; } (i16::try_from(remaining).unwrap(), zero_hits_count) } fn clamp(&self, value: i16) -> (i16, u32) { if value > DialPosition::DIAL_MAX { (value - 100, 1) } else if value < 0 { (100 + value, if self.current_position != 0 { 1 } else { 0 }) } else { (value, if value == 0 { 1 } else { 0 }) } } } #[cfg(test)] mod tests { use super::*; #[test] fn dial_rotated_right_from_0_by_2_gives_2() { let result = DialPosition::new(0).rotated_right(2); assert_eq!(result.current_position, 2); } #[test] fn dial_rotated_right_from_0_by_102_gives_2() { let result = DialPosition::new(0).rotated_right(102); assert_eq!(result.current_position, 2); } #[test] fn dial_rotated_left_from_0_by_2_gives_98() { let result = DialPosition::new(0).rotated_left(2); assert_eq!(result.current_position, 98); } #[test] fn dial_rotated_left_from_0_by_102_gives_98() { let result = DialPosition::new(0).rotated_left(102); assert_eq!(result.current_position, 98); } #[test] fn dial_rotated_left_from_5_by_105_gives_0() { let result = DialPosition::new(5).rotated_left(105); assert_eq!(result.current_position, 0); } #[test] fn dial_rotated_right_from_5_by_105_gives_10() { let result = DialPosition::new(5).rotated_right(105); assert_eq!(result.current_position, 10); } } // part 2 #[test] fn dial_from_50_right_1000_hits_zero_10_times() { let result = DialPosition::new(50).rotated_right(1000); assert_eq!(result.current_position, 50); assert_eq!(result.zero_hits_count, 10); } #[test] fn dial_from_50_left_68_hits_zero_once() { let result = DialPosition::new(50).rotated_left(68); assert_eq!(result.current_position, 82); assert_eq!(result.zero_hits_count, 1); } #[test] fn dial_from_82_left_30_no_zero_hit() { let result = DialPosition::new(82).rotated_left(30); assert_eq!(result.current_position, 52); assert_eq!(result.zero_hits_count, 0); } #[test] fn dial_from_52_right_48_lands_on_zero() { let result = DialPosition::new(52).rotated_right(48); assert_eq!(result.current_position, 0); assert_eq!(result.zero_hits_count, 1); } #[test] fn dial_from_0_left_5_no_zero_hit() { let result = DialPosition::new(0).rotated_left(5); assert_eq!(result.current_position, 95); assert_eq!(result.zero_hits_count, 0); } #[test] fn dial_from_95_right_60_hits_zero_once() { let result = DialPosition::new(95).rotated_right(60); assert_eq!(result.current_position, 55); assert_eq!(result.zero_hits_count, 1); } #[test] fn dial_from_55_left_55_lands_on_zero() { let result = DialPosition::new(55).rotated_left(55); assert_eq!(result.current_position, 0); assert_eq!(result.zero_hits_count, 1); } #[test] fn dial_from_0_left_1_no_zero_hit() { let result = DialPosition::new(0).rotated_left(1); assert_eq!(result.current_position, 99); assert_eq!(result.zero_hits_count, 0); } #[test] fn dial_from_99_left_99_lands_on_zero() { let result = DialPosition::new(99).rotated_left(99); assert_eq!(result.current_position, 0); assert_eq!(result.zero_hits_count, 1); } #[test] fn dial_from_0_right_14_no_zero_hit() { let result = DialPosition::new(0).rotated_right(14); assert_eq!(result.current_position, 14); assert_eq!(result.zero_hits_count, 0); } #[test] fn dial_from_14_left_82_hits_zero_once() { let result = DialPosition::new(14).rotated_left(82); assert_eq!(result.current_position, 32); assert_eq!(result.zero_hits_count, 1); }