programming puzzle solutions
1/// The direction to rotate the dial.
2pub enum Direction {
3 Left, // Move toward lower numbers.
4 Right, // Move toward higher numbers.
5}
6
7/// An instruction to rotate the dial.
8pub struct Instruction {
9 pub direction: Direction, // L or R.
10 pub distance: u16, // How many clicks to rotate.
11}
12impl Instruction {
13 /// Parse an instruction from a line of input (e.g. "L68" or "R48").
14 pub fn parse(line: &str) -> Self {
15 let direction = match &line[0..1] {
16 "L" => Direction::Left, // Parse the direction character.
17 "R" => Direction::Right,
18 _ => unreachable!(), // All input has a L/R direction.
19 };
20 let distance = line[1..].parse().expect("valid distance"); // Then parse the distance value.
21 Self {
22 direction,
23 distance,
24 }
25 }
26}
27
28/// A safe dial with positions 0-99 that wraps around.
29pub struct Dial {
30 pub position: u16, // Current position on the dial.
31}
32impl Dial {
33 /// Create a new dial starting at position 50.
34 pub const fn new() -> Self {
35 Self { position: 50 }
36 }
37
38 /// Rotate the dial according to an instruction, wrapping around at 0/99 using modulo arithmetic.
39 ///
40 /// The remainder of the modulo operation is effectively, "what remains after subtracting 100 as many times as possible?"
41 /// This allows us to account for full rotations around the dial and determining what position we should have landed on.
42 pub const fn rotate(&mut self, instruction: &Instruction) {
43 self.position = match instruction.direction {
44 Direction::Left => (self.position + 100 - instruction.distance % 100) % 100, // For Left, add 100 before subtracting to avoid negative values.
45 Direction::Right => (self.position + instruction.distance) % 100, // For Right, simply wrap around at 100.
46 };
47 }
48
49 /// Check if dial points at 0.
50 pub const fn is_at_zero(&self) -> bool {
51 self.position == 0
52 }
53
54 /// Count how many times the dial passes through 0 during a rotation using division.
55 pub const fn count_zero_crossings(&self, instruction: &Instruction) -> u16 {
56 match instruction.direction {
57 Direction::Left => {
58 let first_crossing = if self.position == 0 { 100 } else { self.position };
59 if instruction.distance < first_crossing { 0 } // Distance too short to reach first crossing.
60 else { (instruction.distance - first_crossing) / 100 + 1 } // First crossing + additional full rotations.
61 }
62 Direction::Right => (self.position + instruction.distance) / 100, // Count complete 100-step cycles.
63 }
64 }
65}