// 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 ChronospatialComputer::ChronospatialComputer() : state_{}, program_{}, instructions_{ std::make_unique(0), // adv std::make_unique(), // bxl std::make_unique(), // bst std::make_unique(), // jnz std::make_unique(), // bxc std::make_unique(), // out std::make_unique(1), // bdv std::make_unique(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(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& 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 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>, 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; }