1use utils::point::Point2D;
2use utils::prelude::*;
3
4#[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 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]);