diff --git a/userspace/aoc2023/CMakeLists.txt b/userspace/aoc2023/CMakeLists.txt index e1a7b83d4a..616381a754 100644 --- a/userspace/aoc2023/CMakeLists.txt +++ b/userspace/aoc2023/CMakeLists.txt @@ -12,6 +12,7 @@ set(AOC2023_PROJECTS day7 day8 day9 + day10 ) set(BANAN_AOC2023_BIN ${BANAN_BIN}/aoc2023) diff --git a/userspace/aoc2023/day10/CMakeLists.txt b/userspace/aoc2023/day10/CMakeLists.txt new file mode 100644 index 0000000000..5b1560954a --- /dev/null +++ b/userspace/aoc2023/day10/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.26) + +project(aoc2023_day10 CXX) + +set(SOURCES + main.cpp +) + +add_executable(aoc2023_day10 ${SOURCES}) +target_compile_options(aoc2023_day10 PUBLIC -O2 -g) +target_link_libraries(aoc2023_day10 PUBLIC libc ban) + +add_dependencies(aoc2023_day10 libc-install ban-install) + +add_custom_target(aoc2023_day10-install + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/aoc2023_day10 ${BANAN_AOC2023_BIN}/day10 + DEPENDS aoc2023_day10 + DEPENDS aoc2023_always +) + +add_dependencies(aoc2023 aoc2023_day10) +add_dependencies(aoc2023-install aoc2023_day10-install) diff --git a/userspace/aoc2023/day10/main.cpp b/userspace/aoc2023/day10/main.cpp new file mode 100644 index 0000000000..0e3b58a72b --- /dev/null +++ b/userspace/aoc2023/day10/main.cpp @@ -0,0 +1,278 @@ +#include +#include +#include + +#include +#include +#include + +using i32 = int32_t; +using i64 = int64_t; + +using u32 = uint32_t; +using u64 = uint64_t; + +enum class Direction +{ + North, + East, + South, + West +}; + +struct Position +{ + size_t x; + size_t y; + Direction from; +}; + +using Grid = BAN::Vector>; + +Grid parse_grid(FILE* fp) +{ + Grid grid; + char buffer[256]; + while (fgets(buffer, sizeof(buffer), fp)) + { + if (strlen(buffer) < 2) + continue; + MUST(grid.emplace_back(strlen(buffer) - 1)); + for (size_t i = 0; buffer[i + 1]; i++) + grid.back()[i] = buffer[i]; + } + return grid; +} + +bool can_enter_tile_from(char tile, Direction from) +{ + switch (from) + { + case Direction::North: + return tile == '|' || tile == 'L' || tile == 'J'; + case Direction::South: + return tile == '|' || tile == '7' || tile == 'F'; + case Direction::West: + return tile == '-' || tile == 'J' || tile == '7'; + case Direction::East: + return tile == '-' || tile == 'L' || tile == 'F'; + default: + return false; + } +}; + +Direction tile_exit_direction(char tile, Direction enter) +{ + switch (tile) + { + case '|': return (enter == Direction::North) ? Direction::South : Direction::North; + case '-': return (enter == Direction::East) ? Direction::West : Direction::East; + case 'L': return (enter == Direction::North) ? Direction::East : Direction::North; + case 'J': return (enter == Direction::North) ? Direction::West : Direction::North; + case '7': return (enter == Direction::South) ? Direction::West : Direction::South; + case 'F': return (enter == Direction::South) ? Direction::East : Direction::South; + } + ASSERT_NOT_REACHED(); +}; + +BAN::Array find_grid_first_moves(const Grid& grid) +{ + BAN::Array positions; + for (size_t y = 0; y < grid.size(); y++) + { + for (size_t x = 0; x < grid.size(); x++) + { + if (grid[y][x] == 'S') + { + size_t index = 0; + if (can_enter_tile_from(grid[y - 1][x], Direction::South)) + positions[index++] = { x, y - 1, Direction::South }; + if (can_enter_tile_from(grid[y + 1][x], Direction::North)) + positions[index++] = { x, y + 1, Direction::North }; + if (can_enter_tile_from(grid[y][x - 1], Direction::East)) + positions[index++] = { x - 1, y, Direction::East }; + if (can_enter_tile_from(grid[y][x + 1], Direction::West)) + positions[index++] = { x + 1, y, Direction::West }; + ASSERT(index == 2); + return positions; + } + } + } + ASSERT_NOT_REACHED(); +} + +i64 puzzle1(FILE* fp) +{ + auto grid = parse_grid(fp); + auto positions = find_grid_first_moves(grid); + + for (i64 distance = 1;; distance++) + { + if (positions[0].x == positions[1].x && positions[0].y == positions[1].y) + return distance; + + for (auto& position : positions) + { + Direction direction = tile_exit_direction(grid[position.y][position.x], position.from); + switch (direction) + { + case Direction::North: position.y--; position.from = Direction::South; break; + case Direction::South: position.y++; position.from = Direction::North; break; + case Direction::West: position.x--; position.from = Direction::East; break; + case Direction::East: position.x++; position.from = Direction::West; break; + } + } + } +} + +i64 puzzle2(FILE* fp) +{ + enum Flag : u32 + { + Path = 1 << 8, + Left = 1 << 9, + Right = 1 << 10, + Mask = Path | Left | Right, + }; + + auto grid = parse_grid(fp); + auto position = find_grid_first_moves(grid)[0]; + + while ((grid[position.y][position.x] & ~Flag::Mask) != 'S') + { + Direction direction = tile_exit_direction(grid[position.y][position.x] & ~Flag::Mask, position.from); + + switch (grid[position.y][position.x] & ~Flag::Mask) + { + case '|': + if (position.x > 0) + grid[position.y][position.x - 1] |= (direction == Direction::North) ? Flag::Left : Flag::Right; + if (position.x < grid[position.y].size() - 1) + grid[position.y][position.x + 1] |= (direction == Direction::North) ? Flag::Right : Flag::Left; + break; + case '-': + if (position.y > 0) + grid[position.y - 1][position.x] |= (direction == Direction::East) ? Flag::Left : Flag::Right; + if (position.y < grid.size() - 1) + grid[position.y + 1][position.x] |= (direction == Direction::East) ? Flag::Right : Flag::Left; + break; + case 'L': + if (position.x > 0) + grid[position.y][position.x - 1] |= (direction == Direction::North) ? Flag::Left : Flag::Right; + if (position.y < grid.size() - 1) + grid[position.y + 1][position.x] |= (direction == Direction::North) ? Flag::Left : Flag::Right; + break; + case 'J': + if (position.x < grid[position.y].size() - 1) + grid[position.y][position.x + 1] |= (direction == Direction::West) ? Flag::Left : Flag::Right; + if (position.y < grid.size() - 1) + grid[position.y + 1][position.x] |= (direction == Direction::West) ? Flag::Left : Flag::Right; + break; + case '7': + if (position.y > 0) + grid[position.y - 1][position.x] |= (direction == Direction::South) ? Flag::Left : Flag::Right; + if (position.x < grid[position.y].size() - 1) + grid[position.y][position.x + 1] |= (direction == Direction::South) ? Flag::Left : Flag::Right; + break; + case 'F': + if (position.y > 0) + grid[position.y - 1][position.x] |= (direction == Direction::East) ? Flag::Left : Flag::Right; + if (position.x > 0) + grid[position.y][position.x - 1] |= (direction == Direction::East) ? Flag::Left : Flag::Right; + break; + } + + grid[position.y][position.x] |= Flag::Path; + + switch (direction) + { + case Direction::North: position.y--; position.from = Direction::South; break; + case Direction::South: position.y++; position.from = Direction::North; break; + case Direction::West: position.x--; position.from = Direction::East; break; + case Direction::East: position.x++; position.from = Direction::West; break; + } + } + + // Mark start tile as part of the path + grid[position.y][position.x] |= Flag::Path; + + // Clean up flags + for (auto& row : grid) + { + for (u32& tile : row) + { + // Remove left and right from path + if (tile & Flag::Path) + tile &= ~(Flag::Left | Flag::Right); + // Tile should never be both left and right + ASSERT(!((tile & Flag::Left) && (tile & Flag::Right))); + } + } + + // Determine whether left or right is enclosed by loop + Flag enclosed = Flag::Path; + for (const auto& row : grid) + { + for (u32 tile : row) + { + if ((tile & (Flag::Right | Flag::Left))) + { + enclosed = (tile & Flag::Right) ? Flag::Left : Flag::Right; + break; + } + } + if (enclosed != Flag::Path) + break; + } + ASSERT(enclosed != Flag::Path); + + // Expand all enclosed areas + bool modified = true; + while (modified) + { + modified = false; + for (size_t y = 1; y < grid.size(); y++) + { + for (size_t x = 1; x < grid[y].size(); x++) + { + if (grid[y][x] & Flag::Mask) + continue; + if ((grid[y - 1][x] & enclosed) || (grid[y][x - 1] & enclosed)) + { + grid[y][x] |= enclosed; + modified = true; + } + } + } + } + + // Calculate number of enclosed tiles + i64 result = 0; + for (const auto& row : grid) + for (u32 c : row) + result += !!(c & enclosed); + return result; +} + +int main(int argc, char** argv) +{ + const char* file_path = "/usr/share/aoc2023/day10_input.txt"; + + if (argc >= 2) + file_path = argv[1]; + + FILE* fp = fopen(file_path, "r"); + if (fp == nullptr) + { + perror("fopen"); + return 1; + } + + printf("puzzle1: %lld\n", puzzle1(fp)); + + fseek(fp, 0, SEEK_SET); + + printf("puzzle2: %lld\n", puzzle2(fp)); + + fclose(fp); +}