Skip to content

Commit

Permalink
[2024] Day 15 initial solution
Browse files Browse the repository at this point in the history
  • Loading branch information
connorslade committed Dec 15, 2024
1 parent 3b5690a commit f97266c
Show file tree
Hide file tree
Showing 3 changed files with 274 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Thank you to [Eric Wastl](http://was.tl) for running this incredible yearly even
- [Day 12: Garden Groups](aoc_2024/src/day_12.rs)
- [Day 13: Claw Contraption](aoc_2024/src/day_13.rs)
- [Day 14: Restroom Redoubt](aoc_2024/src/day_14.rs)
- [Day 15: Warehouse Woes](aoc_2024/src/day_15.rs)
<!-- MARKER -->

## [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)
Expand Down
271 changes: 271 additions & 0 deletions aoc_2024/src/day_15.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
use std::fmt::{Debug, Write};

use aoc_lib::{direction::cardinal::Direction, matrix::Matrix};
use common::{solution, Answer};
use nd_vec::{vector, Vec2};

solution!("Warehouse Woes", 15);

fn part_a(input: &str) -> Answer {
let mut problem = Problem::parse(input, false);
problem.tick_all(false);
problem.score().into()
}

fn part_b(input: &str) -> Answer {
let mut problem = Problem::parse(input, true);
problem.tick_all(true);
problem.score().into()
}

struct Problem {
pos: Vec2<usize>,
idx: usize,

board: Matrix<Tile>,
instructions: Vec<Direction>,
}

#[derive(PartialEq, Eq, Clone, Copy)]
enum Tile {
Robot,
Wall,
Box,
BoxRight,
Empty,
}

impl Problem {
fn parse(input: &str, part_b: bool) -> Self {
let (board, instructions) = input.split_once("\n\n").unwrap();
let mut board = Matrix::new_chars(board, |chr| match chr {
'@' => Tile::Robot,
'#' => Tile::Wall,
'O' => Tile::Box,
'.' => Tile::Empty,
_ => panic!(),
});

if part_b {
board.size = vector!(board.size.x() * 2, board.size.y());
let mut new = Vec::new();
for data in board.data {
new.push(data);
new.push(match data {
Tile::Box => Tile::BoxRight,
Tile::Robot => Tile::Empty,
_ => data,
});
}
board.data = new;
}

let instructions = instructions
.chars()
.filter_map(|x| {
Some(match x {
'<' => Direction::Left,
'>' => Direction::Right,
'^' => Direction::Up,
'v' => Direction::Down,
_ => return None,
})
})
.collect::<Vec<_>>();

let pos = board.find(Tile::Robot).unwrap();
board.set(pos, Tile::Empty);

Self {
pos,
idx: 0,

board,
instructions,
}
}

fn tick_all(&mut self, part_b: bool) {
for _ in 0..self.instructions.len() {
self.tick(part_b);
}
}

fn tick(&mut self, part_b: bool) {
let dir = self.instructions[self.idx];
self.idx += 1;

let new = dir.advance(self.pos);
if {
if part_b {
self.push_b(new, dir)
} else {
self.push(new, dir)
}
} {
self.pos = new;
}
}

// -> was successful
fn push(&mut self, pos: Vec2<usize>, dir: Direction) -> bool {
// if we are air, return true
let value = self.board[pos];
match value {
Tile::Empty => return true,
Tile::Wall => return false,
_ => {}
}

// if where we want to move is full, try to move that
let new = dir.wrapping_advance(pos);
if !self.board.contains(new) {
return false;
}

if self.board[new] == Tile::Empty || self.push(new, dir) {
self.board.set(new, value);
self.board.set(pos, Tile::Empty);
true
} else {
false
}
}

fn can_push(&self, pos: Vec2<usize>, dir: Direction) -> bool {
// println!("{pos:?}, {dir:?}");
let value = self.board[pos];
match value {
Tile::Empty => return true,
Tile::Wall => return false,
Tile::Box | Tile::BoxRight => {}
Tile::Robot => unreachable!(),
}

let other_box = match value {
Tile::Box => pos + vector!(1, 0),
Tile::BoxRight => pos - vector!(1, 0),
_ => unreachable!(),
};

// if where we want to move is full, try to move that
let new_a = dir.wrapping_advance(pos);
let new_b = dir.wrapping_advance(other_box);
if !(self.board.contains(new_a) && self.board.contains(new_b)) {
return false;
}

(self.board[new_a] == Tile::Empty && self.board[new_b] == Tile::Empty)
|| ((new_a == other_box || self.can_push(new_a, dir))
&& (new_b == pos || self.can_push(new_b, dir)))
}

fn push_b(&mut self, pos: Vec2<usize>, dir: Direction) -> bool {
if self.can_push(pos, dir) {
let value = self.board[pos];
if value == Tile::Empty {
return true;
}

assert!(matches!(value, Tile::Box | Tile::BoxRight));
let other_box = match value {
Tile::Box => pos + vector!(1, 0),
Tile::BoxRight => pos - vector!(1, 0),
_ => unreachable!(),
};
let other_value = self.board[other_box];

let new_a = dir.wrapping_advance(pos);
let new_b = dir.wrapping_advance(other_box);
if !(self.board.contains(new_a) && self.board.contains(new_b)) {
return false;
}

if (self.board[new_a] == Tile::Empty && self.board[new_b] == Tile::Empty)
|| ((new_a == other_box || self.push_b(new_a, dir))
&& (new_b == pos || self.push_b(new_b, dir)))
{
// do push
self.board.set(new_a, value);
self.board.set(pos, Tile::Empty);

self.board.set(new_b, other_value);
if other_box != new_a {
self.board.set(other_box, Tile::Empty);
}
true
} else {
false
}
} else {
false
}
}

fn score(&self) -> u32 {
let mut score = 0;

for (pos, _) in self.board.iter().filter(|x| *x.1 == Tile::Box) {
score += (100 * pos.y() + pos.x()) as u32;
}

score
}

fn debug(&self) {
let mut dbg = self.board.clone();
dbg.set(self.pos, Tile::Robot);
println!("{:?}", dbg);
}
}

impl Debug for Tile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Tile::Robot => f.write_char('@'),
Tile::Wall => f.write_char('#'),
Tile::Box => f.write_char('['),
Tile::BoxRight => f.write_char(']'),
Tile::Empty => f.write_char('.'),
}
}
}

#[cfg(test)]
mod test {
use indoc::indoc;

const CASE: &str = indoc! {"
##########
#..O..O.O#
#......O.#
#.OO..O.O#
#..O@..O.#
#O#..O...#
#O..O..O.#
#.OO.O.OO#
#....O...#
##########
<vv>^<v^>v>^vv^v>v<>v^v<v<^vv<<<^><<><>>v<vvv<>^v^>^<<<><<v<<<v^vv^v>^
vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<<v<^v>^<^^>>>^<v<v
><>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^<v>v^^<^^vv<
<<v<^>>^^^^>>>v^<>vvv^><v<<<>^^^vv^<vvv>^>v<^^^^v<>^>vvvv><>>v^<<^^^^^
^><^><>>><>^^<<^^v>>><^<v>^<vv>>v>>>^v><>^v><<<<v>>v<v<v>vvv>^<><<>^><
^>><>^v<><^vvv<^^<><v<<<<<><^v<<<><<<^^<v<^^^><^>>^<v^><<<^>>^v<v^v<v^
>^>>^v>vv>^<<^v<>><<><<v<<v><>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^
<><^^>^^^<><vvvvv^v<v<<>^v<v>v<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<>
^^>vv<^v^v<vv>^<><v<^v>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<><<v>
v^^>>><<^^<>>^v^<v^vv<>v^<<>^<^v^v><^<<<><<^<v><v<>vv>>v><v^<vv<>v^<<^
"};

#[test]
fn part_a() {
assert_eq!(super::part_a(CASE), 10092.into());
}

#[test]
fn part_b() {
assert_eq!(super::part_b(CASE), 9021.into());
}
}
2 changes: 2 additions & 0 deletions aoc_2024/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod day_11;
mod day_12;
mod day_13;
mod day_14;
mod day_15;
// [import_marker]

pub const SOLUTIONS: &[Solution] = &[
Expand All @@ -31,5 +32,6 @@ pub const SOLUTIONS: &[Solution] = &[
day_12::SOLUTION,
day_13::SOLUTION,
day_14::SOLUTION,
day_15::SOLUTION,
// [list_marker]
];

0 comments on commit f97266c

Please sign in to comment.