--- Day 17: Chronospatial Computer ---
The Historians push the button on their strange device, but this time, you all just feel like you're falling.
"Situation critical", the device announces in a familiar voice. "Bootstrapping process failed. Initializing debugger...."
The small handheld device suddenly unfolds into an entire computer! The Historians look around nervously before one of them tosses it to you.
This seems to be a 3-bit computer: its program is a list of 3-bit numbers (0 through 7), like
0,1,2,3
. The computer also has three registers namedA
,B
, andC
, but these registers aren't limited to 3 bits and can instead hold any integer.The computer knows eight instructions, each identified by a 3-bit number (called the instruction's opcode). Each instruction also reads the 3-bit number after it as an input; this is called its operand.
A number called the instruction pointer identifies the position in the program from which the next opcode will be read; it starts at
0
, pointing at the first 3-bit number in the program. Except for jump instructions, the instruction pointer increases by2
after each instruction is processed (to move past the instruction's opcode and its operand). If the computer tries to read an opcode past the end of the program, it instead halts.So, the program
0,1,2,3
would run the instruction whose opcode is0
and pass it the operand1
, then run the instruction having opcode2
and pass it the operand3
, then halt.There are two types of operands; each instruction specifies the type of its operand. The value of a literal operand is the operand itself. For example, the value of the literal operand
7
is the number7
. The value of a combo operand can be found as follows:
- Combo operands
0
through3
represent literal values0
through3
.- Combo operand
4
represents the value of registerA
.- Combo operand
5
represents the value of registerB
.- Combo operand
6
represents the value of registerC
.- Combo operand
7
is reserved and will not appear in valid programs.The eight instructions are as follows:
The
adv
instruction (opcode0
) performs division. The numerator is the value in theA
register. The denominator is found by raising 2 to the power of the instruction's combo operand. (So, an operand of2
would divideA
by4
(2^2
); an operand of5
would divideA
by2^B
.) The result of the division operation is truncated to an integer and then written to theA
register.The
bxl
instruction (opcode1
) calculates the bitwise XOR of registerB
and the instruction's literal operand, then stores the result in registerB
.The
bst
instruction (opcode2
) calculates the value of its combo operand modulo 8 (thereby keeping only its lowest 3 bits), then writes that value to theB
register.The
jnz
instruction (opcode3
) does nothing if theA
register is0
. However, if theA
register is not zero, it jumps by setting the instruction pointer to the value of its literal operand; if this instruction jumps, the instruction pointer is not increased by2
after this instruction.The
bxc
instruction (opcode4
) calculates the bitwise XOR of registerB
and registerC
, then stores the result in registerB
. (For legacy reasons, this instruction reads an operand but ignores it.)The
out
instruction (opcode5
) calculates the value of its combo operand modulo 8, then outputs that value. (If a program outputs multiple values, they are separated by commas.)The
bdv
instruction (opcode6
) works exactly like theadv
instruction except that the result is stored in theB
register. (The numerator is still read from theA
register.)The
cdv
instruction (opcode7
) works exactly like theadv
instruction except that the result is stored in theC
register. (The numerator is still read from theA
register.)Here are some examples of instruction operation:
- If register
C
contains9
, the program2,6
would set registerB
to1
.- If register
A
contains10
, the program5,0,5,1,5,4
would output0,1,2
.- If register
A
contains2024
, the program0,1,5,4,3,0
would output4,2,5,6,7,7,7,7,3,1,0
and leave0
in registerA
.- If register
B
contains29
, the program1,7
would set registerB
to26
.- If register
B
contains2024
and registerC
contains43690
, the program4,0
would set registerB
to44354
.The Historians' strange device has finished initializing its debugger and is displaying some information about the program it is trying to run (your puzzle input). For example:
Register A: 729 Register B: 0 Register C: 0 Program: 0,1,5,4,3,0
Your first task is to determine what the program is trying to output. To do this, initialize the registers to the given values, then run the given program, collecting any output produced by
out
instructions. (Always join the values produced byout
instructions with commas.) After the above program halts, its final output will be4,6,3,5,6,3,5,2,1,0
.Using the information provided by the debugger, initialize the registers to the given values, then run the program. Once it halts, what do you get if you use commas to join the values it output into a single string?
The first step is to read the input.
show (A;B;C;::;P):{get last ": "vs x} each read0 `17.txt 729 0 0 :: 0 1 5 4 3 0
Now, define functions for all of the various operations. Each will take the current instruction pointer and the operand.
We define two helper functions, one to convert from a literal operand to a combo operand, and one for xor, as it isn't built in.
combo:{(0 1 2 3,A,B,C)x}
xor:{2 sv (<>)./:2 vs (x;y)}
Now, define each of the 8 instructions, and a list storing them all.
adv:{A::A div prd combo[y]#2; x+2}
bxl:{B::xor[B;y]; x+2}
bst:{B::combo[y] mod 8; x+2}
jnz:{$[A=0; x+2; y]}
bxc:{[x;y] B::xor[B;C]; x+2}
OUT:(); out:{OUT,:combo[y] mod 8; x+2}
bdv:{B::A div prd combo[y]#2; x+2}
cdv:{C::A div prd combo[y]#2; x+2}
ops:(adv;bxl;bst;jnz;bxc;out;bdv;cdv)
They are all simple to implement, directly translating the specification for each from the problem description. We are storing A, B, C, and the OUT variable as globals. Passing around a state "object" would also work, but would be slightly clunkier.
Now, we need to run the "program".
{x<count P}{ops[P x;x;P x+1]}\0 0 2 4 0 2 4 0 2 4 0 2 4 0 2 4 0 2 4 0 2 4 0 2 4 0 2 4 0 2 4 6
While the instruction pointer is within range, we select the function required, and call it.
Now, we can view the output.
","sv string OUT
--- Part Two ---
Digging deeper in the device's manual, you discover the problem: this program is supposed to output another copy of the program! Unfortunately, the value in register
A
seems to have been corrupted. You'll need to find a new value to which you can initialize registerA
so that the program's output instructions produce an exact copy of the program itself.For example:
Register A: 2024 Register B: 0 Register C: 0 Program: 0,3,5,4,3,0
This program outputs a copy of itself if register
A
is instead initialized to117440
. (The original initial value of registerA
,2024
, is ignored.)What is the lowest positive initial value for register
A
that causes the program to output a copy of itself?
Let's wrap the process in a function, that takes in the value for A, and returns the OUT value.
run:{A::x; B::C::0; OUT::(); {x<count P}{ops[P x;x;P x+1]}/0; OUT}
Now, set the program to the new example.
P: 0,3,5,4,3,0
We can check that with an input of 117440 it should return itself.
run 117440
There are various ways to approach this, but one would be to guess that given we seem to be working with base 8 and with bit operations (shifting/xor) we should examine the base 8 representation of A.
8 vs 117440 3 4 5 3 0 0
This doesn't seem that helpful, other than noting it is the same length as the program.
We can also try encoding from base 8, and seeing what happens.
run each 8 sv' (1 2 3;1 2 3 4;1 2 3 4 5) 2 1 0 3 2 1 0 4 3 2 1 0
This seems useful. It seems like appending a new value prepends a value to the output. This makes sense if it is doing some sort of process from the low bits to the high bits.
Let's write a function that takes a partial input and searches for new values that partially match the program.
new:{new where (neg[1+count x]#P)~/:run each 8 sv' new:x,/:til 8}
And run it count P
times starting from the empty list.
count[P] {raze new each x}/ enlist til 0 3 4 5 3 0 0 3 4 5 3 0 1 3 4 5 3 0 2 3 4 5 3 0 3 3 4 5 3 0 4 3 4 5 3 0 5 3 4 5 3 0 6 3 4 5 3 0 7
We can see we get the base 8 value from before. All that's left is to convert back to integers and take the minimum.
min 8 sv' count[P] {raze new each x}/ enlist til 0 117440
(A;B;C;::;P):{get last ": "vs x} each read0 `17.txt
combo:{(0 1 2 3,A,B,C)x}
xor:{2 sv (<>)./:2 vs (x;y)}
adv:{A::A div prd combo[y]#2; x+2}
bxl:{B::xor[B;y]; x+2}
bst:{B::combo[y] mod 8; x+2}
jnz:{$[A=0; x+2; y]}
bxc:{[x;y] B::xor[B;C]; x+2}
OUT:(); out:{OUT,:combo[y] mod 8; x+2}
bdv:{B::A div prd combo[y]#2; x+2}
cdv:{C::A div prd combo[y]#2; x+2}
ops:(adv;bxl;bst;jnz;bxc;out;bdv;cdv)
{x<count P}{ops[P x;x;P x+1]}\0
","sv string OUT
run:{A::x; B::C::0; OUT::(); {x<count P}{ops[P x;x;P x+1]}/0; OUT}
new:{new where (neg[1+count x]#P)~/:run each 8 sv' new:x,/:til 8}
min 8 sv' count[P] {raze new each x}/ enlist til 0