// 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;
}