1use std::cmp::Reverse;
2use std::collections::BinaryHeap;
3use utils::grid;
4use utils::prelude::*;
5
6#[derive(Clone, Debug)]
8pub struct Day16 {
9    grid: Vec<u8>,
10    start: usize,
11    end: usize,
12    offsets: [isize; 4],
13    cheapest: Vec<[u32; 4]>,
14    part1: u32,
15}
16
17impl Day16 {
18    pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
19        let ((_, cols, grid), start, end) = grid::parse_maze(input, 1)?;
20
21        let mut instance = Self {
22            cheapest: vec![[u32::MAX; 4]; grid.len()],
23            part1: 0,
24            grid,
25            start,
26            end,
27            offsets: [1, cols as isize, -1, -(cols as isize)],
28        };
29
30        if !instance.dijkstra() {
32            return Err(InputError::new(input, 'E', "no path"));
33        }
34
35        Ok(instance)
36    }
37
38    fn dijkstra(&mut self) -> bool {
39        let mut queue = BinaryHeap::new();
40        queue.push(Reverse((0, self.start, 0)));
41        self.cheapest[self.start][0] = 0;
42
43        while let Some(Reverse((score, index, dir))) = queue.pop() {
44            if score > self.cheapest[index][dir] {
45                continue;
46            }
47            if index == self.end {
48                self.part1 = score;
49                return true;
50            }
51
52            if let Some(branch) =
55                self.find_branch(index.wrapping_add_signed(self.offsets[dir]), dir, score + 1)
56            {
57                queue.push(Reverse(branch));
59            }
60
61            for next_dir in [(dir + 1) % 4, (dir + 3) % 4] {
62                if score + 1000 < self.cheapest[index][next_dir] {
64                    self.cheapest[index][next_dir] = score + 1000;
65
66                    if let Some(branch) = self.find_branch(
67                        index.wrapping_add_signed(self.offsets[next_dir]),
68                        next_dir,
69                        score + 1001,
70                    ) {
71                        queue.push(Reverse(branch));
72                    }
73                }
74            }
75        }
76
77        false
78    }
79
80    fn find_branch(
81        &mut self,
82        mut index: usize,
83        mut dir: usize,
84        mut score: u32,
85    ) -> Option<(u32, usize, usize)> {
86        if self.grid[index] != b'.' {
87            return None;
88        }
89
90        loop {
91            if score < self.cheapest[index][dir] {
92                self.cheapest[index][dir] = score;
93            } else if score > self.cheapest[index][dir] {
94                return None;
95            }
96
97            if index == self.end {
98                break;
99            }
100
101            let mut count = 0;
102            let mut next_index = 0;
103            let mut next_dir = 0;
104            for d in [dir, (dir + 1) % 4, (dir + 3) % 4] {
105                let i = index.wrapping_add_signed(self.offsets[d]);
106                if self.grid[i] == b'.' {
107                    count += 1;
108                    next_index = i;
109                    next_dir = d;
110                }
111            }
112
113            if count == 0 {
114                return None;
115            } else if count > 1 {
116                break;
117            }
118
119            score += if dir == next_dir { 1 } else { 1001 };
120            index = next_index;
121            dir = next_dir;
122        }
123
124        Some((score, index, dir))
125    }
126
127    #[must_use]
128    pub fn part1(&self) -> u32 {
129        self.part1
130    }
131
132    #[must_use]
133    pub fn part2(&self) -> u32 {
134        let mut on_best = vec![0u8; self.grid.len()];
135        on_best[self.start] = 0b1111;
136        on_best[self.end] = 0b1111;
137        for d in 0..4 {
138            if self.cheapest[self.end][d] == self.part1 {
139                let prev = self.end.wrapping_add_signed(-self.offsets[d]);
140                self.reverse(prev, d, self.part1 - 1, &mut on_best);
141            }
142        }
143        on_best.iter().filter(|&&b| b != 0).count() as u32
144    }
145
146    fn reverse(&self, index: usize, dir: usize, score: u32, on_best: &mut [u8]) {
147        if on_best[index] & (1 << dir) != 0 {
148            return;
149        }
150        on_best[index] |= 1 << dir;
151
152        if score < 1000 {
153            self.reverse(
156                index.wrapping_add_signed(-self.offsets[dir]),
157                dir,
158                score - 1,
159                on_best,
160            );
161            return;
162        }
163
164        let mut count = 0;
165        let mut next_index = 0;
166        let mut next_dir = 0;
167        for d in [dir, (dir + 1) % 4, (dir + 3) % 4] {
168            let i = index.wrapping_add_signed(-self.offsets[d]);
169            if self.grid[i] == b'.' {
170                count += 1;
171                next_index = i;
172                next_dir = d;
173            }
174        }
175        assert!(count > 0);
176
177        if count == 1 {
178            let next_score = score - if dir == next_dir { 1 } else { 1001 };
179            self.reverse(next_index, next_dir, next_score, on_best);
180        } else {
181            for (next_dir, next_score) in [
183                (dir, score),
184                ((dir + 1) % 4, score - 1000),
185                ((dir + 3) % 4, score - 1000),
186            ] {
187                let next_index = index.wrapping_add_signed(-self.offsets[next_dir]);
188                if self.cheapest[index][next_dir] == next_score
189                    && self.cheapest[next_index][next_dir] == next_score - 1
190                {
191                    self.reverse(next_index, next_dir, next_score - 1, on_best);
192                }
193            }
194        }
195    }
196}
197
198examples!(Day16 -> (u32, u32) [
199    {file: "day16_example0.txt", part1: 7036, part2: 45},
200    {file: "day16_example1.txt", part1: 11048, part2: 64},
201]);