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;
94 let y = point.y as usize;
95 if (0..self.width).contains(&x) && (0..self.height).contains(&y) {
96 self.tiles[y * self.width + x]
97 } else {
98 Tile::None
99 }
100 }
101}
102
103#[derive(Copy, Clone, Hash, PartialEq, Eq)]
105struct Vector {
106 x: i32,
107 y: i32,
108 z: i32,
109}
110
111impl Neg for Vector {
113 type Output = Self;
114
115 fn neg(self) -> Self::Output {
116 Vector { x: -self.x, y: -self.y, z: -self.z }
117 }
118}
119
120#[derive(Clone, Copy)]
122struct Face {
123 corner: Point,
124 i: Vector,
125 j: Vector,
126 k: Vector,
127}
128
129pub struct Input {
130 grid: Grid,
131 moves: Vec<Move>,
132}
133
134pub fn parse(input: &str) -> Input {
135 let (prefix, suffix) = input.rsplit_once("\n\n").unwrap();
136 let grid = parse_grid(prefix);
137 let moves = parse_moves(suffix);
138 Input { grid, moves }
139}
140
141pub fn part1(input: &Input) -> i32 {
142 let grid = &input.grid;
143 let block = grid.block;
144
145 let handle_none = |position, direction| {
147 let reverse = direction * -block;
148 let mut next = position + reverse;
149
150 while grid.tile(next) != Tile::None {
151 next += reverse;
152 }
153
154 next += direction;
155 (next, direction)
156 };
157
158 password(input, handle_none)
159}
160
161pub fn part2(input: &Input) -> i32 {
162 let grid = &input.grid;
163 let block = grid.block;
164 let edge = block - 1;
165
166 let start = Face {
168 corner: Point::new(grid.start - grid.start % block, 0),
169 i: Vector { x: 1, y: 0, z: 0 },
170 j: Vector { x: 0, y: 1, z: 0 },
171 k: Vector { x: 0, y: 0, z: 1 },
172 };
173 let mut todo = VecDeque::from([start]);
174 let mut faces = FastMap::build([(start.k, start)]);
175 let mut corners = FastMap::build([(start.corner, start)]);
176
177 while let Some(next) = todo.pop_front() {
178 let Face { corner, i, j, k } = next;
179
180 let neighbors = [
182 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 }, ];
187
188 for next in neighbors {
190 if grid.tile(next.corner) != Tile::None && !faces.contains_key(&next.k) {
191 todo.push_back(next);
192 faces.insert(next.k, next);
193 corners.insert(next.corner, next);
194 }
195 }
196 }
197
198 let handle_none = |position: Point, direction| {
199 let offset = Point::new(position.x % block, position.y % block);
201 let corner = position - offset;
203 let Face { i, j, k, .. } = corners[&corner];
205 let next_k = match direction {
207 LEFT => i,
208 RIGHT => -i,
209 UP => j,
210 DOWN => -j,
211 _ => unreachable!(),
212 };
213 let Face { corner: next_corner, i: next_i, j: next_j, .. } = faces[&next_k];
214 let next_direction = if k == next_i {
218 RIGHT
219 } else if k == -next_i {
220 LEFT
221 } else if k == next_j {
222 DOWN
223 } else if k == -next_j {
224 UP
225 } else {
226 unreachable!()
227 };
228 let next_offset = match (direction, next_direction) {
231 (LEFT, LEFT) => Point::new(edge, offset.y),
232 (LEFT, RIGHT) => Point::new(0, edge - offset.y),
233 (LEFT, DOWN) => Point::new(offset.y, 0),
234 (LEFT, UP) => Point::new(edge - offset.y, edge),
235 (RIGHT, LEFT) => Point::new(edge, edge - offset.y),
236 (RIGHT, RIGHT) => Point::new(0, offset.y),
237 (RIGHT, DOWN) => Point::new(edge - offset.y, 0),
238 (RIGHT, UP) => Point::new(offset.y, edge),
239 (DOWN, LEFT) => Point::new(edge, offset.x),
240 (DOWN, RIGHT) => Point::new(0, edge - offset.x),
241 (DOWN, DOWN) => Point::new(offset.x, 0),
242 (DOWN, UP) => Point::new(edge - offset.x, edge),
243 (UP, LEFT) => Point::new(edge, edge - offset.x),
244 (UP, RIGHT) => Point::new(0, offset.x),
245 (UP, DOWN) => Point::new(edge - offset.x, 0),
246 (UP, UP) => Point::new(offset.x, edge),
247 _ => unreachable!(),
248 };
249 let next_position = next_corner + next_offset;
250 (next_position, next_direction)
251 };
252
253 password(input, handle_none)
254}
255
256fn parse_grid(input: &str) -> Grid {
257 let raw: Vec<_> = input.lines().map(str::as_bytes).collect();
258 let width = raw.iter().map(|line| line.len()).max().unwrap();
260 let height = raw.len();
261 let mut tiles = vec![Tile::None; width * height];
262
263 for (y, row) in raw.iter().enumerate() {
265 for (x, col) in row.iter().enumerate() {
266 let tile = match col {
267 b'.' => Tile::Open,
268 b'#' => Tile::Wall,
269 _ => Tile::None,
270 };
271 tiles[y * width + x] = tile;
272 }
273 }
274
275 let start = tiles.iter().position(|&t| t == Tile::Open).unwrap() as i32;
277 let block = width.gcd(height) as i32;
279 Grid { width, height, tiles, start, block }
280}
281
282fn parse_moves(input: &str) -> Vec<Move> {
283 let mut moves = Vec::new();
284 let mut numbers = input.iter_unsigned();
285 let mut letters = input.bytes().filter(u8::is_ascii_uppercase);
286
287 loop {
289 let Some(n) = numbers.next() else {
290 break;
291 };
292 moves.push(Move::Forward(n));
293
294 let Some(d) = letters.next() else {
295 break;
296 };
297 moves.push(if d == b'L' { Move::Left } else { Move::Right });
298 }
299
300 moves
301}
302
303fn password(input: &Input, handle_none: impl Fn(Point, Point) -> (Point, Point)) -> i32 {
306 let Input { grid, moves } = input;
307 let mut position = Point::new(grid.start, 0);
308 let mut direction = Point::new(1, 0);
309
310 for command in moves {
311 match command {
312 Move::Left => direction = direction.counter_clockwise(),
313 Move::Right => direction = direction.clockwise(),
314 Move::Forward(n) => {
315 for _ in 0..*n {
316 let next = position + direction;
317 match grid.tile(next) {
318 Tile::Wall => break,
320 Tile::Open => position = next,
322 Tile::None => {
323 let (next_position, next_direction) = handle_none(position, direction);
324 if grid.tile(next_position) == Tile::Open {
326 position = next_position;
327 direction = next_direction;
328 } else {
329 break;
330 }
331 }
332 }
333 }
334 }
335 }
336 }
337
338 let position_score = 1000 * (position.y + 1) + 4 * (position.x + 1);
340 let direction_score = match direction {
341 RIGHT => 0,
342 DOWN => 1,
343 LEFT => 2,
344 UP => 3,
345 _ => unreachable!(),
346 };
347 position_score + direction_score
348}