AOC2023: Implement day10
This commit is contained in:
parent
2f8759d2d3
commit
f077e17b2a
|
@ -12,6 +12,7 @@ set(AOC2023_PROJECTS
|
|||
day7
|
||||
day8
|
||||
day9
|
||||
day10
|
||||
)
|
||||
|
||||
set(BANAN_AOC2023_BIN ${BANAN_BIN}/aoc2023)
|
||||
|
|
|
@ -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)
|
|
@ -0,0 +1,278 @@
|
|||
#include <BAN/Array.h>
|
||||
#include <BAN/String.h>
|
||||
#include <BAN/Vector.h>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
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<BAN::Vector<u32>>;
|
||||
|
||||
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<Position, 2> find_grid_first_moves(const Grid& grid)
|
||||
{
|
||||
BAN::Array<Position, 2> 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);
|
||||
}
|
Loading…
Reference in New Issue