1use crate::util::hash::*;
63use crate::util::math::*;
64use crate::util::parse::*;
65use crate::util::point::*;
66use std::collections::VecDeque;
67use std::ops::Neg;
68
69#[derive(Clone, Copy, PartialEq, Eq)]
70enum Tile {
71 None,
72 Open,
73 Wall,
74}
75
76enum Move {
77 Left,
78 Right,
79 Forward(u32),
80}
81
82pub struct Grid {
83 width: usize,
84 height: usize,
85 tiles: Vec<Tile>,
86 start: i32,
87 block: i32,
88}
89
90impl Grid {
92 fn tile(&self, point: Point) -> Tile {
93 let x = point.x as usize;
95 let y = point.y as usize;
96 if x < self.width && y < self.height { self.tiles[y * self.width + x] } else { Tile::None }
97 }
98}
99
100#[derive(Copy, Clone, Hash, PartialEq, Eq)]
102struct Vector {
103 x: i32,
104 y: i32,
105 z: i32,
106}
107
108impl Neg for Vector {
110 type Output = Self;
111
112 fn neg(self) -> Self::Output {
113 Self { x: -self.x, y: -self.y, z: -self.z }
114 }
115}
116
117#[derive(Clone, Copy)]
119struct Face {
120 corner: Point,
121 i: Vector,
122 j: Vector,
123 k: Vector,
124}
125
126pub struct Input {
127 grid: Grid,
128 moves: Vec<Move>,
129}
130
131pub fn parse(input: &str) -> Input {
132 let (prefix, suffix) = input.rsplit_once("\n\n").unwrap();
133 let grid = parse_grid(prefix);
134 let moves = parse_moves(suffix);
135 Input { grid, moves }
136}
137
138pub fn part1(input: &Input) -> i32 {
139 let grid = &input.grid;
140 let block = grid.block;
141
142 let handle_none = |position, direction| {
144 let reverse = direction * -block;
145 let mut next = position + reverse;
146
147 while grid.tile(next) != Tile::None {
148 next += reverse;
149 }
150
151 next += direction;
152 (next, direction)
153 };
154
155 password(input, handle_none)
156}
157
158pub fn part2(input: &Input) -> i32 {
159 let grid = &input.grid;
160 let block = grid.block;
161 let edge = block - 1;
162
163 let start = Face {
165 corner: Point::new(grid.start - grid.start % block, 0),
166 i: Vector { x: 1, y: 0, z: 0 },
167 j: Vector { x: 0, y: 1, z: 0 },
168 k: Vector { x: 0, y: 0, z: 1 },
169 };
170 let mut todo = VecDeque::from([start]);
171 let mut faces = FastMap::build([(start.k, start)]);
172 let mut corners = FastMap::build([(start.corner, start)]);
173
174 while let Some(next) = todo.pop_front() {
175 let Face { corner, i, j, k } = next;
176
177 let neighbors = [
179 Face { corner: corner + Point::new(-block, 0), i: -k, j, k: i }, Face { corner: corner + Point::new(block, 0), i: k, j, k: -i }, Face { corner: corner + Point::new(0, -block), i, j: -k, k: j }, Face { corner: corner + Point::new(0, block), i, j: k, k: -j }, ];
184
185 for next in neighbors {
187 if grid.tile(next.corner) != Tile::None && !faces.contains_key(&next.k) {
188 todo.push_back(next);
189 faces.insert(next.k, next);
190 corners.insert(next.corner, next);
191 }
192 }
193 }
194
195 let handle_none = |position: Point, direction| {
196 let offset = Point::new(position.x % block, position.y % block);
198 let corner = position - offset;
200 let Face { i, j, k, .. } = corners[&corner];
202 let next_k = match direction {
204 LEFT => i,
205 RIGHT => -i,
206 UP => j,
207 DOWN => -j,
208 _ => unreachable!(),
209 };
210 let Face { corner: next_corner, i: next_i, j: next_j, .. } = faces[&next_k];
211 let next_direction = if k == next_i {
215 RIGHT
216 } else if k == -next_i {
217 LEFT
218 } else if k == next_j {
219 DOWN
220 } else if k == -next_j {
221 UP
222 } else {
223 unreachable!()
224 };
225 let next_offset = match (direction, next_direction) {
228 (LEFT, LEFT) => Point::new(edge, offset.y),
229 (LEFT, RIGHT) => Point::new(0, edge - offset.y),
230 (LEFT, DOWN) => Point::new(offset.y, 0),
231 (LEFT, UP) => Point::new(edge - offset.y, edge),
232 (RIGHT, LEFT) => Point::new(edge, edge - offset.y),
233 (RIGHT, RIGHT) => Point::new(0, offset.y),
234 (RIGHT, DOWN) => Point::new(edge - offset.y, 0),
235 (RIGHT, UP) => Point::new(offset.y, edge),
236 (DOWN, LEFT) => Point::new(edge, offset.x),
237 (DOWN, RIGHT) => Point::new(0, edge - offset.x),
238 (DOWN, DOWN) => Point::new(offset.x, 0),
239 (DOWN, UP) => Point::new(edge - offset.x, edge),
240 (UP, LEFT) => Point::new(edge, edge - offset.x),
241 (UP, RIGHT) => Point::new(0, offset.x),
242 (UP, DOWN) => Point::new(edge - offset.x, 0),
243 (UP, UP) => Point::new(offset.x, edge),
244 _ => unreachable!(),
245 };
246 let next_position = next_corner + next_offset;
247 (next_position, next_direction)
248 };
249
250 password(input, handle_none)
251}
252
253fn parse_grid(input: &str) -> Grid {
254 let raw: Vec<_> = input.lines().map(str::as_bytes).collect();
255 let width = raw.iter().map(|line| line.len()).max().unwrap();
257 let height = raw.len();
258 let mut tiles = vec![Tile::None; width * height];
259
260 for (y, row) in raw.iter().enumerate() {
262 for (x, &col) in row.iter().enumerate() {
263 tiles[y * width + x] = match col {
264 b'.' => Tile::Open,
265 b'#' => Tile::Wall,
266 _ => Tile::None,
267 };
268 }
269 }
270
271 let start = tiles.iter().position(|&t| t == Tile::Open).unwrap() as i32;
273 let block = width.gcd(height) as i32;
275 Grid { width, height, tiles, start, block }
276}
277
278fn parse_moves(input: &str) -> Vec<Move> {
279 let mut letters = input.bytes().filter(u8::is_ascii_uppercase);
280 let mut moves = Vec::new();
281
282 for n in input.iter_unsigned() {
284 moves.push(Move::Forward(n));
285
286 if let Some(d) = letters.next() {
287 moves.push(if d == b'L' { Move::Left } else { Move::Right });
288 }
289 }
290
291 moves
292}
293
294fn password(input: &Input, handle_none: impl Fn(Point, Point) -> (Point, Point)) -> i32 {
297 let Input { grid, moves } = input;
298 let mut position = Point::new(grid.start, 0);
299 let mut direction = RIGHT;
300
301 for command in moves {
302 match command {
303 Move::Left => direction = direction.counter_clockwise(),
304 Move::Right => direction = direction.clockwise(),
305 Move::Forward(n) => {
306 for _ in 0..*n {
307 let next = position + direction;
308 match grid.tile(next) {
309 Tile::Wall => break,
311 Tile::Open => position = next,
313 Tile::None => {
314 let (next_position, next_direction) = handle_none(position, direction);
315 if grid.tile(next_position) == Tile::Open {
317 position = next_position;
318 direction = next_direction;
319 } else {
320 break;
321 }
322 }
323 }
324 }
325 }
326 }
327 }
328
329 let position_score = 1000 * (position.y + 1) + 4 * (position.x + 1);
331 let direction_score = match direction {
332 RIGHT => 0,
333 DOWN => 1,
334 LEFT => 2,
335 UP => 3,
336 _ => unreachable!(),
337 };
338 position_score + direction_score
339}