// 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 #include #include #include #include #include RestroomRedoubt::RestroomRedoubt(const int width, const int height, const bool runPart2) : width_{ width }, height_{ height }, halfWidth_{ width / 2 }, halfHeight_{ height / 2 }, runPart2_{ runPart2 } { quadrants_.fill(0); } const std::string RestroomRedoubt::getPuzzleName() const { return "Restroom Redoubt"; } const int RestroomRedoubt::getPuzzleDay() const { return 14; } void RestroomRedoubt::processDataLine(const std::string& line) { std::istringstream stream{ line }; char c; Point2 p, v; stream >> c >> c >> p.x >> c >> p.y >> c >> c >> v.x >> c >> v.y; // Calculates the resulting position, and shifts negative results back into the target range. Point2 position = p + v * getNPredictionSeconds(); position.x %= width_; if (position.x < 0) { position.x += width_; } position.y %= height_; if (position.y < 0) { position.y += height_; } // Determines the resulting quadrant from the position. if (position.x < halfWidth_) { if (position.y < halfHeight_) { quadrants_[0]++; } else if (position.y > halfHeight_) { quadrants_[1]++; } } else if (position.x > halfWidth_) { if (position.y < halfHeight_) { quadrants_[2]++; } else if (position.y > halfHeight_) { quadrants_[3]++; } } // Keeps track of the robot for part 2. robots_.push_back({ p, v }); } void RestroomRedoubt::finish() { part1 = std::accumulate(quadrants_.cbegin(), quadrants_.cend(), 1, std::multiplies{}); if (runPart2_) { findEasterEgg(); } } constexpr int RestroomRedoubt::getNPredictionSeconds() { return 100; } constexpr int RestroomRedoubt::getEasterEggThreshold() { return 25; } void RestroomRedoubt::findEasterEgg() { std::array patterns{ 0, 0 }; std::array sizes{ width_, height_ }; for (size_t i = 0; i < 2; i++) { int seconds{ 0 }; while (patterns[i] == 0) { std::vector buckets(sizes[i], 0); for (auto& robot : robots_) { buckets[robot.first[i]]++; robot.first[i] += robot.second[i]; if (robot.first[i] < 0) { robot.first[i] += sizes[i]; } else if (robot.first[i] >= sizes[i]) { robot.first[i] -= sizes[i]; } } auto it = std::find_if(buckets.cbegin(), buckets.cend(), [](int x) { return x > getEasterEggThreshold(); }); if (it != buckets.cend()) { patterns[i] = seconds; } seconds++; } } // Determines the iteration where the repeating patterns of horizontal and vertical bands overlap, that is // patterns_0 + width * x = patterns_1 + height * y // for two positive integers 'x' and 'y'. This is equivalent to finding 'x' such that // width * x ≡ patterns_1 - patterns_0 (modulo height) // We can determine the modular multiplicative inverse of 'width' modulo 'height' with the extended Euclidean // algorithm. const auto [gcd, invHeight, invWidth] = Math::extendedEuclid(height_, width_); // With that we can find 'x'. int x{ (invWidth * (patterns[1] - patterns[0])) % height_ }; // And calculate when the overlap occurs. part2 = patterns[0] + width_ * x; }