aoc2024: Implement day20 solution
This commit is contained in:
parent
3e9826acf5
commit
9caa3030ea
|
@ -18,6 +18,7 @@ set(AOC2024_PROJECTS
|
|||
day17
|
||||
day18
|
||||
day19
|
||||
day20
|
||||
full
|
||||
)
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
set(SOURCES
|
||||
main.cpp
|
||||
)
|
||||
|
||||
add_executable(aoc2024_day20 ${SOURCES})
|
||||
banan_include_headers(aoc2024_day20 ban)
|
||||
banan_link_library(aoc2024_day20 libc)
|
||||
|
||||
install(TARGETS aoc2024_day20 OPTIONAL)
|
|
@ -0,0 +1,216 @@
|
|||
#include <BAN/Optional.h>
|
||||
#include <BAN/Vector.h>
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
|
||||
using i8 = int8_t;
|
||||
using i16 = int16_t;
|
||||
using i32 = int32_t;
|
||||
using i64 = int64_t;
|
||||
using isize = ssize_t;
|
||||
|
||||
using u8 = uint8_t;
|
||||
using u16 = uint16_t;
|
||||
using u32 = uint32_t;
|
||||
using u64 = uint64_t;
|
||||
using usize = size_t;
|
||||
|
||||
template<typename T>
|
||||
struct Vec2
|
||||
{
|
||||
T x, y;
|
||||
};
|
||||
|
||||
struct Grid2D
|
||||
{
|
||||
struct Tile
|
||||
{
|
||||
static constexpr u32 undefined_cost = BAN::numeric_limits<u32>::max();
|
||||
|
||||
bool wall;
|
||||
u32 cost;
|
||||
};
|
||||
|
||||
usize width { 0 };
|
||||
usize height { 0 };
|
||||
BAN::Vector<Tile> data;
|
||||
|
||||
inline Tile get(Vec2<usize> pos) const
|
||||
{
|
||||
ASSERT(pos.x < width && pos.y < height);
|
||||
return data[pos.y * width + pos.x];
|
||||
}
|
||||
|
||||
inline Tile& get(Vec2<usize> pos)
|
||||
{
|
||||
ASSERT(pos.x < width && pos.y < height);
|
||||
return data[pos.y * width + pos.x];
|
||||
}
|
||||
};
|
||||
|
||||
struct ParseInputResult
|
||||
{
|
||||
Grid2D grid;
|
||||
Vec2<usize> start;
|
||||
Vec2<usize> end;
|
||||
};
|
||||
|
||||
static ParseInputResult parse_input(FILE* fp)
|
||||
{
|
||||
Grid2D grid;
|
||||
Vec2<usize> start, end;
|
||||
|
||||
char buffer[1024] {};
|
||||
for (usize y = 0; fgets(buffer, sizeof(buffer), fp); y++)
|
||||
{
|
||||
const usize len = strlen(buffer);
|
||||
if (len == 0 || buffer[0] == '\n')
|
||||
break;
|
||||
if (grid.data.empty())
|
||||
grid.width = len - 1;
|
||||
grid.height++;
|
||||
|
||||
ASSERT(buffer[grid.width] == '\n');
|
||||
|
||||
for (usize x = 0; x < grid.width; x++)
|
||||
{
|
||||
if (buffer[x] == 'S')
|
||||
start = { .x = x, .y = y };
|
||||
if (buffer[x] == 'E')
|
||||
end = { .x = x, .y = y };
|
||||
MUST(grid.data.push_back({ .wall = (buffer[x] == '#'), .cost = Grid2D::Tile::undefined_cost }));
|
||||
}
|
||||
}
|
||||
|
||||
return ParseInputResult {
|
||||
.grid = BAN::move(grid),
|
||||
.start = start,
|
||||
.end = end,
|
||||
};
|
||||
}
|
||||
|
||||
static void evaluate_grid(Grid2D& grid, const Vec2<usize> start)
|
||||
{
|
||||
struct ToCheck
|
||||
{
|
||||
Vec2<usize> pos;
|
||||
u32 cost;
|
||||
};
|
||||
BAN::Vector<ToCheck> to_check;
|
||||
MUST(to_check.push_back({ .pos = start, .cost = 0 }));
|
||||
|
||||
while (!to_check.empty())
|
||||
{
|
||||
const auto [pos, cost] = to_check.front();
|
||||
to_check.remove(0);
|
||||
|
||||
{
|
||||
auto& tile = grid.get(pos);
|
||||
if (tile.wall || tile.cost <= cost)
|
||||
continue;
|
||||
tile.cost = cost;
|
||||
}
|
||||
|
||||
constexpr Vec2<isize> dirs[] {
|
||||
{ 1, 0 },
|
||||
{ -1, 0 },
|
||||
{ 0, 1 },
|
||||
{ 0, -1 },
|
||||
};
|
||||
|
||||
for (const auto dir : dirs)
|
||||
{
|
||||
const auto next_pos = Vec2<usize> {
|
||||
.x = pos.x + dir.x,
|
||||
.y = pos.y + dir.y,
|
||||
};
|
||||
if (next_pos.x >= grid.width || next_pos.y >= grid.height)
|
||||
continue;
|
||||
|
||||
const auto next_tile = grid.get(next_pos);
|
||||
if (next_tile.wall || next_tile.cost <= cost + 1)
|
||||
continue;
|
||||
MUST(to_check.push_back({ .pos = next_pos, .cost = cost + 1}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static u32 count_100_picosecond_cheats(const Grid2D& grid, i32 max_cheat_length)
|
||||
{
|
||||
u32 result = 0;
|
||||
for (usize y = 0; y < grid.height; y++)
|
||||
{
|
||||
for (usize x = 0; x < grid.width; x++)
|
||||
{
|
||||
const auto tile = grid.get({ .x = x, .y = y });
|
||||
if (tile.wall || tile.cost == Grid2D::Tile::undefined_cost)
|
||||
continue;
|
||||
|
||||
for (i32 yoff = -max_cheat_length; yoff <= max_cheat_length; yoff++)
|
||||
{
|
||||
const i32 max_xoff = max_cheat_length - BAN::Math::abs(yoff);
|
||||
for (i32 xoff = -max_xoff; xoff <= max_xoff; xoff++)
|
||||
{
|
||||
const auto next_pos = Vec2<usize> {
|
||||
.x = x + xoff,
|
||||
.y = y + yoff,
|
||||
};
|
||||
if (next_pos.x >= grid.width || next_pos.y >= grid.height)
|
||||
continue;
|
||||
|
||||
const auto next_tile = grid.get(next_pos);
|
||||
if (next_tile.wall)
|
||||
continue;
|
||||
if (next_tile.cost == Grid2D::Tile::undefined_cost)
|
||||
continue;
|
||||
if (next_tile.cost <= tile.cost)
|
||||
continue;
|
||||
|
||||
const usize distance = BAN::Math::abs(yoff) + BAN::Math::abs(xoff);
|
||||
const u32 save = next_tile.cost - tile.cost;
|
||||
if (save >= 100 + distance)
|
||||
result++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
i64 part1(FILE* fp)
|
||||
{
|
||||
auto [grid, start, end] = parse_input(fp);
|
||||
evaluate_grid(grid, start);
|
||||
return count_100_picosecond_cheats(grid, 2);
|
||||
}
|
||||
|
||||
i64 part2(FILE* fp)
|
||||
{
|
||||
auto [grid, start, end] = parse_input(fp);
|
||||
evaluate_grid(grid, start);
|
||||
return count_100_picosecond_cheats(grid, 20);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
const char* file_path = "/usr/share/aoc2024/day20_input.txt";
|
||||
|
||||
if (argc >= 2)
|
||||
file_path = argv[1];
|
||||
|
||||
FILE* fp = fopen(file_path, "r");
|
||||
if (fp == nullptr)
|
||||
{
|
||||
perror("fopen");
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("part1: %" PRId64 "\n", part1(fp));
|
||||
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
|
||||
printf("part2: %" PRId64 "\n", part2(fp));
|
||||
|
||||
fclose(fp);
|
||||
}
|
Loading…
Reference in New Issue