1use utils::grid;
2use utils::prelude::*;
3
4#[derive(Clone, Debug)]
6pub struct Day15<'a> {
7 cols: usize,
8 grid: Vec<u8>,
9 robot: usize,
10 moves: &'a str,
11}
12
13impl<'a> Day15<'a> {
14 pub fn new(input: &'a str, _: InputType) -> Result<Self, InputError> {
15 let Some((grid, moves)) = input.split_once("\n\n") else {
16 return Err(InputError::new(input, 0, "expected grid and moves"));
17 };
18
19 let (rows, cols, mut grid) = grid::from_str(grid, |b| match b {
20 b'.' | b'#' | b'O' | b'@' => Some(b),
21 _ => None,
22 })?;
23 if !grid::is_enclosed(rows, cols, &grid, |&b| b == b'#') {
24 return Err(InputError::new(
25 input,
26 0,
27 "expected grid to be enclosed by walls",
28 ));
29 }
30
31 let mut robots = grid.iter().enumerate().filter(|&(_, &b)| b == b'@');
32 let Some((robot, _)) = robots.next() else {
33 return Err(InputError::new(input, 0, "expected a robot"));
34 };
35 if robots.count() > 0 {
36 return Err(InputError::new(input, 0, "expected only one robot"));
37 }
38 grid[robot] = b'.';
39
40 if let Some(idx) = moves.find(|b| !matches!(b, '^' | 'v' | '<' | '>' | '\n')) {
41 return Err(InputError::new(
42 input,
43 &moves[idx..],
44 "expected ^, v, <, or >",
45 ));
46 }
47
48 Ok(Self {
49 cols,
50 grid,
51 robot,
52 moves,
53 })
54 }
55
56 #[must_use]
57 pub fn part1(&self) -> u32 {
58 let mut grid = self.grid.clone();
59 let mut robot = self.robot;
60
61 for offset in self.moves_iterator(self.cols) {
62 let next = robot.wrapping_add_signed(offset);
63 if grid[next] == b'.' {
64 robot = next;
65 } else if grid[next] == b'O' {
66 let mut next_free = next.wrapping_add_signed(offset);
67 while grid[next_free] == b'O' {
68 next_free = next_free.wrapping_add_signed(offset);
69 }
70 if grid[next_free] == b'.' {
71 grid[next_free] = b'O';
72 grid[next] = b'.';
73 robot = next;
74 }
75 }
76 }
77
78 Self::sum_box_coords(&grid, self.cols, b'O')
79 }
80
81 #[must_use]
82 pub fn part2(&self) -> u32 {
83 let mut grid = self
84 .grid
85 .iter()
86 .flat_map(|&b| match b {
87 b'#' => [b'#', b'#'],
88 b'.' => [b'.', b'.'],
89 b'O' => [b'[', b']'],
90 _ => unreachable!(),
91 })
92 .collect::<Vec<_>>();
93 let cols = self.cols * 2;
94 let mut robot = (self.robot / self.cols * cols) + (self.robot % self.cols * 2);
95
96 for offset in self.moves_iterator(cols) {
97 let next = robot.wrapping_add_signed(offset);
98 if grid[next] == b'.' {
99 robot = next;
100 } else if (grid[next] == b'[' || grid[next] == b']')
101 && Self::can_move_p2(&grid, next, offset)
102 {
103 Self::move_box_p2(&mut grid, next, offset);
104 robot = next;
105 }
106 }
107
108 Self::sum_box_coords(&grid, cols, b'[')
109 }
110
111 #[inline]
112 fn moves_iterator(&self, cols: usize) -> impl Iterator<Item = isize> + use<'_> {
113 self.moves.bytes().filter_map(move |c| match c {
114 b'^' => Some(-(cols as isize)),
115 b'v' => Some(cols as isize),
116 b'<' => Some(-1),
117 b'>' => Some(1),
118 _ => None,
119 })
120 }
121
122 #[inline]
123 fn sum_box_coords(grid: &[u8], cols: usize, box_byte: u8) -> u32 {
124 grid.iter()
125 .enumerate()
126 .map(|(i, &b)| {
127 if b == box_byte {
128 100 * ((i / cols) as u32) + ((i % cols) as u32)
129 } else {
130 0
131 }
132 })
133 .sum()
134 }
135
136 fn can_move_p2(grid: &[u8], pos: usize, offset: isize) -> bool {
137 let (left, right) = match grid[pos] {
138 b'[' => (pos, pos + 1),
139 b']' => (pos - 1, pos),
140 b'.' => return true,
141 b'#' => return false,
142 _ => unreachable!(),
143 };
144
145 if offset == -1 || offset == 1 {
146 Self::can_move_p2(grid, pos.wrapping_add_signed(offset), offset)
147 } else if grid[left.wrapping_add_signed(offset)] == b'[' {
148 Self::can_move_p2(grid, left.wrapping_add_signed(offset), offset)
150 } else {
151 Self::can_move_p2(grid, left.wrapping_add_signed(offset), offset)
152 && Self::can_move_p2(grid, right.wrapping_add_signed(offset), offset)
153 }
154 }
155
156 fn move_box_p2(grid: &mut [u8], pos: usize, offset: isize) {
157 let (left, right) = match grid[pos] {
158 b'[' => (pos, pos + 1),
159 b']' => (pos - 1, pos),
160 b'.' => return,
161 _ => unreachable!(),
162 };
163
164 if offset == -1 || offset == 1 {
165 Self::move_box_p2(grid, pos.wrapping_add_signed(offset * 2), offset);
166 } else {
167 Self::move_box_p2(grid, left.wrapping_add_signed(offset), offset);
168 Self::move_box_p2(grid, right.wrapping_add_signed(offset), offset);
169 }
170
171 grid[left] = b'.';
172 grid[right] = b'.';
173 grid[left.wrapping_add_signed(offset)] = b'[';
174 grid[right.wrapping_add_signed(offset)] = b']';
175 }
176}
177
178examples!(Day15<'_> -> (u32, u32) [
179 {file: "day15_example0.txt", part1: 10092, part2: 9021},
180 {file: "day15_example1.txt", part1: 2028},
181 {file: "day15_example2.txt", part2: 618},
182]);