diff --git a/include/aoc/ReindeerMaze.hpp b/include/aoc/ReindeerMaze.hpp
new file mode 100644
index 0000000..7c8c3d8
--- /dev/null
+++ b/include/aoc/ReindeerMaze.hpp
@@ -0,0 +1,45 @@
+// Solutions to the Advent Of Code 2024.
+// Copyright (C) 2025 Stefan Müller
+//
+// This program is free software: you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free Software
+// Foundation, either version 3 of the License, or (at your option) any later
+// version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program. If not, see .
+
+#pragma once
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+class ReindeerMaze
+ : public LinesSolver
+{
+public:
+ ReindeerMaze(const int inputFileNameSuffix = 0);
+ virtual const std::string getPuzzleName() const override;
+ virtual const int getPuzzleDay() const override;
+ virtual void finish() override;
+private:
+ static constexpr char getStartChar();
+ static constexpr char getEndChar();
+ static constexpr char getWallChar();
+ static constexpr int getTurnCost();
+ void initializeWorkList(std::list& crossings, const int entryVertex);
+ void addCheckedIncidence(std::vector& incidences, const Point2 start,
+ const Point2 direction);
+ Point2 findStart();
+ void AddPathSegmentEdges(WeightedEdgeGraph& graph, const ReindeerMazePathIncidence& pathIncidence,
+ const std::vector& otherPathIncidences);
+};
diff --git a/include/aoc/ReindeerMazeCrossing.hpp b/include/aoc/ReindeerMazeCrossing.hpp
new file mode 100644
index 0000000..a9b5cdf
--- /dev/null
+++ b/include/aoc/ReindeerMazeCrossing.hpp
@@ -0,0 +1,32 @@
+// Solutions to the Advent Of Code 2024.
+// Copyright (C) 2025 Stefan Müller
+//
+// This program is free software: you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free Software
+// Foundation, either version 3 of the License, or (at your option) any later
+// version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program. If not, see .
+
+#pragma once
+
+#include
+
+#include
+#include
+
+class ReindeerMazeCrossing
+{
+public:
+ ReindeerMazeCrossing(const Point2 position);
+ Point2 getPosition() const;
+ bool isFinished() const;
+ std::vector incidences;
+private:
+ Point2 position_;
+};
diff --git a/include/aoc/ReindeerMazePathIncidence.hpp b/include/aoc/ReindeerMazePathIncidence.hpp
new file mode 100644
index 0000000..01ca275
--- /dev/null
+++ b/include/aoc/ReindeerMazePathIncidence.hpp
@@ -0,0 +1,34 @@
+// Solutions to the Advent Of Code 2024.
+// Copyright (C) 2025 Stefan Müller
+//
+// This program is free software: you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free Software
+// Foundation, either version 3 of the License, or (at your option) any later
+// version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program. If not, see .
+
+#pragma once
+
+#include
+
+class ReindeerMazePathIncidence
+{
+public:
+ ReindeerMazePathIncidence(const Point2 direction);
+ ReindeerMazePathIncidence(const Point2 direction, const int pathVertex);
+ Point2 getDirection() const;
+ int getPathVertex() const;
+ void setPathVertex(const int pathVertex);
+ int getPathCost() const;
+ void setPathCost(const int pathCost);
+private:
+ Point2 direction_;
+ int pathVertex_;
+ int pathCost_;
+};
diff --git a/include/aoc/VertexEdgeIncidence.hpp b/include/aoc/VertexEdgeIncidence.hpp
new file mode 100644
index 0000000..452c061
--- /dev/null
+++ b/include/aoc/VertexEdgeIncidence.hpp
@@ -0,0 +1,28 @@
+// Solutions to the Advent Of Code 2024.
+// Copyright (C) 2025 Stefan Müller
+//
+// This program is free software: you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free Software
+// Foundation, either version 3 of the License, or (at your option) any later
+// version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program. If not, see .
+
+#pragma once
+
+class VertexEdgeIncidence
+{
+public:
+ VertexEdgeIncidence(const int vertex, const int next)
+ {
+ this->vertex = vertex;
+ this->next = next;
+ }
+ int vertex;
+ int next;
+};
diff --git a/include/aoc/WeightedEdgeGraph.hpp b/include/aoc/WeightedEdgeGraph.hpp
new file mode 100644
index 0000000..3a99c5f
--- /dev/null
+++ b/include/aoc/WeightedEdgeGraph.hpp
@@ -0,0 +1,33 @@
+// Solutions to the Advent Of Code 2024.
+// Copyright (C) 2025 Stefan Müller
+//
+// This program is free software: you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free Software
+// Foundation, either version 3 of the License, or (at your option) any later
+// version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program. If not, see .
+
+#pragma once
+
+#include
+
+#include
+
+class WeightedEdgeGraph
+{
+public:
+ WeightedEdgeGraph();
+ int addVertex();
+ void addEdge(const int vertex1, const int vertex2, const int weight);
+ int dijkstra(const int source, const int target) const;
+private:
+ std::vector firstVertexIncidences_;
+ std::vector vertexEdgeIncidences_;
+ std::vector edgeWeights_;
+};
diff --git a/src/Program.cpp b/src/Program.cpp
index f88ac2d..6c970b9 100644
--- a/src/Program.cpp
+++ b/src/Program.cpp
@@ -36,6 +36,7 @@
#include
#include
#include
+#include
#include
void Program::run()
@@ -69,6 +70,7 @@ void Program::runSolvers()
runSolver(solverEngine);
runSolver(solverEngine);
runSolver(solverEngine);
+ runSolver(solverEngine);
runSolver(solverEngine);
}
diff --git a/src/ReindeerMaze.cpp b/src/ReindeerMaze.cpp
new file mode 100644
index 0000000..8347373
--- /dev/null
+++ b/src/ReindeerMaze.cpp
@@ -0,0 +1,258 @@
+// Solutions to the Advent Of Code 2024.
+// Copyright (C) 2025 Stefan Müller
+//
+// This program is free software: you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free Software
+// Foundation, either version 3 of the License, or (at your option) any later
+// version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program. If not, see .
+
+#include
+
+ReindeerMaze::ReindeerMaze(const int inputFileNameSuffix)
+ : LinesSolver{ inputFileNameSuffix }
+{
+}
+
+const std::string ReindeerMaze::getPuzzleName() const
+{
+ return "Reindeer Maze";
+}
+
+const int ReindeerMaze::getPuzzleDay() const
+{
+ return 16;
+}
+
+void ReindeerMaze::finish()
+{
+ // Initializes the graph of path segment incidences.
+ WeightedEdgeGraph graph{};
+ auto entry = graph.addVertex();
+ auto exit = graph.addVertex();
+
+ // Uses list for work items to prevent invalidation of iterators on add.
+ std::list crossings{};
+ initializeWorkList(crossings, entry);
+
+ while (!crossings.empty())
+ {
+ auto startCrossing{ --crossings.end() };
+ Point2 startPosition = startCrossing->getPosition();
+ auto incidence = std::find_if(startCrossing->incidences.begin(), startCrossing->incidences.end(),
+ [](auto& x) { return x.getPathVertex() == -1; });
+ Point2 direction{ incidence->getDirection() };
+ Point2 backwards{ -direction };
+ Point2 position{ startPosition + direction };
+
+ int pathCost{ 1 };
+ bool isSkipped{ false };
+
+ int nNext{ 0 };
+ while (getCharAt(position) != getEndChar() &&
+ std::find_if(crossings.begin(), crossings.end(),
+ [position](auto& x) { return x.getPosition() == position; }) == crossings.end())
+ {
+ nNext = 0;
+ Point2 nextPosition, nextDirection;
+ for (const auto& checkDirection : Point2::cardinalDirections)
+ {
+ if (checkDirection != backwards)
+ {
+ Point2 checkPosition{ checkDirection + position };
+ if (getCharAt(checkPosition) != getWallChar())
+ {
+ nNext++;
+ if (nNext == 1)
+ {
+ // Found a possible next step.
+ nextPosition = checkPosition;
+ nextDirection = checkDirection;
+ }
+ else if (nNext == 2)
+ {
+ // Found a second possible step, i.e. a new crossing. Will stop processing this path
+ // segment.
+ crossings.emplace_back(position);
+ crossings.back().incidences.emplace_back(backwards);
+ crossings.back().incidences.emplace_back(nextDirection);
+ crossings.back().incidences.emplace_back(checkDirection);
+ }
+ else
+ {
+ // Found another possible step. Adds the incidence to the new crossing.
+ crossings.back().incidences.emplace_back(checkDirection);
+ }
+ }
+ }
+ }
+
+ if (nNext == 1)
+ {
+ position = nextPosition;
+ pathCost++;
+ if (direction != nextDirection)
+ {
+ pathCost += getTurnCost();
+ direction = nextDirection;
+ backwards = -direction;
+ }
+
+ if (position == startPosition)
+ {
+ // Stops processing this path segment because it is a loop, and remove both incidences from the
+ // start crossing. Both must exist here.
+ startCrossing->incidences.erase(incidence);
+ auto endIncidence = std::find_if(startCrossing->incidences.begin(), startCrossing->incidences.end(),
+ [backwards](auto& x) { return x.getDirection() == backwards; });
+ startCrossing->incidences.erase(endIncidence);
+ isSkipped = true;
+ break;
+ }
+ }
+ else if (nNext == 0)
+ {
+ // Stops processing this path segment because it is a dead-end, and remove it from the start crossing.
+ startCrossing->incidences.erase(incidence);
+ isSkipped = true;
+ break;
+ }
+ else
+ {
+ // Stops processing this path segment because a new crossing has been found. This check avoids the
+ // find_if() call in the loop condition.
+ break;
+ }
+ }
+
+ if (!isSkipped)
+ {
+ // Adds the new maze path segment as a vertex.
+ auto pathVertex = graph.addVertex();
+
+ // Updates start crossing.
+ incidence->setPathVertex(pathVertex);
+ incidence->setPathCost(pathCost);
+
+ // Determines the end crossing of the new path segment and its incidence.
+ std::list::iterator endCrossing;
+ std::vector::iterator endIncidence;
+ endCrossing = nNext == 1
+ ? endCrossing = std::find_if(crossings.begin(), crossings.end(),
+ [position](auto& x) { return x.getPosition() == position; })
+ : --crossings.end();
+ if (endCrossing != crossings.end())
+ {
+ // This incidence must exist, no need to check the incidence iterator.
+ endIncidence = std::find_if(endCrossing->incidences.begin(), endCrossing->incidences.end(),
+ [backwards](auto& x) { return x.getDirection() == backwards; });
+ endIncidence->setPathVertex(pathVertex);
+ endIncidence->setPathCost(pathCost);
+ }
+
+ // Connects the new path segment to all adjacent path segments.
+ AddPathSegmentEdges(graph, *incidence, startCrossing->incidences);
+ if (endCrossing != crossings.end())
+ {
+ AddPathSegmentEdges(graph, *endIncidence, endCrossing->incidences);
+ }
+ else
+ {
+ graph.addEdge(pathVertex, exit, pathCost);
+ }
+
+ // Checks if end crossing is finished.
+ //checkFinishedCrossing(crossings, endCrossing);
+ if (endCrossing != crossings.end() && endCrossing->isFinished())
+ {
+ crossings.erase(endCrossing);
+ }
+ }
+
+ // Checks if start crossing is finished.
+ if (startCrossing->isFinished())
+ {
+ crossings.erase(startCrossing);
+ }
+ }
+
+ part1 = graph.dijkstra(entry, exit) / 2;
+}
+
+constexpr char ReindeerMaze::getStartChar()
+{
+ return 'S';
+}
+
+constexpr char ReindeerMaze::getEndChar()
+{
+ return 'E';
+}
+
+constexpr char ReindeerMaze::getWallChar()
+{
+ return '#';
+}
+
+constexpr int ReindeerMaze::getTurnCost()
+{
+ return 1000;
+}
+
+// Initializes the work list of crossing incidences.
+void ReindeerMaze::initializeWorkList(std::list& crossings, const int entryVertex)
+{
+ Point2 start{ findStart() };
+ crossings.emplace_back(start);
+ crossings.back().incidences.emplace_back(Point2::left, entryVertex);
+ addCheckedIncidence(crossings.back().incidences, start, Point2::right);
+ addCheckedIncidence(crossings.back().incidences, start, Point2::up);
+}
+
+void ReindeerMaze::addCheckedIncidence(std::vector& incidences, const Point2 start,
+ const Point2 direction)
+{
+ if (getCharAt(start + direction) != getWallChar())
+ {
+ incidences.emplace_back(direction);
+ }
+}
+
+Point2 ReindeerMaze::findStart()
+{
+ for (int j = 0; j < lines.size(); j++)
+ {
+ for (int i = 0; i < lines[j].size(); i++)
+ {
+ if (lines[j][i] == getStartChar())
+ {
+ return { i, j };
+ }
+ }
+ }
+ return { 0, 0 };
+}
+
+void ReindeerMaze::AddPathSegmentEdges(WeightedEdgeGraph& graph, const ReindeerMazePathIncidence& pathIncidence,
+ const std::vector& otherPathIncidences)
+{
+ for (auto& otherIncidence : otherPathIncidences)
+ {
+ if (otherIncidence.getPathVertex() > -1 && otherIncidence.getPathVertex() != pathIncidence.getPathVertex())
+ {
+ int weight{ pathIncidence.getPathCost() + otherIncidence.getPathCost() };
+ // Checks for turn, i.e. perpendicular directions.
+ if (otherIncidence.getDirection().x != -pathIncidence.getDirection().x)
+ {
+ weight += 2 * getTurnCost();
+ }
+ graph.addEdge(pathIncidence.getPathVertex(), otherIncidence.getPathVertex(), weight);
+ }
+ }
+}
diff --git a/src/ReindeerMazeCrossing.cpp b/src/ReindeerMazeCrossing.cpp
new file mode 100644
index 0000000..26edcce
--- /dev/null
+++ b/src/ReindeerMazeCrossing.cpp
@@ -0,0 +1,32 @@
+// Solutions to the Advent Of Code 2024.
+// Copyright (C) 2025 Stefan Müller
+//
+// This program is free software: you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free Software
+// Foundation, either version 3 of the License, or (at your option) any later
+// version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program. If not, see .
+
+#include
+
+ReindeerMazeCrossing::ReindeerMazeCrossing(const Point2 position)
+ : position_{ position }, incidences{}
+{
+}
+
+Point2 ReindeerMazeCrossing::getPosition() const
+{
+ return position_;
+}
+
+bool ReindeerMazeCrossing::isFinished() const
+{
+ return std::find_if(incidences.begin(), incidences.end(), [](auto& x) { return x.getPathVertex() < 0; }) ==
+ incidences.end();
+}
diff --git a/src/ReindeerMazePathIncidence.cpp b/src/ReindeerMazePathIncidence.cpp
new file mode 100644
index 0000000..fbaa5ae
--- /dev/null
+++ b/src/ReindeerMazePathIncidence.cpp
@@ -0,0 +1,51 @@
+// Solutions to the Advent Of Code 2024.
+// Copyright (C) 2025 Stefan Müller
+//
+// This program is free software: you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free Software
+// Foundation, either version 3 of the License, or (at your option) any later
+// version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program. If not, see .
+
+#include
+
+ReindeerMazePathIncidence::ReindeerMazePathIncidence(const Point2 direction)
+ : ReindeerMazePathIncidence(direction, -1)
+{
+}
+
+ReindeerMazePathIncidence::ReindeerMazePathIncidence(const Point2 direction, const int pathVertex)
+ : direction_{ direction }, pathVertex_{ pathVertex }, pathCost_{ 0 }
+{
+}
+
+Point2 ReindeerMazePathIncidence::getDirection() const
+{
+ return direction_;
+}
+
+int ReindeerMazePathIncidence::getPathVertex() const
+{
+ return pathVertex_;
+}
+
+void ReindeerMazePathIncidence::setPathVertex(const int pathVertex)
+{
+ pathVertex_ = pathVertex;
+}
+
+int ReindeerMazePathIncidence::getPathCost() const
+{
+ return pathCost_;
+}
+
+void ReindeerMazePathIncidence::setPathCost(const int pathCost)
+{
+ pathCost_ = pathCost;
+}
diff --git a/src/WeightedEdgeGraph.cpp b/src/WeightedEdgeGraph.cpp
new file mode 100644
index 0000000..dee4144
--- /dev/null
+++ b/src/WeightedEdgeGraph.cpp
@@ -0,0 +1,70 @@
+// Solutions to the Advent Of Code 2024.
+// Copyright (C) 2025 Stefan Müller
+//
+// This program is free software: you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free Software
+// Foundation, either version 3 of the License, or (at your option) any later
+// version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program. If not, see .
+
+#include
+
+#include
+#include
+
+WeightedEdgeGraph::WeightedEdgeGraph()
+ : firstVertexIncidences_{}, vertexEdgeIncidences_{}, edgeWeights_{}
+{
+}
+
+int WeightedEdgeGraph::addVertex()
+{
+ firstVertexIncidences_.push_back(-1);
+ return (int)firstVertexIncidences_.size() - 1;
+}
+
+void WeightedEdgeGraph::addEdge(const int vertex1, const int vertex2, const int weight)
+{
+ vertexEdgeIncidences_.emplace_back(vertex2, firstVertexIncidences_[vertex1]);
+ firstVertexIncidences_[vertex1] = (int)vertexEdgeIncidences_.size() - 1;
+ vertexEdgeIncidences_.emplace_back(vertex1, firstVertexIncidences_[vertex2]);
+ firstVertexIncidences_[vertex2] = (int)vertexEdgeIncidences_.size() - 1;
+ edgeWeights_.push_back(weight);
+}
+
+int WeightedEdgeGraph::dijkstra(const int source, const int target) const
+{
+ std::vector distances(firstVertexIncidences_.size(), std::numeric_limits::max());
+ auto compare = [&distances](int left, int right) { return distances[left] > distances[right]; };
+ std::priority_queue, decltype(compare)> queue{ compare };
+
+ distances[source] = 0;
+ queue.push(source);
+
+ while (!queue.empty())
+ {
+ int v{ queue.top() };
+ queue.pop();
+
+ int incidence{ firstVertexIncidences_[v] };
+ while (incidence > -1)
+ {
+ int neighbor{ vertexEdgeIncidences_[incidence].vertex };
+ int newDistance{ distances[v] + edgeWeights_[incidence >> 1] };
+ if (distances[neighbor] > newDistance)
+ {
+ distances[neighbor] = newDistance;
+ queue.push(neighbor);
+ }
+ incidence = vertexEdgeIncidences_[incidence].next;
+ }
+ }
+
+ return distances[target];
+}
diff --git a/tests/src/TestCases.cpp b/tests/src/TestCases.cpp
index 651b5bd..c063546 100644
--- a/tests/src/TestCases.cpp
+++ b/tests/src/TestCases.cpp
@@ -33,6 +33,7 @@
#include
#include
#include
+#include
#include
#define REQUIRE_MESSAGE(cond, msg) if (!(cond)) { INFO(msg); REQUIRE(cond); }
@@ -252,6 +253,23 @@ TEST_CASE("[WarehouseWoesTests]")
}
}
+TEST_CASE("[ReindeerMazeTests]")
+{
+ TestContext test;
+ SECTION("FullData")
+ {
+ test.run(std::make_unique(), 72400, 0, test.getInputPaths());
+ }
+ SECTION("ExampleData")
+ {
+ test.run(std::make_unique(), 7036, 0, test.getExampleInputPaths());
+ }
+ SECTION("ExampleData2")
+ {
+ test.run(std::make_unique(2), 11048, 0, test.getExampleInputPaths());
+ }
+}
+
TEST_CASE("[LanPartyTests]")
{
TestContext test;