-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0b2c59e
commit 1241082
Showing
5 changed files
with
313 additions
and
0 deletions.
There are no files selected for viewing
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
|
||
--- Day 17: Clumsy Crucible --- | ||
|
||
The lava starts flowing rapidly once the Lava Production Facility is operational. As you leave, the reindeer offers you a parachute, allowing you to quickly reach Gear Island. | ||
|
||
As you descend, your bird's-eye view of Gear Island reveals why you had trouble finding anyone on your way up: half of Gear Island is empty, but the half below you is a giant factory city! | ||
|
||
You land near the gradually-filling pool of lava at the base of your new lavafall. Lavaducts will eventually carry the lava throughout the city, but to make use of it immediately, Elves are loading it into large crucibles on wheels. | ||
|
||
The crucibles are top-heavy and pushed by hand. Unfortunately, the crucibles become very difficult to steer at high speeds, and so it can be hard to go in a straight line for very long. | ||
|
||
To get Desert Island the machine parts it needs as soon as possible, you'll need to find the best way to get the crucible from the lava pool to the machine parts factory. To do this, you need to minimize heat loss while choosing a route that doesn't require the crucible | ||
to go in a straight line for too long. | ||
|
||
Fortunately, the Elves here have a map (your puzzle input) that uses traffic patterns, ambient temperature, and hundreds of other parameters to calculate exactly how much heat loss can be expected for a crucible entering any particular city block. | ||
|
||
For example: | ||
|
||
2413432311323 | ||
3215453535623 | ||
3255245654254 | ||
3446585845452 | ||
4546657867536 | ||
1438598798454 | ||
4457876987766 | ||
3637877979653 | ||
4654967986887 | ||
4564679986453 | ||
1224686865563 | ||
2546548887735 | ||
4322674655533 | ||
|
||
Each city block is marked by a single digit that represents the amount of heat loss if the crucible enters that block. The starting point, the lava pool, is the top-left city block; the destination, the machine parts factory, is the bottom-right city block. (Because you | ||
already start in the top-left block, you don't incur that block's heat loss unless you leave that block and then return to it.) | ||
|
||
Because it is difficult to keep the top-heavy crucible going in a straight line for very long, it can move at most three blocks in a single direction before it must turn 90 degrees left or right. The crucible also can't reverse direction; after entering each city block, | ||
it may only turn left, continue straight, or turn right. | ||
|
||
One way to minimize heat loss is this path: | ||
|
||
correct until row 0, col 5, then no neighbours found?! | ||
|
||
2>>34^>>>1323 | ||
32v>>>35v5623 | ||
32552456v>>54 | ||
3446585845v52 | ||
4546657867v>6 | ||
14385987984v4 | ||
44578769877v6 | ||
36378779796v> | ||
465496798688v | ||
456467998645v | ||
12246868655<v | ||
25465488877v5 | ||
43226746555v> | ||
|
||
This path never moves more than three consecutive blocks in the same direction and incurs a heat loss of only 102. | ||
|
||
Directing the crucible from the lava pool to the machine parts factory, but not moving more than three consecutive blocks in the same direction, what is the least heat loss it can incur? | ||
|
||
To begin, get your puzzle input. | ||
|
||
Answer: | ||
|
||
You can also [Shareon Twitter Mastodon] this puzzle. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
/* | ||
* Author: Michael Adler | ||
* | ||
* Copyright: 2023 Michael Adler | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
#include "solve.h" | ||
#include "aoc/all.h" | ||
|
||
#define GRID_SIZE 256 | ||
#define INFINITY 1 << 30; | ||
|
||
static int grid[GRID_SIZE][GRID_SIZE]; | ||
static int rows = 0, cols = 0; | ||
|
||
/* Dijkstra state */ | ||
static int dist[GRID_SIZE][GRID_SIZE]; | ||
static Point2D prev[GRID_SIZE][GRID_SIZE]; | ||
|
||
typedef struct { | ||
int x, y; | ||
char previous[3]; | ||
int previous_count; | ||
} QueueEntry; | ||
|
||
typedef struct { | ||
int x, y; | ||
char dir; | ||
} Neighbor; | ||
|
||
#define P | ||
#define T QueueEntry | ||
#include <lst.h> | ||
|
||
static void add_point(Neighbor *neighbor, int *neighbor_count, Neighbor p) { | ||
if (p.x < 0 || p.x >= cols || p.y < 0 || p.y >= rows) return; | ||
neighbor[*neighbor_count] = p; | ||
*neighbor_count = *neighbor_count + 1; | ||
} | ||
|
||
void solve(char *buf, size_t buf_size, Solution *result) { | ||
int part1 = 0, part2 = 0; | ||
|
||
for (int pos = 0, x = 0; pos < (int)buf_size; pos++) { | ||
char c = buf[pos]; | ||
switch (c) { | ||
case '\n': | ||
cols = x; | ||
x = 0; | ||
rows++; | ||
break; | ||
default: grid[rows][x++] = c - '0'; break; | ||
} | ||
} | ||
log_debug("rows: %d, cols: %d", rows, cols); | ||
|
||
// Dijkstra | ||
_cleanup_(lst_QueueEntry_free) lst_QueueEntry queue = lst_QueueEntry_init(); | ||
|
||
Point2D undefined = {.x = -1, .y = -1}; | ||
for (int y = 0; y < rows; y++) { | ||
for (int x = 0; x < cols; x++) { | ||
dist[y][x] = INFINITY; | ||
prev[y][x] = undefined; | ||
lst_QueueEntry_push_back(&queue, (QueueEntry){.x = x, .y = y}); | ||
} | ||
} | ||
dist[0][0] = 0; | ||
|
||
Point2D dest = {.x = cols - 1, .y = rows - 1}; | ||
|
||
while (!lst_QueueEntry_empty(&queue)) { | ||
// find min | ||
lst_QueueEntry_node *node = NULL; | ||
int best_d = INFINITY; | ||
foreach (lst_QueueEntry, &queue, it) { | ||
int d = dist[it.ref->y][it.ref->x]; | ||
if (d < best_d) { | ||
best_d = d; | ||
node = it.node; | ||
} | ||
} | ||
QueueEntry entry = node->value; | ||
lst_QueueEntry_erase(&queue, node); | ||
|
||
int dist_entry = dist[entry.y][entry.x]; | ||
|
||
if (entry.x == dest.x && entry.y == dest.y) { | ||
log_debug("found shortest path to dest: %d", dist_entry); | ||
part1 = dist_entry; | ||
break; | ||
} | ||
|
||
bool must_turn = entry.previous_count == 3 && entry.previous[0] == entry.previous[1] && | ||
entry.previous[1] == entry.previous[2]; | ||
log_debug(">> visiting row %d, col %d, dist: %d, must_turn: %d, previous: %.*s", entry.y, entry.x, dist_entry, | ||
must_turn, entry.previous_count, entry.previous); | ||
// check neighbors | ||
Neighbor neighbor[4]; | ||
int neighbor_count = 0; | ||
char direction; | ||
if (entry.previous_count > 0) { | ||
direction = entry.previous[entry.previous_count - 1]; | ||
} else { | ||
direction = '\0'; | ||
} | ||
|
||
Neighbor top = {.x = entry.x, .y = entry.y - 1, .dir = '^'}; | ||
Neighbor bottom = {.x = entry.x, .y = entry.y + 1, .dir = 'v'}; | ||
Neighbor right = {.x = entry.x + 1, .y = entry.y, .dir = '>'}; | ||
Neighbor left = {.x = entry.x - 1, .y = entry.y, .dir = '<'}; | ||
switch (direction) { | ||
case '>': | ||
add_point(neighbor, &neighbor_count, top); | ||
add_point(neighbor, &neighbor_count, bottom); | ||
if (!must_turn) { add_point(neighbor, &neighbor_count, right); } | ||
break; | ||
case '<': | ||
add_point(neighbor, &neighbor_count, top); | ||
add_point(neighbor, &neighbor_count, bottom); | ||
if (!must_turn) { add_point(neighbor, &neighbor_count, left); } | ||
break; | ||
case 'v': | ||
add_point(neighbor, &neighbor_count, left); | ||
add_point(neighbor, &neighbor_count, right); | ||
if (!must_turn) { add_point(neighbor, &neighbor_count, bottom); } | ||
break; | ||
case '^': | ||
add_point(neighbor, &neighbor_count, left); | ||
add_point(neighbor, &neighbor_count, right); | ||
if (!must_turn) { add_point(neighbor, &neighbor_count, top); } | ||
break; | ||
default: | ||
add_point(neighbor, &neighbor_count, top); | ||
add_point(neighbor, &neighbor_count, bottom); | ||
add_point(neighbor, &neighbor_count, left); | ||
add_point(neighbor, &neighbor_count, right); | ||
break; | ||
} | ||
log_debug("found %d neighbors", neighbor_count); | ||
for (int i = 0; i < neighbor_count; i++) { | ||
Neighbor nb = neighbor[i]; | ||
int alt = dist_entry + grid[nb.y][nb.x]; | ||
int old = dist[nb.y][nb.x]; | ||
log_debug("checking neighbor in row %d, col %d, dir %c, alt %d, old %d", nb.y, nb.x, nb.dir, alt, old); | ||
if (alt < old) { | ||
log_debug("!!! found new best dist"); | ||
dist[nb.y][nb.x] = alt; | ||
prev[nb.y][nb.x] = (Point2D){.x = entry.x, .y = entry.y}; | ||
|
||
foreach (lst_QueueEntry, &queue, it) { | ||
if (it.ref->x == nb.x && it.ref->y == nb.y) { | ||
if (entry.previous_count == 0) { | ||
it.ref->previous_count = 1; | ||
it.ref->previous[0] = nb.dir; | ||
} else if (entry.previous_count == 1) { | ||
it.ref->previous[0] = entry.previous[0]; | ||
it.ref->previous[1] = nb.dir; | ||
it.ref->previous_count = 2; | ||
} else if (entry.previous_count == 2) { | ||
it.ref->previous[0] = entry.previous[0]; | ||
it.ref->previous[1] = entry.previous[1]; | ||
it.ref->previous[2] = nb.dir; | ||
it.ref->previous_count = 3; | ||
} else if (entry.previous_count == 3) { | ||
it.ref->previous[0] = entry.previous[1]; | ||
it.ref->previous[1] = entry.previous[2]; | ||
it.ref->previous[2] = nb.dir; | ||
it.ref->previous_count = 3; | ||
} | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
Point2D path[1024]; | ||
int path_count = 0; | ||
Point2D tmp = dest; | ||
while (tmp.x != undefined.x && tmp.y != undefined.y) { | ||
path[path_count++] = tmp; | ||
tmp = prev[tmp.y][tmp.x]; | ||
} | ||
|
||
for (int i = path_count - 1; i >= 0; i--) { printf("row=%d, col=%d\n", path[i].y, path[i].x); } | ||
|
||
aoc_itoa(part1, result->part1, 10); | ||
aoc_itoa(part2, result->part2, 10); | ||
} | ||
|
||
int solve_input(const char *fname, Solution *result) { | ||
char buf[1 << 15]; | ||
int n = aoc_io_read_input(fname, buf, sizeof(buf)); | ||
if (n <= 0) { | ||
fprintf(stderr, "Failed to read %s\n", fname); | ||
return -1; | ||
} | ||
solve(buf, n, result); | ||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/* | ||
* Author: Michael Adler | ||
* | ||
* Copyright: 2023 Michael Adler | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
#define CTEST_MAIN | ||
|
||
#include "ctest.h" | ||
#include "solve.h" | ||
|
||
CTEST(day17, example) { | ||
char *buf = "2413432311323\n\ | ||
3215453535623\n\ | ||
3255245654254\n\ | ||
3446585845452\n\ | ||
4546657867536\n\ | ||
1438598798454\n\ | ||
4457876987766\n\ | ||
3637877979653\n\ | ||
4654967986887\n\ | ||
4564679986453\n\ | ||
1224686865563\n\ | ||
2546548887735\n\ | ||
4322674655533\n"; | ||
Solution solution; | ||
solve(buf, strlen(buf), &solution); | ||
ASSERT_STR("102", solution.part1); | ||
// ASSERT_STR("0", solution.part2); | ||
} | ||
|
||
#ifdef HAVE_INPUTS | ||
CTEST_SKIP(day17, real) { | ||
Solution solution; | ||
solve_input("input/" DAY ".txt", &solution); | ||
ASSERT_STR("0", solution.part1); | ||
// ASSERT_STR("0", solution.part2); | ||
} | ||
#endif | ||
|
||
int main(int argc, const char *argv[]) { return ctest_main(argc, argv); } |