1use std::collections::VecDeque;
16
17use crate::util::grid::*;
18use crate::util::hash::*;
19use crate::util::point::*;
20
21type Key = ((u8, u8), Kind);
22
23#[derive(Clone, Copy, Eq, Hash, PartialEq)]
24pub enum Kind {
25 Inner,
26 Outer,
27 Start,
28 End,
29}
30
31enum Tile {
32 Wall,
33 Open,
34 Portal(Key, Kind),
35}
36
37struct Edge {
38 to: usize,
39 kind: Kind,
40 distance: u32,
41}
42
43pub struct Maze {
44 start: usize,
45 portals: Vec<Vec<Edge>>,
46}
47
48pub fn parse(input: &str) -> Maze {
51 let grid = Grid::parse(input);
52 let width = grid.width as usize;
53
54 let mut tiles: Vec<_> =
55 grid.bytes.iter().map(|&b| if b == b'.' { Tile::Open } else { Tile::Wall }).collect();
56 let mut map = FastMap::new();
57 let mut found = Vec::new();
58 let mut start = usize::MAX;
59
60 for y in (1..grid.height - 1).step_by(2) {
62 for x in (1..grid.width - 1).step_by(2) {
63 let point = Point::new(x, y);
64 if !grid[point].is_ascii_uppercase() {
65 continue;
66 }
67
68 let (first, second, third) = if grid[point + UP] == b'.' {
70 (point, point + DOWN, point + UP)
71 } else if grid[point + DOWN] == b'.' {
72 (point + UP, point, point + DOWN)
73 } else if grid[point + LEFT] == b'.' {
74 (point, point + RIGHT, point + LEFT)
75 } else if grid[point + RIGHT] == b'.' {
76 (point + LEFT, point, point + RIGHT)
77 } else {
78 continue;
79 };
80
81 let pair = (grid[first], grid[second]);
82 let index = (grid.width * third.y + third.x) as usize;
83 let inner = 2 < x && x < grid.width - 3 && 2 < y && y < grid.height - 3;
84
85 let (kind, opposite) = if inner {
86 (Kind::Inner, Kind::Outer)
87 } else {
88 match pair {
89 (b'A', b'A') => {
90 start = found.len();
91 (Kind::Start, Kind::Start)
92 }
93 (b'Z', b'Z') => (Kind::End, Kind::End),
94 _ => (Kind::Outer, Kind::Inner),
95 }
96 };
97
98 tiles[index] = Tile::Portal((pair, opposite), kind);
100 map.insert((pair, kind), found.len());
101 found.push(index);
102 }
103 }
104
105 let mut portals = Vec::new();
107 let mut todo = VecDeque::new();
108 let mut seen = vec![0; tiles.len()];
109
110 for start in found {
111 let mut edges = Vec::new();
112 todo.push_back((start, 0));
113
114 while let Some((index, steps)) = todo.pop_front() {
115 seen[index] = start;
116
117 for next_index in [index + 1, index - 1, index + width, index - width] {
118 let next_steps = steps + 1;
119
120 if seen[next_index] != start {
121 match tiles[next_index] {
122 Tile::Wall => (),
123 Tile::Open => {
124 todo.push_back((next_index, next_steps));
125 }
126 Tile::Portal(key, kind) => {
127 let to = map[&key];
128 edges.push(Edge { to, kind, distance: next_steps });
129 }
130 }
131 }
132 }
133 }
134
135 portals.push(edges);
136 }
137
138 Maze { start, portals }
139}
140
141pub fn part1(input: &Maze) -> u32 {
143 let mut todo = VecDeque::new();
144 todo.push_back((0, input.start));
145
146 while let Some((steps, index)) = todo.pop_front() {
147 for &Edge { to, kind, distance } in &input.portals[index] {
148 let next_steps = steps + distance + 1;
149
150 match kind {
151 Kind::Inner | Kind::Outer => todo.push_back((next_steps, to)),
152 Kind::End => return next_steps - 1,
153 Kind::Start => (),
154 }
155 }
156 }
157
158 unreachable!()
159}
160
161pub fn part2(input: &Maze) -> u32 {
163 let mut cache = FastMap::with_capacity(2_000);
164 let mut todo = VecDeque::new();
165 todo.push_back((0, input.start, 0));
166
167 while let Some((steps, index, level)) = todo.pop_front() {
168 let best = cache.entry((index, level)).or_insert(u32::MAX);
169 if *best <= steps {
170 continue;
171 }
172 *best = steps;
173
174 for &Edge { to, kind, distance } in &input.portals[index] {
175 let next_steps = steps + distance + 1;
176
177 match kind {
178 Kind::Inner if level < input.portals.len() => {
180 todo.push_back((next_steps, to, level + 1));
181 }
182 Kind::Outer if level > 0 => {
183 todo.push_back((next_steps, to, level - 1));
184 }
185 Kind::End if level == 0 => {
186 return next_steps - 1;
187 }
188 _ => (),
189 }
190 }
191 }
192
193 unreachable!()
194}