diff --git a/Makefile b/Makefile index 65e37ac..8179a24 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ main: main.c day*.c vec.h aoc.h vec2.h types.h - gcc -o main main.c -lcurl + gcc -o main main.c -lcurl -O3 run: main ./main $(day) diff --git a/day19.c b/day19.c new file mode 100644 index 0000000..4b78ba6 --- /dev/null +++ b/day19.c @@ -0,0 +1,336 @@ +#include +#include +#include +#include + +#include "types.h" +#include "aoc.h" + +typedef enum { + RESOURCE_ORE, + RESOURCE_CLAY, + RESOURCE_OBSIDIAN, + RESOURCE_GEODE, + __RESOURCE_COUNT +} day19_resource; +char *g_day19_resource_str[] = { + [RESOURCE_ORE] = "ore", + [RESOURCE_CLAY] = "clay", + [RESOURCE_OBSIDIAN] = "obsidian", + [RESOURCE_GEODE] = "geode" +}; + +typedef u8 day19_robot_cost[__RESOURCE_COUNT]; + +typedef struct { + day19_robot_cost robot_costs[__RESOURCE_COUNT]; +} day19_blueprint; + +typedef struct { + day19_blueprint *blueprints; + size_t count; +} day19_data; + +static void day19_parse_count_with_resource(char *str, u8 *robot_cost) +{ + char *after_count; + u8 count = strtol(str, &after_count, 10); + char *resource_str = after_count+1; + + day19_resource resource = -1; + for (int i = 0; i < ARRAY_LEN(g_day19_resource_str); i++) { + if (strncmp(resource_str, g_day19_resource_str[i], strlen(g_day19_resource_str[i])) == 0) { + resource = i; + } + } + assert(resource != -1); + + robot_cost[resource] = count; +} + +static void day19_parse_robot_cost(char* sentence, u8 *robot_cost) +{ + char *costs_start = strstr(sentence, "costs")+6; + char *and = strstr(costs_start, "and"); + if (and) { + day19_parse_count_with_resource(costs_start, robot_cost); + day19_parse_count_with_resource(and+4, robot_cost); + } else { + day19_parse_count_with_resource(costs_start, robot_cost); + } +} + +static void* day19_parse(char** lines, int line_count) +{ + day19_data *data = malloc(sizeof(day19_data)); + data->blueprints = calloc(line_count, sizeof(day19_blueprint)); + data->count = line_count; + + for (int i = 0; i < line_count; i++) { + day19_blueprint *bp = &data->blueprints[i]; + char *line = strchr(lines[i], ':')+2; // Skip `Blueprint %d:` part at start + + day19_resource resource = RESOURCE_ORE; + char *tok_save = NULL; + for (char *tok = strtok_r(line, ".", &tok_save); tok; tok = strtok_r(NULL, ".", &tok_save)) { + day19_parse_robot_cost(tok, bp->robot_costs[resource]); + resource++; + if (resource == __RESOURCE_COUNT) break; + } + } + + return data; +} + +static void day19_print_blueprint(day19_blueprint *bp) +{ + printf("Blueprint:\n"); + for (int i = 0; i < __RESOURCE_COUNT; i++) { + printf(" Each %s robot costs", g_day19_resource_str[i]); + bool resource_printed = false; + for (int j = 0; j < __RESOURCE_COUNT; j++) { + u8 count = bp->robot_costs[i][j]; + if (count == 0) continue; + + if (resource_printed) printf(" and"); + printf(" %d %s", bp->robot_costs[i][j], g_day19_resource_str[j]); + resource_printed = true; + } + printf(".\n"); + } +} + +static bool day19_can_buy(day19_robot_cost cost, u32 storage[__RESOURCE_COUNT]) +{ + for (int i = 0; i < __RESOURCE_COUNT; i++) { + if (cost[i] > storage[i]) { + return false; + } + } + return true; +} + +static void day19_subtract_cost(day19_robot_cost cost, u32 storage[__RESOURCE_COUNT]) +{ + for (int i = 0; i < __RESOURCE_COUNT; i++) { + storage[i] -= cost[i]; + } +} + +static u32 div_ceil(u32 a, u32 b) +{ + return a / b + (a % b > 0); +} + +typedef struct { + u16 *bucket_values; + u32 *bucket_keys; + u32 capacity; + u32 count; +} day19_cache; + +static u32 day19_mix_and_mul(u32 key) +{ + // Robert Jenkins' 32 bit Mix Function + key += (key << 12); + key ^= (key >> 22); + key += (key << 4); + key ^= (key >> 9); + key += (key << 10); + key ^= (key >> 2); + key += (key << 7); + key ^= (key >> 12); + + // Knuth's Multiplicative Method + key = (key >> 3) * 2654435761; + + return key; +} + +static u32 day19_hash( + u32 time_left, + u32 robots[__RESOURCE_COUNT], + u32 storage[__RESOURCE_COUNT]) +{ + u32 key = day19_mix_and_mul(time_left); + for (int i = 0; i < __RESOURCE_COUNT; i++) { + key ^= day19_mix_and_mul(robots[i] << 16 | storage[i]); + } + return key; +} +static i16 day19_cache_get( + day19_cache *cache, + u32 time_left, + u32 robots[__RESOURCE_COUNT], + u32 storage[__RESOURCE_COUNT]) +{ + u32 key = day19_hash(time_left, robots, storage); + assert(key != 0 && "Cache key can't be zero"); + for (int i = 0; i < cache->count; i++) { + u32 idx = (key + i) % cache->capacity; + if (cache->bucket_keys[idx] == key) { + return cache->bucket_values[idx]; + } else if (cache->bucket_keys[idx] == 0) { + break; + } + } + return -1; +} + +static void day19_cache_put_with_key(day19_cache *cache, u32 key, u16 value) +{ + assert(cache->count < cache->capacity && "Cache reached capacity"); + + for (int i = 0; i < cache->capacity; i++) { + u32 idx = (key + i) % cache->capacity; + if (cache->bucket_keys[idx] == key) { + assert(false && "Already exists"); + } else if (cache->bucket_keys[idx] == 0) { + cache->bucket_values[idx] = value; + cache->bucket_keys[idx] = key; + cache->count++; + return; + } + } + + assert(false && "Failed to put to cache"); +} + +static void day19_cache_put( + day19_cache *cache, + u32 time_left, + u32 robots[__RESOURCE_COUNT], + u32 storage[__RESOURCE_COUNT], + u16 max_geodes) +{ + u32 key = day19_hash(time_left, robots, storage); + assert(key != 0 && "Cache key can't be zero"); + day19_cache_put_with_key(cache, key, max_geodes); +} + +static void day19_cache_init(day19_cache *cache, u32 initial_capacity) +{ + cache->count = 0; + cache->capacity = initial_capacity; + cache->bucket_keys = calloc(initial_capacity, sizeof(u32)); + cache->bucket_values = calloc(initial_capacity, sizeof(u32)); +} + +static void day19_cache_free(day19_cache *cache) +{ + free(cache->bucket_values); + free(cache->bucket_keys); + cache->bucket_keys = NULL; + cache->bucket_values = NULL; +} + +static u16 day19_max_geodes_dfs( + day19_blueprint *bp, + u32 time_left, + u32 max_spend[__RESOURCE_COUNT], + u32 robots[__RESOURCE_COUNT], + u32 storage[__RESOURCE_COUNT], + day19_cache *cache) +{ + if (time_left == 0) return storage[RESOURCE_GEODE]; + + i32 cached_value = day19_cache_get(cache, time_left, robots, storage); + if (cached_value != -1) return cached_value; + + u16 max_geodes = storage[RESOURCE_GEODE] + robots[RESOURCE_GEODE] * time_left; + + u32 storage_copy[__RESOURCE_COUNT]; + + for (int type = __RESOURCE_COUNT-1; type >= 0; type--) { + if (robots[type] >= max_spend[type] && type != RESOURCE_GEODE) continue; + + bool can_be_built = true; + u32 time_until_robot = 0; + for (int cost_type = 0; cost_type < __RESOURCE_COUNT; cost_type++) { + u8 cost = bp->robot_costs[type][cost_type]; + if (cost == 0) continue; + + if (robots[cost_type] == 0 && storage[cost_type] < cost) { + can_be_built = false; + break; + } + + if (storage[cost_type] <= cost) { + u8 missing = cost - storage[cost_type]; + u32 time_to_resource = div_ceil(missing, robots[cost_type]); + time_until_robot = MAX(time_until_robot, time_to_resource); + } + } + + if (!can_be_built) continue; + + time_until_robot++; + if (time_until_robot > time_left) continue; + + for (int i = 0; i < __RESOURCE_COUNT; i++) { + storage_copy[i] = storage[i]; + storage_copy[i] += robots[i] * time_until_robot; + storage_copy[i] -= bp->robot_costs[type][i]; + } + for (int i = 0; i < __RESOURCE_COUNT-1; i++) { + storage_copy[i] = MIN(storage_copy[i], max_spend[i] * time_left); + } + robots[type]++; + max_geodes = MAX(max_geodes, day19_max_geodes_dfs(bp, time_left - time_until_robot, max_spend, robots, storage_copy, cache)); + robots[type]--; + } + + day19_cache_put(cache, time_left, robots, storage, max_geodes); + + return max_geodes; +} + +static u32 day19_max_geodes(day19_blueprint *bp, u32 time_limit) +{ + u32 max_spend[__RESOURCE_COUNT] = { 0 }; + for (int resource = 0; resource < __RESOURCE_COUNT; resource++) { + for (int robot = 0; robot < __RESOURCE_COUNT; robot++) { + max_spend[resource] = MAX(max_spend[resource], bp->robot_costs[robot][resource]); + } + } + u32 robots[__RESOURCE_COUNT] = { 0 }; + robots[RESOURCE_ORE] = 1; + + u32 storage[__RESOURCE_COUNT] = { 0 }; + + day19_cache cache = { 0 }; + day19_cache_init(&cache, 1024*1024*4); // 4 MiB + u32 answer = day19_max_geodes_dfs(bp, time_limit, max_spend, robots, storage, &cache); + day19_cache_free(&cache); + + return answer; +} + +static void day19_part1(void *p) +{ + day19_data *data = (day19_data*)p; + + u32 time_limit = 24; + + u32 answer = 0; + for (int i = 0; i < data->count; i++) { + u32 max_geodes = day19_max_geodes(&data->blueprints[i], time_limit); + answer += (i+1) * max_geodes; + } + printf("%d\n", answer); +} + +static void day19_part2(void *p) +{ + day19_data *data = (day19_data*)p; + + u32 time_limit = 32; + + u32 answer = 1; + answer *= day19_max_geodes(&data->blueprints[0], time_limit); + answer *= day19_max_geodes(&data->blueprints[1], time_limit); + answer *= day19_max_geodes(&data->blueprints[2], time_limit); + printf("%d\n", answer); +} + +ADD_SOLUTION(19, day19_parse, day19_part1, day19_part2); diff --git a/main.c b/main.c index 0974339..e4110c4 100644 --- a/main.c +++ b/main.c @@ -27,6 +27,7 @@ #include "day16.c" #include "day17.c" #include "day18.c" +#include "day19.c" Solution *find_solution(int day) { diff --git a/types.h b/types.h index 9459789..33dcaec 100644 --- a/types.h +++ b/types.h @@ -16,4 +16,7 @@ typedef int16_t i16; typedef int32_t i32; typedef int64_t i64; +typedef float f32; +typedef double f64; + #endif //TYPES_H_