r/adventofcode • u/musifter • 8h ago
Other [2025 Day 17] In Review (Chronospatial Computer)
Today we find ourselves suddenly falling through time, and need to fix the device. Which has that trope of unfolding into an entire computer. I like this old school touch (I like this trope, like in Brain Donors with the laptop that unfolds into a desk with CRT and printer). Nowadays, the small device would just produce a large holographic display up in the air to poke at and I'd be suffering from sympathetic Gorilla Arm Syndrome.
The input for this one is a list of initial values for the three registers, followed by the code (which is comma delimited integers, like the Intcode problems of 2019). We're given that this is a 3-bit machine, but the registers can be bigger (but with my input, only the bottom three bits of B and C ever matter, and so only A makes use of that).
For part 1, we just need to run the program and collect the output. This is one of the rare problems with a string answer, in this case, it's got commas between the output values. So I did my usual, hash of anonymous subs dispatch table approach:
sub c_op ($) {my $op = shift; return (($op & 04) ? $reg[$op & ~04] : $op)}
my %opcodes =
(
0 => sub {my $op = c_op shift; $reg[A] = $reg[A] >> $op}, # adv
6 => sub {my $op = c_op shift; $reg[B] = $reg[A] >> $op}, # bdv
7 => sub {my $op = c_op shift; $reg[C] = $reg[A] >> $op}, # cdv
1 => sub {my $op = shift; $reg[B] ^= $op}, # bxl
4 => sub {my $op = shift; $reg[B] ^= $reg[C]}, # bxc
2 => sub {my $op = c_op shift; $reg[B] = $op & 7}, # bst
5 => sub {my $op = c_op shift; push(@out, $op & 7)}, # out
3 => sub {my $op = shift; $ip = ($reg[A]) ? $op - 2 : $ip}, # jnz
);
Could have been an array, but this way I get to reorder the instructions to pair them. With that, running the machine just becomes:
while ($ip < $#code) {
&{$opcodes{$code[$ip]}}( $code[$ip+1] );
$ip += 2;
}
Part 2 reveals why the output is that way... we're asked to find the value for register A that makes it a quine) (technically it's not because supplying the value for A is an input, but the job is the same).
The trick with doing non-trivial quines is outputting the command to print. After all, the print can print the rest. For example, if I want to do a 1-line quine in dc, I can output a number (without a newline) with n, and output that number as a character string (ASCII coded on bytes) with P. A duplicate d is needed because n removes the value from the stack. And so the number that's needed is 0x646E50 (the packed ASCII encoding of dnP), which is 6581840 decimal:
dc -e'6581840dnP'
You can also similarly do a two-line quine, using p (which outputs the top value with a newline, but leaves it on the stack).
And so we should expect something similar from the code in this problem. Given that, all the inputs are probably something like this:
bst A # B = bottom 3-bits of A
...
adv 3 # A >>= 3
...
out B # output bottom 3-bits of B (could also be out C)
jnz 0 # loop until A is gone
A loop that decodes the values 3-bits at a time in A (from the little end). Given that, it's probably safe to believe that everyone's code is 16 long... which is 48-bits to encode in A (17 would be a bus error, and 18 would be problematic for floating point native languages).
And so with that in mind, I made the code to run the machine into a function that takes a value to initialize A with and breaks to return the first output value, which allows for calculating things 3-bits (one digit) at a time:
my %poss = (0 => 1);
foreach my $digit (reverse @code) {
my %new_poss;
# shift left possibilities one octal digit and try all 8 lower options
foreach my $val (map {($_<<3) .. ($_<<3) + 7} keys %poss) {
$new_poss{$val}++ if ($digit == &try_code( $val ));
}
%poss = %new_poss;
}
Here I brute force over all 8 possibilities for the next 3-bits, for each of the numbers that worked last time. In the end, there at 42 values in that hash that work, and I just take the minimum one for the answer.
I did do dc solutions for this one... they are absolute monsters (part 2 is about 350 characters). For tracking the possible values, I do it on the stack (the new values go into a register stack, and then I dump that out onto the main stack for the next loop) and there's no duplication of them (everything is 3-bit aligned and won't overlap), so technically, that hash could have just been a list in the Perl too.
This was a fun one, even though the reverse engineering wasn't much, the quine aspect was a nice touch.
