Skip to content

Commit f97266c

Browse files
committed
[2024] Day 15 initial solution
1 parent 3b5690a commit f97266c

File tree

3 files changed

+274
-0
lines changed

3 files changed

+274
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Thank you to [Eric Wastl](http://was.tl) for running this incredible yearly even
1919
- [Day 12: Garden Groups](aoc_2024/src/day_12.rs)
2020
- [Day 13: Claw Contraption](aoc_2024/src/day_13.rs)
2121
- [Day 14: Restroom Redoubt](aoc_2024/src/day_14.rs)
22+
- [Day 15: Warehouse Woes](aoc_2024/src/day_15.rs)
2223
<!-- MARKER -->
2324

2425
## [2023](https://adventofcode.com/2023) [![aoc_2023](https://github.com/connorslade/advent-of-code/actions/workflows/aoc_2023.yml/badge.svg)](https://github.com/connorslade/advent-of-code/actions/workflows/aoc_2023.yml)

aoc_2024/src/day_15.rs

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
use std::fmt::{Debug, Write};
2+
3+
use aoc_lib::{direction::cardinal::Direction, matrix::Matrix};
4+
use common::{solution, Answer};
5+
use nd_vec::{vector, Vec2};
6+
7+
solution!("Warehouse Woes", 15);
8+
9+
fn part_a(input: &str) -> Answer {
10+
let mut problem = Problem::parse(input, false);
11+
problem.tick_all(false);
12+
problem.score().into()
13+
}
14+
15+
fn part_b(input: &str) -> Answer {
16+
let mut problem = Problem::parse(input, true);
17+
problem.tick_all(true);
18+
problem.score().into()
19+
}
20+
21+
struct Problem {
22+
pos: Vec2<usize>,
23+
idx: usize,
24+
25+
board: Matrix<Tile>,
26+
instructions: Vec<Direction>,
27+
}
28+
29+
#[derive(PartialEq, Eq, Clone, Copy)]
30+
enum Tile {
31+
Robot,
32+
Wall,
33+
Box,
34+
BoxRight,
35+
Empty,
36+
}
37+
38+
impl Problem {
39+
fn parse(input: &str, part_b: bool) -> Self {
40+
let (board, instructions) = input.split_once("\n\n").unwrap();
41+
let mut board = Matrix::new_chars(board, |chr| match chr {
42+
'@' => Tile::Robot,
43+
'#' => Tile::Wall,
44+
'O' => Tile::Box,
45+
'.' => Tile::Empty,
46+
_ => panic!(),
47+
});
48+
49+
if part_b {
50+
board.size = vector!(board.size.x() * 2, board.size.y());
51+
let mut new = Vec::new();
52+
for data in board.data {
53+
new.push(data);
54+
new.push(match data {
55+
Tile::Box => Tile::BoxRight,
56+
Tile::Robot => Tile::Empty,
57+
_ => data,
58+
});
59+
}
60+
board.data = new;
61+
}
62+
63+
let instructions = instructions
64+
.chars()
65+
.filter_map(|x| {
66+
Some(match x {
67+
'<' => Direction::Left,
68+
'>' => Direction::Right,
69+
'^' => Direction::Up,
70+
'v' => Direction::Down,
71+
_ => return None,
72+
})
73+
})
74+
.collect::<Vec<_>>();
75+
76+
let pos = board.find(Tile::Robot).unwrap();
77+
board.set(pos, Tile::Empty);
78+
79+
Self {
80+
pos,
81+
idx: 0,
82+
83+
board,
84+
instructions,
85+
}
86+
}
87+
88+
fn tick_all(&mut self, part_b: bool) {
89+
for _ in 0..self.instructions.len() {
90+
self.tick(part_b);
91+
}
92+
}
93+
94+
fn tick(&mut self, part_b: bool) {
95+
let dir = self.instructions[self.idx];
96+
self.idx += 1;
97+
98+
let new = dir.advance(self.pos);
99+
if {
100+
if part_b {
101+
self.push_b(new, dir)
102+
} else {
103+
self.push(new, dir)
104+
}
105+
} {
106+
self.pos = new;
107+
}
108+
}
109+
110+
// -> was successful
111+
fn push(&mut self, pos: Vec2<usize>, dir: Direction) -> bool {
112+
// if we are air, return true
113+
let value = self.board[pos];
114+
match value {
115+
Tile::Empty => return true,
116+
Tile::Wall => return false,
117+
_ => {}
118+
}
119+
120+
// if where we want to move is full, try to move that
121+
let new = dir.wrapping_advance(pos);
122+
if !self.board.contains(new) {
123+
return false;
124+
}
125+
126+
if self.board[new] == Tile::Empty || self.push(new, dir) {
127+
self.board.set(new, value);
128+
self.board.set(pos, Tile::Empty);
129+
true
130+
} else {
131+
false
132+
}
133+
}
134+
135+
fn can_push(&self, pos: Vec2<usize>, dir: Direction) -> bool {
136+
// println!("{pos:?}, {dir:?}");
137+
let value = self.board[pos];
138+
match value {
139+
Tile::Empty => return true,
140+
Tile::Wall => return false,
141+
Tile::Box | Tile::BoxRight => {}
142+
Tile::Robot => unreachable!(),
143+
}
144+
145+
let other_box = match value {
146+
Tile::Box => pos + vector!(1, 0),
147+
Tile::BoxRight => pos - vector!(1, 0),
148+
_ => unreachable!(),
149+
};
150+
151+
// if where we want to move is full, try to move that
152+
let new_a = dir.wrapping_advance(pos);
153+
let new_b = dir.wrapping_advance(other_box);
154+
if !(self.board.contains(new_a) && self.board.contains(new_b)) {
155+
return false;
156+
}
157+
158+
(self.board[new_a] == Tile::Empty && self.board[new_b] == Tile::Empty)
159+
|| ((new_a == other_box || self.can_push(new_a, dir))
160+
&& (new_b == pos || self.can_push(new_b, dir)))
161+
}
162+
163+
fn push_b(&mut self, pos: Vec2<usize>, dir: Direction) -> bool {
164+
if self.can_push(pos, dir) {
165+
let value = self.board[pos];
166+
if value == Tile::Empty {
167+
return true;
168+
}
169+
170+
assert!(matches!(value, Tile::Box | Tile::BoxRight));
171+
let other_box = match value {
172+
Tile::Box => pos + vector!(1, 0),
173+
Tile::BoxRight => pos - vector!(1, 0),
174+
_ => unreachable!(),
175+
};
176+
let other_value = self.board[other_box];
177+
178+
let new_a = dir.wrapping_advance(pos);
179+
let new_b = dir.wrapping_advance(other_box);
180+
if !(self.board.contains(new_a) && self.board.contains(new_b)) {
181+
return false;
182+
}
183+
184+
if (self.board[new_a] == Tile::Empty && self.board[new_b] == Tile::Empty)
185+
|| ((new_a == other_box || self.push_b(new_a, dir))
186+
&& (new_b == pos || self.push_b(new_b, dir)))
187+
{
188+
// do push
189+
self.board.set(new_a, value);
190+
self.board.set(pos, Tile::Empty);
191+
192+
self.board.set(new_b, other_value);
193+
if other_box != new_a {
194+
self.board.set(other_box, Tile::Empty);
195+
}
196+
true
197+
} else {
198+
false
199+
}
200+
} else {
201+
false
202+
}
203+
}
204+
205+
fn score(&self) -> u32 {
206+
let mut score = 0;
207+
208+
for (pos, _) in self.board.iter().filter(|x| *x.1 == Tile::Box) {
209+
score += (100 * pos.y() + pos.x()) as u32;
210+
}
211+
212+
score
213+
}
214+
215+
fn debug(&self) {
216+
let mut dbg = self.board.clone();
217+
dbg.set(self.pos, Tile::Robot);
218+
println!("{:?}", dbg);
219+
}
220+
}
221+
222+
impl Debug for Tile {
223+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224+
match self {
225+
Tile::Robot => f.write_char('@'),
226+
Tile::Wall => f.write_char('#'),
227+
Tile::Box => f.write_char('['),
228+
Tile::BoxRight => f.write_char(']'),
229+
Tile::Empty => f.write_char('.'),
230+
}
231+
}
232+
}
233+
234+
#[cfg(test)]
235+
mod test {
236+
use indoc::indoc;
237+
238+
const CASE: &str = indoc! {"
239+
##########
240+
#..O..O.O#
241+
#......O.#
242+
#.OO..O.O#
243+
#..O@..O.#
244+
#O#..O...#
245+
#O..O..O.#
246+
#.OO.O.OO#
247+
#....O...#
248+
##########
249+
250+
<vv>^<v^>v>^vv^v>v<>v^v<v<^vv<<<^><<><>>v<vvv<>^v^>^<<<><<v<<<v^vv^v>^
251+
vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<<v<^v>^<^^>>>^<v<v
252+
><>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^<v>v^^<^^vv<
253+
<<v<^>>^^^^>>>v^<>vvv^><v<<<>^^^vv^<vvv>^>v<^^^^v<>^>vvvv><>>v^<<^^^^^
254+
^><^><>>><>^^<<^^v>>><^<v>^<vv>>v>>>^v><>^v><<<<v>>v<v<v>vvv>^<><<>^><
255+
^>><>^v<><^vvv<^^<><v<<<<<><^v<<<><<<^^<v<^^^><^>>^<v^><<<^>>^v<v^v<v^
256+
>^>>^v>vv>^<<^v<>><<><<v<<v><>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^
257+
<><^^>^^^<><vvvvv^v<v<<>^v<v>v<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<>
258+
^^>vv<^v^v<vv>^<><v<^v>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<><<v>
259+
v^^>>><<^^<>>^v^<v^vv<>v^<<>^<^v^v><^<<<><<^<v><v<>vv>>v><v^<vv<>v^<<^
260+
"};
261+
262+
#[test]
263+
fn part_a() {
264+
assert_eq!(super::part_a(CASE), 10092.into());
265+
}
266+
267+
#[test]
268+
fn part_b() {
269+
assert_eq!(super::part_b(CASE), 9021.into());
270+
}
271+
}

aoc_2024/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ mod day_11;
1414
mod day_12;
1515
mod day_13;
1616
mod day_14;
17+
mod day_15;
1718
// [import_marker]
1819

1920
pub const SOLUTIONS: &[Solution] = &[
@@ -31,5 +32,6 @@ pub const SOLUTIONS: &[Solution] = &[
3132
day_12::SOLUTION,
3233
day_13::SOLUTION,
3334
day_14::SOLUTION,
35+
day_15::SOLUTION,
3436
// [list_marker]
3537
];

0 commit comments

Comments
 (0)