164 lines
6.3 KiB
C++
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;
|
|
}
|