Skip to content

day 20 part 1 #39

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions day_20/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
aoc_day(20)
1 change: 1 addition & 0 deletions day_20/lib/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
aoc_add_library()
38 changes: 38 additions & 0 deletions day_20/lib/maze.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#include "maze.hpp"

#include <stdexcept>
#include <string>

namespace aoc::day_20 {

/// @brief Parses the maze from the input
/// @param lines The input to parse
/// @return The parsed maze
auto parseMaze(std::vector<std::string_view> lines) -> std::vector<std::vector<aoc::path_finding::maze_cell>> {
std::vector<std::vector<aoc::path_finding::maze_cell>> maze;
for (auto const & line : lines) {
std::vector<aoc::path_finding::maze_cell> row;
for (auto const & cell : line) {
switch (cell) {
case '#':
row.push_back(aoc::path_finding::maze_cell::WALL);
break;
case '.':
row.push_back(aoc::path_finding::maze_cell::EMPTY);
break;
case 'S':
row.push_back(aoc::path_finding::maze_cell::START);
break;
case 'E':
row.push_back(aoc::path_finding::maze_cell::END);
break;
default:
throw std::invalid_argument("Invalid cell type in maze");
}
}
maze.push_back(row);
}
return maze;
}

} // namespace aoc::day_20
15 changes: 15 additions & 0 deletions day_20/lib/maze.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#pragma once

#include <string_view>
#include <vector>

#include "../../shared/src/maze_cell.hpp"

namespace aoc::day_20 {

/// @brief Parses the maze from the input
/// @param lines The input to parse
/// @return The parsed maze
auto parseMaze(std::vector<std::string_view> lines) -> std::vector<std::vector<aoc::path_finding::maze_cell>>;

} // namespace aoc::day_20
2 changes: 2 additions & 0 deletions day_20/src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
aoc_add_executable()
configure_file(input.txt input.txt COPYONLY)
141 changes: 141 additions & 0 deletions day_20/src/input.txt

Large diffs are not rendered by default.

48 changes: 48 additions & 0 deletions day_20/src/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#include <expected>
#include <ranges>
#include <string>
#include <system_error>
#include <unordered_set>
#include <vector>

#include "../../shared/src/astar.hpp"
#include "../../shared/src/exit_code.hpp"
#include "../../shared/src/file_operations.hpp"
#include "../../shared/src/grid_processor.hpp"
#include "../../shared/src/print_compatibility_layer.hpp"

#include "../lib/maze.hpp"

auto main(int argc, char const ** argv) -> int {
std::expected<std::string, std::error_code> input = aoc::file_operations::read("input.txt");
if (!input) [[unlikely]] {
std::println(stderr, "Could not open file: {}", input.error().message());
return aoc::EXIT_CODE_IO_ERROR;
}

auto grid = aoc::grid_processor::processLines(*input);

if (!aoc::grid_processor::validateGrid(grid)) {
std::println(stderr, "Grid is not valid");
return aoc::EXIT_CODE_DATA_ERROR;
}

auto maze = aoc::day_20::parseMaze(grid);

auto scoringFun = [](aoc::path_finding::Node const & a, aoc::path_finding::Node const & b) { return 1; };

// Part 1
auto possible_cheats = aoc::path_finding::MazeSolver(maze, scoringFun).findPathUsingCheats(2);

// Sort by saving
std::ranges::sort(possible_cheats, [](auto const & a, auto const & b) { return a.saving > b.saving; });

for (size_t i = 0; i < possible_cheats.size(); i++) {
if (possible_cheats[i].saving < 100) {
std::println("Found paths with a saving over 100: {}", i);
break;
}
}

return aoc::EXIT_CODE_SUCCESS;
}
1 change: 1 addition & 0 deletions day_20/test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
aoc_add_test()
21 changes: 2 additions & 19 deletions day_6/lib/grid_parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,8 @@ struct ParsedGrid {

/// @brief Converts a character representation of direction to FacingDirection enum
/// @param c The character to convert ('^', 'v', '<', '>')
/// @return The corresponding FacingDirection
/// @details Optimized using platform-specific implementations:
/// - MSVC: Uses switch with __assume(0)
/// - GCC/Clang: Uses computed goto for better performance
/// @return The corresponding FacingDirection value
[[nodiscard]] constexpr auto charToGuardDirection(char c) -> FacingDirection {
#ifdef _MSC_VER
switch (c) {
case '^':
return FacingDirection::Up;
Expand All @@ -40,21 +36,8 @@ struct ParsedGrid {
case '>':
return FacingDirection::Right;
default:
__assume(0); // Unreachable
std::unreachable();
}
#else // Use Computed goto's for GCC and Clang
static void * const jumpTable[] = {&&up, &&down, &&left, &&right};
goto * jumpTable[c - '^'];

up:
return FacingDirection::Up;
down:
return FacingDirection::Down;
left:
return FacingDirection::Left;
right:
return FacingDirection::Right;
#endif
}

/// @brief Checks if a character represents a guard
Expand Down
49 changes: 48 additions & 1 deletion shared/src/astar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include <ranges>
#include <unordered_map>

#include "print_compatibility_layer.hpp"

namespace aoc::path_finding {

bool Node::operator==(Node const & other) const {
Expand Down Expand Up @@ -61,7 +63,52 @@ auto MazeSolver::findPath() -> PathResult {
return result;
}

/// @brief Find all paths in the maze with the same cost as the cheapest path
auto MazeSolver::findPathUsingCheats(size_t boostLength) -> std::vector<PathResultUsingCheats> {
std::vector<PathResultUsingCheats> results;
auto shortest = findPath();
if (shortest.cost == -1) {
return {};
}
// For every obstacle in the maze remove it and try to find a path
// If the path is shorter than the shortest path, add it to the results
// Then remove a neighbor of the obstacle and try to find a path
// If the path is shorter than the shortest path, add it to the results

for (size_t y = 0; y < m_maze.size(); ++y) {
std::println("y: {} of {}", y, m_maze.size());
for (size_t x = 0; x < m_maze[y].size(); ++x) {
if (m_maze[y][x] == maze_cell::WALL) {
m_maze[y][x] = maze_cell::EMPTY;
auto path = findPath();
auto currentCost = shortest.cost;
if (path.cost != -1 && path.cost < shortest.cost) {
results.push_back({path.path, shortest.cost - path.cost});
}
// Check if the neigbor is a wall as well
for (auto const & dir : {aoc::math::Direction::UP, aoc::math::Direction::RIGHT}) {
auto vec = aoc::math::getDirectionVector(dir);
auto new_pos =
aoc::math::vector_2d<int16_t>{static_cast<int16_t>(x), static_cast<int16_t>(y)} + vec;
if (isValid(new_pos)) {
if (m_maze[new_pos.y][new_pos.x] == maze_cell::WALL) {
m_maze[new_pos.y][new_pos.x] = maze_cell::EMPTY;
auto path = findPath();
if (path.cost != -1 && path.cost < shortest.cost) {
results.push_back({path.path, shortest.cost - path.cost});
}
m_maze[new_pos.y][new_pos.x] = maze_cell::WALL;
}
}
}
m_maze[y][x] = maze_cell::WALL;
}
}
}

return results;
}

// @brief Find all paths in the maze with the same cost as the cheapest path
/// @return All paths in the maze with the same cost as the cheapest path
auto MazeSolver::findPaths() -> std::vector<PathResult> {
std::vector<PathResult> allPaths;
Expand Down
12 changes: 12 additions & 0 deletions shared/src/astar.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ struct Node {
bool operator<(Node const & other) const;
};

/// @brief The result of the pathfinding
struct PathResultUsingCheats {
std::vector<Node> path;
int64_t saving;
};

/// @brief The result of the pathfinding
struct PathResult {
std::vector<Node> path;
Expand All @@ -52,6 +58,12 @@ class MazeSolver {
/// @return All paths in the maze with the same cost as the cheapest path
auto findPaths() -> std::vector<PathResult>;

/// @brief Find all paths in the maze where a temprorary boost allowing to walk through walls for a limited time is
/// used
/// @return All paths in the maze with a cheaper cost than the cheapest path without the boost and the number of
/// steps saved
auto findPathUsingCheats(size_t boostLength) -> std::vector<PathResultUsingCheats>;

private:
/// @brief The maze to solve
std::vector<std::vector<maze_cell>> m_maze;
Expand Down
Loading