1use crate::input::Input;
2
3pub fn solve(input: &Input) -> Result<u64, String> {
4 let (direction_str, mut cube) =
5 Cube::parse(input.text, input.is_part_two()).ok_or("Invalid input")?;
6
7 let mut direction = Direction::Right;
8 let mut steps_forward = 0;
9 for c in direction_str.bytes() {
10 if c.is_ascii_digit() {
11 if steps_forward > 10 {
12 return Err("Too many steps in a direction".to_string());
13 }
14 steps_forward = steps_forward * 10 + (c - b'0');
15 } else {
16 direction = cube.step_forward(steps_forward, direction);
17 match c {
18 b'R' => {
19 direction = direction.turn_right();
20 }
21 b'L' => {
22 direction = direction.turn_left();
23 }
24 _ => {
25 return Err("Strange direction".to_string());
26 }
27 }
28 steps_forward = 0;
29 }
30 }
31 if steps_forward != 0 {
32 direction = cube.step_forward(steps_forward, direction);
33 }
34
35 Ok(cube.password(direction))
36}
37
38#[derive(Copy, Clone)]
39#[allow(clippy::type_complexity)]
40struct CubeSide {
41 grid: [bool; Self::SIZE * Self::SIZE],
42 adjacent_sides: (
45 (usize, Direction),
46 (usize, Direction),
47 (usize, Direction),
48 (usize, Direction),
49 ),
50 grid_position: (usize, usize),
51}
52
53impl CubeSide {
54 const SIZE: usize = 50;
55
56 const fn is_free(&self, x: i32, y: i32) -> bool {
57 if x < 0 || x >= (Self::SIZE as i32) || y < 0 || y >= (Self::SIZE as i32) {
58 return false;
59 }
60 !self.grid[y as usize * Self::SIZE + x as usize]
61 }
62
63 const fn set_wall(&mut self, x: usize, y: usize) {
64 self.grid[y * Self::SIZE + x] = true;
65 }
66}
67
68struct Cube {
69 sides: [CubeSide; 6],
70 current_cube_idx: usize,
71 current_position: (i32, i32),
72}
73
74impl Cube {
75 fn parse(input: &str, fold_cube: bool) -> Option<(&str, Self)> {
76 let mut sides = [CubeSide {
77 grid: [false; CubeSide::SIZE * CubeSide::SIZE],
78 grid_position: (usize::MAX, usize::MAX),
79 adjacent_sides: (
80 (usize::MAX, Direction::Right),
81 (usize::MAX, Direction::Right),
82 (usize::MAX, Direction::Right),
83 (usize::MAX, Direction::Right),
84 ),
85 }; 6];
86
87 let mut parts = input.split("\n\n");
88 let cubes_part = parts.next()?;
89 let directions_part = parts.next()?;
90
91 let mut seen_sides = 0;
92 for (row_idx, line) in cubes_part.lines().enumerate() {
93 let line_start = line.find(['.', '#'])?;
94 let line_end = line.rfind(['.', '#'])?;
95 let line_end_inclusive = line_end + 1;
96 if line_start % CubeSide::SIZE != 0 || line_end_inclusive % CubeSide::SIZE != 0 {
97 return None;
98 }
99
100 let sides_on_line = (line_end_inclusive - line_start) / CubeSide::SIZE;
101
102 for (col_idx, &c) in line.as_bytes()[line_start..line_end_inclusive]
103 .iter()
104 .enumerate()
105 {
106 if c == b'#' {
107 let cube_offset = col_idx / CubeSide::SIZE;
108 sides[seen_sides + cube_offset].set_wall(col_idx % 50, row_idx % 50);
109 }
110 }
111
112 if row_idx % CubeSide::SIZE == 49 {
113 for i in 0..sides_on_line {
114 sides[seen_sides + i].grid_position =
115 (line_start / CubeSide::SIZE + i, row_idx / CubeSide::SIZE);
116 }
117 seen_sides += sides_on_line;
118 }
119 }
120
121 if sides.iter().any(|side| side.grid_position.0 == usize::MAX) {
122 return None;
123 }
124
125 if fold_cube {
126 sides[0].adjacent_sides = (
147 (5, Direction::Right),
148 (1, Direction::Right),
149 (2, Direction::Down),
150 (3, Direction::Right),
151 );
152 sides[1].adjacent_sides = (
153 (5, Direction::Up),
154 (4, Direction::Left),
155 (2, Direction::Left),
156 (0, Direction::Left),
157 );
158 sides[2].adjacent_sides = (
159 (0, Direction::Up),
160 (1, Direction::Up),
161 (4, Direction::Down),
162 (3, Direction::Down),
163 );
164 sides[3].adjacent_sides = (
165 (2, Direction::Right),
166 (4, Direction::Right),
167 (5, Direction::Down),
168 (0, Direction::Right),
169 );
170 sides[4].adjacent_sides = (
171 (2, Direction::Up),
172 (1, Direction::Left),
173 (5, Direction::Left),
174 (3, Direction::Left),
175 );
176 sides[5].adjacent_sides = (
177 (3, Direction::Up),
178 (4, Direction::Up),
179 (1, Direction::Down),
180 (0, Direction::Down),
181 );
182 } else {
183 sides[0].adjacent_sides = (
184 (4, Direction::Up),
185 (1, Direction::Right),
186 (2, Direction::Down),
187 (1, Direction::Left),
188 );
189 sides[1].adjacent_sides = (
190 (1, Direction::Up),
191 (0, Direction::Right),
192 (1, Direction::Down),
193 (0, Direction::Left),
194 );
195 sides[2].adjacent_sides = (
196 (0, Direction::Up),
197 (2, Direction::Right),
198 (4, Direction::Down),
199 (2, Direction::Left),
200 );
201 sides[3].adjacent_sides = (
202 (5, Direction::Up),
203 (4, Direction::Right),
204 (5, Direction::Down),
205 (4, Direction::Left),
206 );
207 sides[4].adjacent_sides = (
208 (2, Direction::Up),
209 (3, Direction::Right),
210 (0, Direction::Down),
211 (3, Direction::Left),
212 );
213 sides[5].adjacent_sides = (
214 (3, Direction::Up),
215 (5, Direction::Right),
216 (3, Direction::Down),
217 (5, Direction::Left),
218 );
219 }
220 Some((
221 directions_part,
222 Self {
223 sides,
224 current_cube_idx: 0,
225 current_position: (0, 0),
226 },
227 ))
228 }
229
230 fn step_forward(&mut self, steps: u8, mut direction: Direction) -> Direction {
231 #![allow(clippy::panic)]
232 #![allow(clippy::useless_let_if_seq)]
233 let mut delta = direction.delta();
234 for _step in 0..steps {
235 let mut new_position = (
236 self.current_position.0 + delta.0,
237 self.current_position.1 + delta.1,
238 );
239 let mut new_cube_idx = self.current_cube_idx;
240 let mut new_direction = direction;
241
242 if let Some((c, facing_direction)) = if new_position.0 < 0 {
243 Some(self.sides[self.current_cube_idx].adjacent_sides.3)
244 } else if new_position.0 == CubeSide::SIZE as i32 {
245 Some(self.sides[self.current_cube_idx].adjacent_sides.1)
246 } else if new_position.1 < 0 {
247 Some(self.sides[self.current_cube_idx].adjacent_sides.0)
248 } else if new_position.1 == CubeSide::SIZE as i32 {
249 Some(self.sides[self.current_cube_idx].adjacent_sides.2)
250 } else {
251 None
252 } {
253 new_cube_idx = c;
254 new_position = match (direction, facing_direction) {
255 (Direction::Up, Direction::Up) => (new_position.0, (CubeSide::SIZE - 1) as i32),
256 (Direction::Up, Direction::Right) => (0, new_position.0),
257 (Direction::Right, Direction::Up) => {
258 (new_position.1, (CubeSide::SIZE - 1) as i32)
259 }
260 (Direction::Right, Direction::Right) => (0, new_position.1),
261 (Direction::Right, Direction::Left) => (
262 (CubeSide::SIZE - 1) as i32,
263 (CubeSide::SIZE - 1) as i32 - new_position.1,
264 ),
265 (Direction::Left, Direction::Right) => {
266 (0, (CubeSide::SIZE - 1) as i32 - new_position.1)
267 }
268 (Direction::Left, Direction::Down) => (new_position.1, 0),
269 (Direction::Left, Direction::Left) => {
270 ((CubeSide::SIZE - 1) as i32, new_position.1)
271 }
272 (Direction::Down, Direction::Down) => (new_position.0, 0),
273 (Direction::Down, Direction::Left) => {
274 ((CubeSide::SIZE - 1) as i32, new_position.0)
275 }
276 _ => {
277 panic!("Unhandled combo");
278 }
279 };
280 new_direction = facing_direction;
281 }
282
283 if self.sides[new_cube_idx].is_free(new_position.0, new_position.1) {
284 self.current_position = new_position;
285 self.current_cube_idx = new_cube_idx;
286 direction = new_direction;
287 delta = direction.delta();
288 } else {
289 break;
290 }
291 }
292 direction
293 }
294
295 const fn password(&self, direction: Direction) -> u64 {
296 let (side_x, side_y) = self.sides[self.current_cube_idx].grid_position;
297 let (offset_x, offset_y) = (side_x * CubeSide::SIZE, side_y * CubeSide::SIZE);
298 1000 * (self.current_position.1 as u64 + 1 + offset_y as u64)
299 + 4 * (self.current_position.0 as u64 + 1 + offset_x as u64)
300 + (direction as u64)
301 }
302}
303
304#[derive(Copy, Clone)]
306enum Direction {
307 Right = 0,
308 Down = 1,
309 Left = 2,
310 Up = 3,
311}
312
313impl Direction {
314 const fn turn_right(self) -> Self {
315 match self {
316 Self::Up => Self::Right,
317 Self::Right => Self::Down,
318 Self::Down => Self::Left,
319 Self::Left => Self::Up,
320 }
321 }
322
323 const fn turn_left(self) -> Self {
324 match self {
325 Self::Up => Self::Left,
326 Self::Left => Self::Down,
327 Self::Down => Self::Right,
328 Self::Right => Self::Up,
329 }
330 }
331 const fn delta(self) -> (i32, i32) {
332 match self {
333 Self::Up => (0, -1),
334 Self::Right => (1, 0),
335 Self::Down => (0, 1),
336 Self::Left => (-1, 0),
337 }
338 }
339}
340
341#[test]
342pub fn tests() {
343 let real_input = include_str!("day22_input.txt");
344 test_part_one!(real_input => 89_224);
345 test_part_two!(real_input => 136_182);
346
347 let real_input = include_str!("day22_input_other.txt");
348 test_part_one!(real_input => 76_332);
349 test_part_two!(real_input => 144_012);
350
351 test_part_one_error!("\n\n" => "Invalid input");
352}