-
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 d7aebf5
Showing
5 changed files
with
361 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,112 @@ | ||
|
||
--- 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: | ||
|
||
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? | ||
|
||
Your puzzle answer was 861. | ||
|
||
The first half of this puzzle is complete! It provides one gold star: * | ||
|
||
--- Part Two --- | ||
|
||
The crucibles of lava simply aren't large enough to provide an adequate supply of lava to the machine parts factory. Instead, the Elves are going to upgrade to ultra crucibles. | ||
|
||
Ultra crucibles are even more difficult to steer than normal crucibles. Not only do they have trouble going in a straight line, but they also have trouble turning! | ||
|
||
Once an ultra crucible starts moving in a direction, it needs to move a minimum of four blocks in that direction before it can turn (or even before it can stop at the end). However, it will eventually start to get wobbly: an ultra crucible can move a maximum of ten consecutive blocks without turning. | ||
|
||
In the above example, an ultra crucible could follow this path to minimize heat loss: | ||
|
||
2>>>>>>>>1323 | ||
32154535v5623 | ||
32552456v4254 | ||
34465858v5452 | ||
45466578v>>>> | ||
143859879845v | ||
445787698776v | ||
363787797965v | ||
465496798688v | ||
456467998645v | ||
122468686556v | ||
254654888773v | ||
432267465553v | ||
|
||
In the above example, an ultra crucible would incur the minimum possible heat loss of 94. | ||
|
||
Here's another example: | ||
|
||
111111111111 | ||
999999999991 | ||
999999999991 | ||
999999999991 | ||
999999999991 | ||
|
||
Sadly, an ultra crucible would need to take an unfortunate path like this one: | ||
|
||
1>>>>>>>1111 | ||
9999999v9991 | ||
9999999v9991 | ||
9999999v9991 | ||
9999999v>>>> | ||
|
||
This route causes the ultra crucible to incur the minimum possible heat loss of 71. | ||
|
||
Directing the ultra crucible from the lava pool to the machine parts factory, what is the least heat loss it can incur? | ||
|
||
Answer: | ||
|
||
Although it hasn't changed, you can still get your puzzle input. | ||
|
||
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,205 @@ | ||
/* | ||
* 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 INT_MAX | ||
|
||
typedef enum { NORTH, EAST, SOUTH, WEST } direction_t; | ||
|
||
typedef struct { | ||
Point2D position; | ||
direction_t direction; // current direction | ||
int direction_count; // how many times we went into the current direction | ||
} Node; | ||
|
||
typedef struct { | ||
Node node; | ||
int dist; | ||
} State; | ||
|
||
#define P | ||
#define T State | ||
#include <pqu.h> | ||
|
||
typedef struct { | ||
Node node; // key | ||
int distance; // value | ||
} NodeDistance; | ||
|
||
#define P | ||
#define T NodeDistance | ||
#include <ust.h> | ||
|
||
// min queue | ||
static int State_compare(State *lhs, State *rhs) { return lhs->dist < rhs->dist; } | ||
|
||
static size_t Node_hash(Node *node) { | ||
const size_t prime = 31; | ||
size_t hash = 17; | ||
// Combine the hash of each struct member | ||
hash = hash * prime + (size_t)node->position.x; | ||
hash = hash * prime + (size_t)node->position.y; | ||
hash = hash * prime + (size_t)node->direction; | ||
hash = hash * prime + (size_t)node->direction_count; | ||
return hash; | ||
} | ||
static int Node_equal(Node *lhs, Node *rhs) { | ||
return Point2D_equal(&lhs->position, &rhs->position) && lhs->direction == rhs->direction && | ||
lhs->direction_count == rhs->direction_count; | ||
} | ||
|
||
static size_t NodeDistance_hash(NodeDistance *item) { return Node_hash(&item->node); } | ||
|
||
static int NodeDistance_equal(NodeDistance *lhs, NodeDistance *rhs) { return Node_equal(&lhs->node, &rhs->node); } | ||
|
||
static inline void add_node(int rows, int cols, Node node, Node *arr, int *arr_len) { | ||
if (node.position.x < 0 || node.position.x >= cols || node.position.y < 0 || node.position.y >= rows) { return; } | ||
int count = *arr_len; | ||
arr[count++] = node; | ||
*arr_len = count; | ||
} | ||
|
||
static inline int find_neighbors(int rows, int cols, Node current, Node *neighbor) { | ||
bool must_turn = current.direction_count == 3; | ||
log_debug(">> visiting %d,%d, must_turn: %d, dir: %d, dir_count: %d", current.position.y, current.position.x, | ||
must_turn, current.direction, current.direction_count); | ||
|
||
int count = 0; | ||
Node north = {.position = (Point2D){.x = current.position.x, .y = current.position.y - 1}, | ||
.direction = NORTH, | ||
.direction_count = 1}; | ||
Node south = {.position = (Point2D){.x = current.position.x, .y = current.position.y + 1}, | ||
.direction = SOUTH, | ||
.direction_count = 1}; | ||
Node east = {.position = (Point2D){.x = current.position.x + 1, .y = current.position.y}, | ||
.direction = EAST, | ||
.direction_count = 1}; | ||
Node west = {.position = (Point2D){.x = current.position.x - 1, .y = current.position.y}, | ||
.direction = WEST, | ||
.direction_count = 1}; | ||
switch (current.direction) { | ||
case EAST: | ||
add_node(rows, cols, north, neighbor, &count); | ||
add_node(rows, cols, south, neighbor, &count); | ||
if (!must_turn) { | ||
east.direction_count = current.direction_count + 1; | ||
add_node(rows, cols, east, neighbor, &count); | ||
} | ||
break; | ||
case WEST: | ||
add_node(rows, cols, north, neighbor, &count); | ||
add_node(rows, cols, south, neighbor, &count); | ||
if (!must_turn) { | ||
west.direction_count = current.direction_count + 1; | ||
add_node(rows, cols, west, neighbor, &count); | ||
} | ||
break; | ||
case SOUTH: | ||
add_node(rows, cols, west, neighbor, &count); | ||
add_node(rows, cols, east, neighbor, &count); | ||
if (!must_turn) { | ||
south.direction_count = current.direction_count + 1; | ||
add_node(rows, cols, south, neighbor, &count); | ||
} | ||
break; | ||
case NORTH: | ||
add_node(rows, cols, west, neighbor, &count); | ||
add_node(rows, cols, east, neighbor, &count); | ||
if (!must_turn) { | ||
north.direction_count = current.direction_count + 1; | ||
add_node(rows, cols, north, neighbor, &count); | ||
} | ||
break; | ||
} | ||
return count; | ||
} | ||
|
||
void solve(char *buf, size_t buf_size, Solution *result) { | ||
int part1 = 0, part2 = 0; | ||
|
||
int grid[GRID_SIZE][GRID_SIZE]; | ||
int rows = 0, cols = 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); | ||
|
||
Point2D start = {.x = 0, .y = 0}, dest = {.x = cols - 1, .y = rows - 1}; | ||
Node start_node_1 = {.position = start, .direction = EAST, .direction_count = 0}; | ||
Node start_node_2 = {.position = start, .direction = SOUTH, .direction_count = 0}; | ||
|
||
// Dijkstra | ||
_cleanup_(pqu_State_free) pqu_State queue = pqu_State_init(State_compare); | ||
pqu_State_push(&queue, (State){.node = start_node_1, .dist = 0}); | ||
pqu_State_push(&queue, (State){.node = start_node_2, .dist = 0}); | ||
|
||
_cleanup_(ust_NodeDistance_free) ust_NodeDistance distances = | ||
ust_NodeDistance_init(NodeDistance_hash, NodeDistance_equal); | ||
ust_NodeDistance_insert(&distances, (NodeDistance){.node = start_node_1, .distance = 0}); | ||
ust_NodeDistance_insert(&distances, (NodeDistance){.node = start_node_2, .distance = 0}); | ||
|
||
// dijkstra | ||
while (!pqu_State_empty(&queue)) { | ||
State current = *pqu_State_top(&queue); | ||
pqu_State_pop(&queue); | ||
|
||
if (Point2D_equal(¤t.node.position, &dest)) { | ||
log_debug("found destination %d,%d: %d", current.node.position.y, current.node.position.x, current.dist); | ||
part1 = current.dist; | ||
break; | ||
} | ||
|
||
Node neighbor[3]; | ||
int neighbor_count = find_neighbors(rows, cols, current.node, neighbor); | ||
|
||
for (int i = 0; i < neighbor_count; i++) { | ||
Node nb = neighbor[i]; | ||
int alt = current.dist + grid[nb.position.y][nb.position.x]; | ||
NodeDistance node_distance = {.node = nb, .distance = alt}; | ||
ust_NodeDistance_node *entry = ust_NodeDistance_find(&distances, node_distance); | ||
if (entry != NULL) { | ||
if (entry->key.distance <= alt) { | ||
log_debug("discarding y,x %d,%d: current best %d is better than %d", nb.position.y, nb.position.x, | ||
entry->key.distance, alt); | ||
continue; | ||
} | ||
} else { | ||
log_debug("entry %d,%d not found in map", node_distance.node.position.y, node_distance.node.position.x); | ||
} | ||
log_debug("inserting new best dist for y,x %d,%d: %d", nb.position.y, nb.position.x, alt); | ||
ust_NodeDistance_insert(&distances, node_distance); | ||
pqu_State_push(&queue, (State){.node = nb, .dist = alt}); | ||
} | ||
} | ||
|
||
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(day17, real) { | ||
Solution solution; | ||
solve_input("input/" DAY ".txt", &solution); | ||
ASSERT_STR("861", solution.part1); | ||
// ASSERT_STR("0", solution.part2); | ||
} | ||
#endif | ||
|
||
int main(int argc, const char *argv[]) { return ctest_main(argc, argv); } |