year2017/
day19.rs

1use utils::point::Point2D;
2use utils::prelude::*;
3
4/// Following a path.
5#[derive(Clone, Debug)]
6pub struct Day19 {
7    part1: String,
8    part2: u32,
9}
10
11impl Day19 {
12    pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
13        let lines = input.lines().collect::<Vec<_>>();
14        let lookup = |p: Point2D<usize>| {
15            if p.y < lines.len() && p.x < lines[p.y].len() {
16                Some(lines[p.y].as_bytes()[p.x])
17            } else {
18                None
19            }
20        };
21
22        let Some(start_col) = lines.first().and_then(|l| l.find('|')) else {
23            return Err(InputError::new(input, 0, "expected '|' on the first line"));
24        };
25        let mut pos = Point2D::new(start_col, 0);
26        let mut dir = Point2D::new(0, 1);
27
28        let mut letters = String::new();
29        let mut steps = 1;
30        loop {
31            match lookup(pos)
32                .ok_or_else(|| InputError::new(input, 0, "path leads outside the input"))?
33            {
34                b'|' | b'-' => {}
35                b'+' => {
36                    let left = lookup(pos.wrapping_add_signed(dir.turn_left())).unwrap_or(b' ');
37                    let right = lookup(pos.wrapping_add_signed(dir.turn_right())).unwrap_or(b' ');
38                    if matches!(left, b'|' | b'-' | b'A'..=b'Z') && right == b' ' {
39                        dir = dir.turn_left();
40                    } else if matches!(right, b'|' | b'-' | b'A'..=b'Z') && left == b' ' {
41                        dir = dir.turn_right();
42                    } else {
43                        return Err(InputError::new(
44                            input,
45                            &lines[pos.y][pos.x..],
46                            "invalid turn",
47                        ));
48                    }
49                }
50                letter @ b'A'..=b'Z' => {
51                    letters.push(letter as char);
52
53                    // The path is allowed to end after letters
54                    if lookup(pos.wrapping_add_signed(dir)).unwrap_or(b' ') == b' ' {
55                        break;
56                    }
57                }
58                _ => {
59                    return Err(InputError::new(
60                        input,
61                        &lines[pos.y][pos.x..],
62                        "expected '|', '-', '+' or 'A'-'Z'",
63                    ));
64                }
65            }
66
67            pos = pos.wrapping_add_signed(dir);
68            steps += 1;
69        }
70
71        Ok(Self {
72            part1: letters,
73            part2: steps,
74        })
75    }
76
77    #[must_use]
78    pub fn part1(&self) -> &str {
79        &self.part1
80    }
81
82    #[must_use]
83    pub fn part2(&self) -> u32 {
84        self.part2
85    }
86}
87
88examples!(Day19 -> (&'static str, u32) [
89    {file: "day19_example0.txt", part1: "ABCDEF", part2: 38},
90]);