Skip to content

Commit

Permalink
2024 day 15: solve part 2 (2024 complete!)
Browse files Browse the repository at this point in the history
  • Loading branch information
yut23 committed Jan 30, 2025
1 parent 473e329 commit 6df9899
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 58 deletions.
1 change: 1 addition & 0 deletions 2024/answer_tests/day15/example1.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
Day 15:
2028
1751
1 change: 1 addition & 0 deletions 2024/answer_tests/day15/example2.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
Day 15:
10092
9021
3 changes: 3 additions & 0 deletions 2024/answer_tests/day15/example3.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Day 15:
905
812
9 changes: 9 additions & 0 deletions 2024/input/day15/example3.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
######
#....#
#.O..#
#O.O@#
#.O..#
#....#
######

<<^<>vv<v<^^
19 changes: 13 additions & 6 deletions 2024/src/day15.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,8 @@
#include <fstream> // for ifstream
#include <iostream> // for cout

int main(int argc, char **argv) {
std::ifstream infile = aoc::parse_args(argc, argv).infile;

auto [warehouse, moves] = aoc::day15::read_input(infile);

int simulate_robot(aoc::day15::Warehouse &warehouse,
const std::vector<aoc::AbsDirection> &moves) {
if constexpr (aoc::DEBUG) {
std::cerr << "Initial state:\n" << warehouse << "\n";
}
Expand All @@ -24,8 +21,18 @@ int main(int argc, char **argv) {
std::cerr << "Move " << dir << ":\n" << warehouse << "\n";
}
}
return warehouse.gps_sum();
}

int main(int argc, char **argv) {
std::ifstream infile = aoc::parse_args(argc, argv).infile;

auto [warehouse_1, moves] = aoc::day15::read_input(infile);
// make a wider copy for part 2
auto warehouse_2 = warehouse_1.widen();

std::cout << warehouse.gps_sum() << "\n";
std::cout << simulate_robot(warehouse_1, moves) << "\n";
std::cout << simulate_robot(warehouse_2, moves) << "\n";

return 0;
}
180 changes: 128 additions & 52 deletions 2024/src/day15.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,102 +8,178 @@
#ifndef DAY15_HPP_7IJC3NYE
#define DAY15_HPP_7IJC3NYE

#include "ds/grid.hpp" // for Grid
#include "lib.hpp" // for Pos, Delta, AbsDirection, DEBUG, FAST
#include <cassert> // for assert
#include <iostream> // for istream
#include <optional> // for optional
#include <string> // for string, getline
#include <utility> // for swap, move, pair
#include <vector> // for vector
#include "ds/grid.hpp" // for Grid
#include "unit_test/pretty_print.hpp" // for repr

#include "lib.hpp" // for Pos, Delta, AbsDirection, DEBUG, FAST
#include <cassert> // for assert
#include <functional> // for hash (unordered_set)
#include <iostream> // for istream
#include <optional> // for optional
#include <string> // for string, getline
#include <unordered_set> // for unordered_set
#include <utility> // for swap, move, pair
#include <vector> // for vector

namespace aoc::day15 {

class Warehouse {
aoc::ds::Grid<char> grid;
Pos robot_pos;
Pos robot_pos{-1, -1};

Warehouse(int width, int height) : grid(width, height) {}

std::optional<Pos> find_empty_tile(Pos pos, AbsDirection dir) const;
std::optional<std::vector<Pos>> try_move(const Pos &pos, AbsDirection dir,
bool wide_box = false) const;

public:
explicit Warehouse(const std::vector<std::string> &lines);
Warehouse widen() const;

void move_robot(AbsDirection dir);
int gps_sum() const;

friend std::ostream &operator<<(std::ostream &, const Warehouse &);
};

Warehouse::Warehouse(const std::vector<std::string> &lines)
: grid(lines), robot_pos(-1, -1) {
Warehouse::Warehouse(const std::vector<std::string> &lines) : grid(lines) {
grid.for_each([this](char tile, const Pos &pos) {
if (tile == '@') {
this->robot_pos = pos;
}
});
}

Warehouse Warehouse::widen() const {
Warehouse wider(grid.width * 2, grid.height);
grid.for_each([&wider](char tile, const Pos &pos) {
char &wider_left = wider.grid.at_unchecked(pos.x * 2, pos.y);
char &wider_right = wider.grid.at_unchecked(pos.x * 2 + 1, pos.y);
switch (tile) {
case '#':
wider_left = '#';
wider_right = '#';
break;
case 'O':
wider_left = '[';
wider_right = ']';
break;
case '.':
wider_left = '.';
wider_right = '.';
break;
case '@':
wider_left = '@';
wider_right = '.';
wider.robot_pos = {pos.x * 2, pos.y};
break;
}
});
return wider;
}

/**
* Returns the position directly in front of the robot if it's unoccupied, the
* new position for a pushed box if there's one or more in the way, or an empty
* optional if the robot is blocked in that direction.
* Returns a collection of box positions that need to be updated, in order from
* furthest to nearest, or an empty optional if the robot is blocked in that
* direction
*/
std::optional<Pos> Warehouse::find_empty_tile(Pos pos, AbsDirection dir) const {
std::optional<std::vector<Pos>>
Warehouse::try_move(const Pos &pos, AbsDirection dir, bool wide_box) const {
using pretty_print::repr;
Delta delta{dir, true};
for (pos += delta; grid.in_bounds(pos); pos += delta) {
switch (grid[pos]) {
case '.':
// found an empty tile
return pos;
case '#':
// hit a wall
return {};
Pos new_pos = pos + delta;
if (!grid.in_bounds(new_pos)) {
// move is blocked
return {};
}
char tile = grid[new_pos];
if (tile == '#') {
// move is blocked
return {};
}
if (tile == '.') {
// move is valid
return {{}};
}
if (tile == '[' || tile == ']') {
// treat a wide box as two connected small boxes when moving vertically,
// and recurse on each, setting the wide_box flag so we only do it once
if (delta.dy != 0 && !wide_box) {
Pos left_side, right_side;
if (tile == '[') {
left_side = pos;
right_side = pos + Delta{1, 0};
} else {
left_side = pos + Delta{-1, 0};
right_side = pos;
}
auto positions_left = try_move(left_side, dir, true);
auto positions_right = try_move(right_side, dir, true);
if (positions_left && positions_right) {
// append all values from positions_right into positions_left
positions_left->insert(positions_left->end(),
positions_right->begin(),
positions_right->end());
return positions_left;
} else {
// movement was blocked somewhere
return {};
}
} else {
// this will handle horizontal movements as two unconnected boxes
tile = 'O';
}
}
if (tile == 'O') {
auto positions = try_move(new_pos, dir);
if (positions.has_value()) {
// move is valid, add this position to the update list
positions->push_back(new_pos);
}
return positions;
}
// hit the edge of the map
return {};
std::cerr << "unhandled tile type " << pretty_print::repr(tile) << " at "
<< new_pos << "\n";
assert(false);
}

void Warehouse::move_robot(AbsDirection dir) {
if (auto empty_pos = find_empty_tile(robot_pos, dir)) {
// new position for the robot
Pos new_pos = robot_pos + Delta(dir, true);
if (auto positions_to_move = try_move(robot_pos, dir);
positions_to_move.has_value()) {
Delta delta(dir, true);
if constexpr (aoc::DEBUG) {
std::cerr << "moving robot " << dir << " from " << robot_pos
<< " to " << new_pos;
<< " to " << robot_pos + delta << "; box positions: "
<< pretty_print::repr(*positions_to_move) << "\n";
}
if (grid[new_pos] == 'O') {
// need to move the box to the next empty position
if constexpr (!aoc::FAST) {
assert(grid[*empty_pos] == '.');
// add the robot to the update list, after any boxes
positions_to_move->push_back(robot_pos);
// execute all movements, skipping duplicate positions
std::unordered_set<Pos> already_moved;
for (const Pos &pos : *positions_to_move) {
Pos new_pos = pos + delta;
bool inserted = already_moved.emplace(new_pos).second;
if (inserted) {
if constexpr (!aoc::FAST) {
assert(grid[new_pos] == '.');
}
std::swap(grid[pos], grid[new_pos]);
}
if constexpr (aoc::DEBUG) {
int box_count = (*empty_pos - new_pos).manhattan_distance();
std::cerr << " and pushing " << box_count << " "
<< (box_count > 1 ? "boxes" : "box") << " to "
<< *empty_pos;
}
std::swap(grid[*empty_pos], grid[new_pos]);
}
// nothing in the way, just move the robot
if constexpr (!aoc::FAST) {
assert(grid[new_pos] == '.');
}
std::swap(grid[robot_pos], grid[new_pos]);
robot_pos = new_pos;
// update the robot position
robot_pos += delta;
} else {
if constexpr (aoc::DEBUG) {
std::cerr << "blocked from moving " << dir << " at " << robot_pos;
std::cerr << "blocked from moving " << dir << " at " << robot_pos
<< "\n";
}
}
if constexpr (aoc::DEBUG) {
std::cerr << "\n";
}
}

int Warehouse::gps_sum() const {
int sum = 0;
grid.for_each([&sum](char tile, const Pos &pos) {
if (tile == 'O') {
if (tile == 'O' || tile == '[') {
sum += 100 * pos.y + pos.x;
}
});
Expand Down

0 comments on commit 6df9899

Please sign in to comment.