AdventOfCode2024/src/ChronospatialComputer.cpp

164 lines
6.3 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/ChronospatialComputer.hpp>
#include <sstream>
#include <string>
ChronospatialComputer::ChronospatialComputer()
: state_{}, program_{}, instructions_{
std::make_unique<ChronospatialComputerDivisionInstruction>(0), // adv
std::make_unique<ChronospatialComputerXorLiteralInstruction>(), // bxl
std::make_unique<ChronospatialComputerModuloInstruction>(), // bst
std::make_unique<ChronospatialComputerJumpInstruction>(), // jnz
std::make_unique<ChronospatialComputerXorRegisterInstruction>(), // bxc
std::make_unique<ChronospatialComputerOutInstruction>(), // out
std::make_unique<ChronospatialComputerDivisionInstruction>(1), // bdv
std::make_unique<ChronospatialComputerDivisionInstruction>(2) // cdv
}
{
}
const std::string ChronospatialComputer::getPuzzleName() const
{
return "Chronospatial Computer";
}
const int ChronospatialComputer::getPuzzleDay() const
{
return 17;
}
void ChronospatialComputer::processDataLine(const std::string& line)
{
std::istringstream stream{ line };
std::string token{};
char c;
int value;
if (stream >> token)
{
if (token == "Register")
{
stream >> c >> token >> value;
// c must be 'A', 'B', or 'C'.
size_t regIndex{ static_cast<size_t>(c - 65) };
state_.registers[regIndex] = value;
}
else
{
while (stream >> value)
{
program_.push_back(value);
// Streams a comma from between values.
stream >> c;
}
}
}
}
void ChronospatialComputer::finish()
{
runProgram(program_, state_);
part1 = state_.output.str();
part2 = findQuineState();
}
void ChronospatialComputer::runProgram(const std::vector<int>& program, ChronospatialComputerState& state) const
{
state.instructionPointer = 0;
while (state.instructionPointer < program.size())
{
instructions_[program[state.instructionPointer]]->run(state, program[state.instructionPointer + 1]);
}
}
int64_t ChronospatialComputer::findQuineState()
{
// The following masks and output patterns are specific to a single program and have been precalculated manually.
// The given Chronospatial Computer program essentially does the following calculations.
//
// A' = A >> 3
// OUT = (((A mod 8) xor 4) xor (A shr ((A mod 8) xor 1))) mod 8,
//
// where A is the initial value of register A, A' is the value of register A when program flow reaches the jump
// instruction at the end of the program, and OUT is the single value written to the output when program flow reaches
// that same jump instruction.
//
// So given a desired output OUT, the number to be appended to the accumulated result can be found by matching the
// output patterns below against the masked trailing bits of the accumulated result, see the calculations below.
// Array index corresponds to number to append.
std::array<int, 8> masks{ 0b1, 0b0, 0b111, 0b11, 0b11100, 0b1110, 0b1110000, 0b111000 };
// Array index is the output number.
// First pair element is the number appended to register A.
// Second pair element is the pattern to match against register A.
std::array<std::vector<std::pair<int, int>>, 8> outputPatterns
{ {
// output 0
{ { 0, 0b1 }, { 2, 0b110 }, { 4, 0b00000 }, { 5, 0b0010 }, { 6, 0b0100000 }, { 7, 0b0110000 } },
// output 1
{ { 2, 0b111 }, { 3, 0b11 }, { 4, 0b00100 }, { 5, 0b0000 }, { 6, 0b0110000 }, { 7, 0b010000 } },
// output 2
{ { 2, 0b100 }, { 4, 0b01000 }, { 5, 0b0110 }, { 6, 0b0000000 }, { 7, 0b001000 } },
// output 3
{ { 2, 0b101 }, { 3, 0b10 }, { 4, 0b01100 }, { 5, 0b0100 }, { 6, 0b0010000 }, { 7, 0b000000 } },
// output 4
{ { 0, 0b0 }, { 1, 0b0 }, { 2, 0b010 }, { 4, 0b10000 }, { 5, 0b1010 }, { 6, 0b1100000 }, { 7, 0b111000 } },
// output 5
{ { 2, 0b011 }, { 3, 0b01 }, { 4, 0b10100 }, { 5, 0b1000 }, { 6, 0b1110000 }, { 7, 0b110000 } },
// output 6
{ { 2, 0b000 }, { 4, 0b11000 }, { 5, 0b1110 }, { 6, 0b1000000 }, { 7, 0b101000 } },
// output 7
{ { 2, 0b001 }, { 3, 0b00 }, { 4, 0b11100 }, { 5, 0b1100 }, { 6, 0b1010000 }, { 7, 0b100000 } }
} };
int64_t result{ 0 };
size_t i{ program_.size() };
int lastAppend{ -1 };
// Appends 3-digit binary numbers to the end of 'result' that will cause the program to output the next desired
// number (from 'program_'). The target output numbers are considered in reverse order.
while (i > 0)
{
auto& patterns = outputPatterns[program_[i - 1]];
auto it = std::find_if(patterns.begin(), patterns.end(),
[&lastAppend, &result, &masks](auto& x) {
return lastAppend < x.first && (result & masks[x.first]) == x.second; });
if (it != patterns.end())
{
// Appends the next number 'it->first' that will result in the desired output 'program_[i - 1]'.
lastAppend = -1;
result = (result << 3) + it->first;
i--;
}
else if (i < program_.size())
{
// Backtrack because no 3-digit number can be appended to yield the desired output.
lastAppend = result & 0b111;
result >>= 3;
i++;
}
else
{
// This should not execute, it's a safeguard against an infinite loop in case of unexpected input.
return result;
}
}
return result;
}