diff --git a/userspace/aoc2023/CMakeLists.txt b/userspace/aoc2023/CMakeLists.txt index f10e8d4f..1bb94e84 100644 --- a/userspace/aoc2023/CMakeLists.txt +++ b/userspace/aoc2023/CMakeLists.txt @@ -19,6 +19,7 @@ set(AOC2023_PROJECTS day14 day15 day16 + day17 full ) diff --git a/userspace/aoc2023/day17/CMakeLists.txt b/userspace/aoc2023/day17/CMakeLists.txt new file mode 100644 index 00000000..f43e3047 --- /dev/null +++ b/userspace/aoc2023/day17/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.26) + +project(aoc2023_day17 CXX) + +set(SOURCES + main.cpp +) + +add_executable(aoc2023_day17 ${SOURCES}) +target_compile_options(aoc2023_day17 PUBLIC -O2 -g) +target_link_libraries(aoc2023_day17 PUBLIC libc ban) + +add_dependencies(aoc2023_day17 libc-install ban-install) + +add_custom_target(aoc2023_day17-install + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/aoc2023_day17 ${BANAN_AOC2023_BIN}/day17 + DEPENDS aoc2023_day17 + DEPENDS aoc2023_always +) + +add_dependencies(aoc2023 aoc2023_day17) +add_dependencies(aoc2023-install aoc2023_day17-install) diff --git a/userspace/aoc2023/day17/a.out b/userspace/aoc2023/day17/a.out new file mode 100755 index 00000000..0b089d1d Binary files /dev/null and b/userspace/aoc2023/day17/a.out differ diff --git a/userspace/aoc2023/day17/main.cpp b/userspace/aoc2023/day17/main.cpp new file mode 100644 index 00000000..462162c4 --- /dev/null +++ b/userspace/aoc2023/day17/main.cpp @@ -0,0 +1,202 @@ +#include +#include +#include + +#include +#include +#include + +using i8 = int8_t; +using i16 = int16_t; +using i32 = int32_t; +using i64 = int64_t; + +using u8 = uint8_t; +using u16 = uint16_t; +using u32 = uint32_t; +using u64 = uint64_t; + +struct Position +{ + i64 x; + i64 y; + + bool operator==(const Position& other) const + { + return x == other.x && y == other.y; + } +}; + + +namespace BAN +{ + template<> + struct hash + { + hash_t operator()(const Position& position) const + { + return hash()(((u64)position.x << 32) | (u64)position.y); + } + }; +} + +BAN::Vector> parse_grid(FILE* fp) +{ + BAN::Vector> grid; + + char buffer[256]; + while (fgets(buffer, sizeof(buffer), fp)) + { + BAN::StringView line(buffer); + ASSERT(line.back() == '\n'); + line = line.substring(0, line.size() - 1); + if (line.empty()) + break; + + MUST(grid.emplace_back(line.size())); + for (size_t i = 0; i < line.size(); i++) + grid.back()[i] = line[i] - '0'; + } + + return grid; +} + +//static constexpr i64 MIN_STEPS = 4; +//static constexpr i64 MAX_STEPS = 10; +template +i64 solve_general(FILE* fp) +{ + struct Block + { + // heatloss[x][y]: + // x: direction + // y: steps left in that direction + i64 heatloss[4][MAX_STEPS + 1]; + i8 entered_from = -1; + }; + + const auto grid = parse_grid(fp); + + // initially mark everything very large, except (0, 0) + BAN::Vector> heatloss_map; + MUST(heatloss_map.resize(grid.size())); + for (size_t y = 0; y < grid.size(); y++) + { + MUST(heatloss_map[y].resize(grid[y].size())); + for (size_t x = 0; x < grid[y].size(); x++) + for (size_t dir = 0; dir < 4; dir++) + for (size_t step = 0; step <= MAX_STEPS; step++) + heatloss_map[y][x].heatloss[dir][step] = 1'000'000'000; + } + for (size_t dir = 0; dir < 4; dir++) + for (size_t step = 0; step <= MAX_STEPS; step++) + heatloss_map[0][0].heatloss[dir][step] = 0; + + BAN::HashSet visited; + BAN::HashSet pending; + MUST(pending.insert({ 0, 0 })); + + while (!pending.empty()) + { + auto position = *pending.begin(); + pending.remove(position); + + Position offsets[4] = { { -1, 0 }, { 0, -1 }, { 1, 0 }, { 0, 1 } }; + + for (i8 dir = 0; dir < 4; dir++) + { + auto target = position; + + i64 path_heatloss = 0; + + auto is_target_in_bounds = + [&](const Position& target) + { + if (target.y < 0 || target.y >= (i64)grid.size()) + return false; + if (target.x < 0 || target.x >= (i64)grid.front().size()) + return false; + return true; + }; + + i64 target_distance = (heatloss_map[position.y][position.x].entered_from == dir) ? 1 : MIN_STEPS; + for (i64 i = 0; i < target_distance; i++) + { + target.x += offsets[dir].x; + target.y += offsets[dir].y; + if (!is_target_in_bounds(target)) + break; + path_heatloss += grid[target.y][target.x]; + } + if (!is_target_in_bounds(target)) + continue; + + auto& target_heatloss = heatloss_map[target.y][target.x]; + + bool target_updated = false; + for (i8 new_dir = 0; new_dir < 4; new_dir++) + { + // Don't allow going backwards + if (new_dir == dir + 2 || dir == new_dir + 2) + continue; + + for (i64 step = target_distance; step <= MAX_STEPS; step++) + { + i64 possible_heatloss = heatloss_map[position.y][position.x].heatloss[dir][step] + path_heatloss; + i64 new_dir_max_step = (new_dir == dir) ? step - target_distance : MAX_STEPS; + for (i64 i = 0; i <= new_dir_max_step; i++) + { + if (possible_heatloss >= target_heatloss.heatloss[new_dir][i]) + continue; + target_heatloss.heatloss[new_dir][i] = possible_heatloss; + target_heatloss.entered_from = dir; + target_updated = true; + } + } + } + if (target_updated || !visited.contains(target)) + MUST(pending.insert(target)); + } + + MUST(visited.insert(position)); + } + + i64 result = INT64_MAX; + for (size_t dir = 0; dir < 4; dir++) + for (size_t step = 0; step <= MAX_STEPS; step++) + result = BAN::Math::min(result, heatloss_map.back().back().heatloss[dir][step]); + return result; +} + +i64 puzzle1(FILE* fp) +{ + return solve_general<1, 3>(fp); +} + +i64 puzzle2(FILE* fp) +{ + return solve_general<4, 10>(fp); +} + +int main(int argc, char** argv) +{ + const char* file_path = "/usr/share/aoc2023/day17_input.txt"; + + if (argc >= 2) + file_path = argv[1]; + + FILE* fp = fopen(file_path, "r"); + if (fp == nullptr) + { + perror("fopen"); + return 1; + } + + printf("puzzle1: %" PRId64 "\n", puzzle1(fp)); + + fseek(fp, 0, SEEK_SET); + + printf("puzzle2: %" PRId64 "\n", puzzle2(fp)); + + fclose(fp); +}