273 lines
7.4 KiB
C++
273 lines
7.4 KiB
C++
#include <BAN/Array.h>
|
|
#include <BAN/String.h>
|
|
#include <BAN/Vector.h>
|
|
|
|
#include <ctype.h>
|
|
#include <inttypes.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 (index < 2 && can_enter_tile_from(grid[y - 1][x], Direction::South))
|
|
positions[index++] = { x, y - 1, Direction::South };
|
|
if (index < 2 && can_enter_tile_from(grid[y + 1][x], Direction::North))
|
|
positions[index++] = { x, y + 1, Direction::North };
|
|
if (index < 2 && can_enter_tile_from(grid[y][x - 1], Direction::East))
|
|
positions[index++] = { x - 1, y, Direction::East };
|
|
if (index < 2 && 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 tiles
|
|
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 (only one pass is needed since exery area
|
|
// is fully bordered by enclosed tiles)
|
|
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;
|
|
}
|
|
}
|
|
|
|
// 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: %" PRId64 "\n", puzzle1(fp));
|
|
|
|
fseek(fp, 0, SEEK_SET);
|
|
|
|
printf("puzzle2: %" PRId64 "\n", puzzle2(fp));
|
|
|
|
fclose(fp);
|
|
}
|