1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
use utils::point::Point2D;
use utils::prelude::*;

/// Following a path.
#[derive(Clone, Debug)]
pub struct Day19 {
    part1: String,
    part2: u32,
}

impl Day19 {
    pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
        let lines = input.lines().collect::<Vec<_>>();
        let lookup = |p: Point2D<usize>| {
            if p.y < lines.len() && p.x < lines[p.y].len() {
                Some(lines[p.y].as_bytes()[p.x])
            } else {
                None
            }
        };

        let Some(start_col) = lines.first().and_then(|l| l.find('|')) else {
            return Err(InputError::new(input, 0, "expected '|' on the first line"));
        };
        let mut pos = Point2D::new(start_col, 0);
        let mut dir = Point2D::new(0, 1);

        let mut letters = String::new();
        let mut steps = 1;
        loop {
            match lookup(pos)
                .ok_or_else(|| InputError::new(input, 0, "path leads outside the input"))?
            {
                b'|' | b'-' => {}
                b'+' => {
                    let left = lookup(pos.wrapping_add_signed(dir.turn_left())).unwrap_or(b' ');
                    let right = lookup(pos.wrapping_add_signed(dir.turn_right())).unwrap_or(b' ');
                    if matches!(left, b'|' | b'-' | b'A'..=b'Z') && right == b' ' {
                        dir = dir.turn_left();
                    } else if matches!(right, b'|' | b'-' | b'A'..=b'Z') && left == b' ' {
                        dir = dir.turn_right();
                    } else {
                        return Err(InputError::new(
                            input,
                            &lines[pos.y][pos.x..],
                            "invalid turn",
                        ));
                    }
                }
                letter @ b'A'..=b'Z' => {
                    letters.push(letter as char);

                    // The path is allowed to end after letters
                    if lookup(pos.wrapping_add_signed(dir)).unwrap_or(b' ') == b' ' {
                        break;
                    }
                }
                _ => {
                    return Err(InputError::new(
                        input,
                        &lines[pos.y][pos.x..],
                        "expected '|', '-', '+' or 'A'-'Z'",
                    ));
                }
            }

            pos = pos.wrapping_add_signed(dir);
            steps += 1;
        }

        Ok(Self {
            part1: letters,
            part2: steps,
        })
    }

    #[must_use]
    pub fn part1(&self) -> &str {
        &self.part1
    }

    #[must_use]
    pub fn part2(&self) -> u32 {
        self.part2
    }
}

examples!(Day19 -> (&'static str, u32) [
    {file: "day19_example0.txt", part1: "ABCDEF", part2: 38},
]);