Skip to content

Commit 4c6da48

Browse files
committed
2024 day 15
1 parent d075301 commit 4c6da48

File tree

6 files changed

+255
-0
lines changed

6 files changed

+255
-0
lines changed

crates/utils/src/grid.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,35 @@ pub fn from_str_padded<T: Clone>(
142142

143143
Ok((rows, padded_columns, data))
144144
}
145+
146+
/// Checks that the provided grid has walls on each edge.
147+
///
148+
/// # Examples
149+
/// ```
150+
/// # use utils::grid::is_enclosed;
151+
/// assert_eq!(
152+
/// is_enclosed(5, 6, &[
153+
/// b'#', b'#', b'#', b'#', b'#', b'#',
154+
/// b'#', b'.', b'.', b'.', b'.', b'#',
155+
/// b'#', b'.', b'.', b'.', b'.', b'#',
156+
/// b'#', b'.', b'.', b'.', b'.', b'#',
157+
/// b'#', b'#', b'#', b'#', b'#', b'#',
158+
/// ], |&b| b == b'#'),
159+
/// true,
160+
/// );
161+
/// assert_eq!(
162+
/// is_enclosed(5, 6, &[
163+
/// b'#', b'#', b'#', b'#', b'#', b'#',
164+
/// b'#', b'.', b'.', b'.', b'.', b'#',
165+
/// b'#', b'.', b'.', b'.', b'.', b'#',
166+
/// b'#', b'.', b'.', b'.', b'.', b'.',
167+
/// b'#', b'#', b'#', b'#', b'#', b'#',
168+
/// ], |&b| b == b'#'),
169+
/// false,
170+
/// );
171+
/// ```
172+
pub fn is_enclosed<T>(rows: usize, cols: usize, grid: &[T], is_wall: impl Fn(&T) -> bool) -> bool {
173+
grid[..cols].iter().all(&is_wall)
174+
&& grid[(rows - 1) * cols..].iter().all(&is_wall)
175+
&& (1..rows).all(|r| is_wall(&grid[r * cols]) && is_wall(&grid[(r + 1) * cols - 1]))
176+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
##########
2+
#..O..O.O#
3+
#......O.#
4+
#.OO..O.O#
5+
6+
#O#..O...#
7+
#O..O..O.#
8+
#.OO.O.OO#
9+
#....O...#
10+
##########
11+
12+
<vv>^<v^>v>^vv^v>v<>v^v<v<^vv<<<^><<><>>v<vvv<>^v^>^<<<><<v<<<v^vv^v>^
13+
vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<<v<^v>^<^^>>>^<v<v
14+
><>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^<v>v^^<^^vv<
15+
<<v<^>>^^^^>>>v^<>vvv^><v<<<>^^^vv^<vvv>^>v<^^^^v<>^>vvvv><>>v^<<^^^^^
16+
^><^><>>><>^^<<^^v>>><^<v>^<vv>>v>>>^v><>^v><<<<v>>v<v<v>vvv>^<><<>^><
17+
^>><>^v<><^vvv<^^<><v<<<<<><^v<<<><<<^^<v<^^^><^>>^<v^><<<^>>^v<v^v<v^
18+
>^>>^v>vv>^<<^v<>><<><<v<<v><>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^
19+
<><^^>^^^<><vvvvv^v<v<<>^v<v>v<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<>
20+
^^>vv<^v^v<vv>^<><v<^v>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<><<v>
21+
v^^>>><<^^<>>^v^<v^vv<>v^<<>^<^v^v><^<<<><<^<v><v<>vv>>v><v^<vv<>v^<<^
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
########
2+
#..O.O.#
3+
##@.O..#
4+
#...O..#
5+
#.#.O..#
6+
#...O..#
7+
#......#
8+
########
9+
10+
<^^>>>vv<v>>v<<
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#######
2+
#...#.#
3+
#.....#
4+
#..OO@#
5+
#..O..#
6+
#.....#
7+
#######
8+
9+
<vv<<^^<<^^

crates/year2024/src/day15.rs

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
use utils::grid;
2+
use utils::prelude::*;
3+
4+
/// Moving boxes around a grid.
5+
#[derive(Clone, Debug)]
6+
pub struct Day15<'a> {
7+
cols: usize,
8+
grid: Vec<u8>,
9+
robot: usize,
10+
moves: &'a str,
11+
}
12+
13+
impl<'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+
// One box directly above/below, only need to recurse once
149+
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+
178+
examples!(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+
]);

crates/year2024/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ utils::year!(2024 => year2024, ${
1616
12 => day12::Day12,
1717
13 => day13::Day13,
1818
14 => day14::Day14,
19+
15 => day15::Day15<'_>,
1920
});

0 commit comments

Comments
 (0)