Skip to main content

advent_of_code/year2022/
day22.rs

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    // (top, right, left, bottom)
43    // The direction is the direction one facing the side on, in map view.
44    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            // See https://www.reddit.com/r/adventofcode/comments/zsct8w/comment/j17s6l5/
127            // for description of programmatic approach.
128            //
129            //       ┌─────┬─────┐
130            //       │  A  │  E  │
131            //       │D 0 B│B 1 F│
132            //       │  C  │  G  │
133            //       ├─────┼─────┘
134            //       │  C  │
135            //       │I 2 G│
136            //       │  H  │
137            // ┌─────┼─────┤
138            // │  I  │  H  │
139            // │D 3 J│J 4 F│
140            // │  K  │  L  │
141            // ├─────┼─────┘
142            // │  K  │
143            // │A 5 L│
144            // │  E  │
145            // └─────┘
146            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/// "Facing is 0 for right (>), 1 for down (v), 2 for left (<), and 3 for up (^)."
305#[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}