// Solutions to the Advent Of Code 2024. // Copyright (C) 2024-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 const std::string GuardGallivant::getPuzzleName() const { return "Guard Gallivant"; } const int GuardGallivant::getPuzzleDay() const { return 6; } void GuardGallivant::processDataLine(const std::string& line) { auto pos = line.find(getStartChar()); if (pos != std::string::npos) { start_ = Point2{ static_cast(pos), static_cast(lines.size()) }; } LinesSolver::processDataLine(line); } void GuardGallivant::finish() { tracePath(); } constexpr size_t GuardGallivant::getStartDirectionIndex() { return 2; } constexpr char GuardGallivant::getStartChar() { return '^'; } constexpr char GuardGallivant::getObstructionChar() { return '#'; } constexpr char GuardGallivant::getEmptyChar() { return '.'; } void GuardGallivant::tracePath() { VisitGrid visitGrid{ lines.size(), lines[0].size() }; visitGrid.fill(false); PathGrid pathGrid{ lines.size(), lines[0].size() }; pathGrid.fill(0); // PathGrid for cycle tests, reused to avoid repeated memory allocation. PathGrid cyclePathGrid{ lines.size(), lines[0].size() }; size_t directionIndex{ getStartDirectionIndex() }; Point2 current{ start_ }; visitPosition(current, directionIndex, visitGrid); tracePosition(current, directionIndex, pathGrid); backtracePath(current, directionIndex, pathGrid); auto next = current + Point2::cardinalDirections[directionIndex]; while (isInBounds(next)) { if (!checkTurn(current, next, directionIndex, pathGrid)) { if (!visitGrid.cell(next)) { if (pathGrid.cell(current)[turnDirectionRight(directionIndex)]) { part2++; } else { setCharAt(next, getObstructionChar()); if (tracePathCycleTest(current, turnDirectionRight(directionIndex), pathGrid, cyclePathGrid)) { part2++; } setCharAt(next, getEmptyChar()); } } current = next; visitPosition(current, directionIndex, visitGrid); tracePosition(current, directionIndex, pathGrid); } next = current + Point2::cardinalDirections[directionIndex]; } } bool GuardGallivant::tracePathCycleTest(const Point2& startPosition, const size_t startDirectionIndex, PathGrid& mainPathGrid, PathGrid& cyclePathGrid) { cyclePathGrid.fill(0); size_t directionIndex{ startDirectionIndex }; Point2 current{ startPosition }; tracePosition(current, directionIndex, cyclePathGrid); backtracePath(current, directionIndex, cyclePathGrid); auto next = current + Point2::cardinalDirections[directionIndex]; while (isInBounds(next)) { if (!checkTurn(current, next, directionIndex, cyclePathGrid)) { if (mainPathGrid.cell(next)[directionIndex] || cyclePathGrid.cell(next)[directionIndex]) { return true; } current = next; tracePosition(current, directionIndex, cyclePathGrid); } next = current + Point2::cardinalDirections[directionIndex]; } return false; } bool GuardGallivant::checkTurn(const Point2& current, const Point2& next, size_t& directionIndex, PathGrid& pathGrid) { if (getCharAt(next) == getObstructionChar()) { directionIndex = turnDirectionRight(directionIndex); backtracePath(current, directionIndex, pathGrid); return true; } return false; } void GuardGallivant::visitPosition(const Point2& current, const size_t directionIndex, VisitGrid& visitGrid) { if (!visitGrid.cell(current)) { visitGrid.cell(current) = true; part1++; } } void GuardGallivant::tracePosition(const Point2& current, const size_t directionIndex, PathGrid& pathGrid) { pathGrid.cell(current)[directionIndex] = true; backtraceTurn(current, turnDirectionLeft(directionIndex), pathGrid); } void GuardGallivant::backtracePath(const Point2& current, const size_t directionIndex, PathGrid& pathGrid) { auto oppositeDirectionIndex = revertDirection(directionIndex); auto next = current + Point2::cardinalDirections[oppositeDirectionIndex]; while (isObstruction(next) && !pathGrid.cell(next)[directionIndex]) { tracePosition(next, oppositeDirectionIndex, pathGrid); next = next + Point2::cardinalDirections[oppositeDirectionIndex]; } } void GuardGallivant::backtraceTurn(const Point2& current, const size_t reverseTurnDirectionIndex, PathGrid& pathGrid) { auto left = current + Point2::cardinalDirections[reverseTurnDirectionIndex]; if (isObstruction(left)) { backtracePath(current, reverseTurnDirectionIndex, pathGrid); } } size_t GuardGallivant::turnDirectionRight(const size_t currentDirectionIndex) const { return currentDirectionIndex == 0 ? 3 : currentDirectionIndex - 1; } size_t GuardGallivant::turnDirectionLeft(const size_t currentDirectionIndex) const { return currentDirectionIndex == 3 ? 0 : currentDirectionIndex + 1; } size_t GuardGallivant::revertDirection(const size_t currentDirectionIndex) const { return currentDirectionIndex > 1 ? currentDirectionIndex - 2 : currentDirectionIndex + 2; } bool GuardGallivant::isObstruction(const Point2& position) const { return isInBounds(position) && getCharAt(position) == getObstructionChar(); }