155 lines
4.4 KiB
C++
155 lines
4.4 KiB
C++
// 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 <http://www.gnu.org/licenses/>.
|
|
|
|
#include <aoc/RestroomRedoubt.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <functional>
|
|
#include <numeric>
|
|
#include <sstream>
|
|
|
|
#include <aoc/common/Math.hpp>
|
|
#include <aoc/common/Point2.hpp>
|
|
|
|
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<int>{});
|
|
|
|
if (runPart2_)
|
|
{
|
|
findEasterEgg();
|
|
}
|
|
}
|
|
|
|
constexpr int RestroomRedoubt::getNPredictionSeconds()
|
|
{
|
|
return 100;
|
|
}
|
|
|
|
constexpr int RestroomRedoubt::getEasterEggThreshold()
|
|
{
|
|
return 25;
|
|
}
|
|
|
|
void RestroomRedoubt::findEasterEgg()
|
|
{
|
|
std::array<int, 2> patterns{ 0, 0 };
|
|
std::array<int, 2> sizes{ width_, height_ };
|
|
for (size_t i = 0; i < 2; i++)
|
|
{
|
|
int seconds{ 0 };
|
|
while (patterns[i] == 0)
|
|
{
|
|
std::vector<int> 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;
|
|
}
|