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...."
Visit the website for the full story and full puzzle description.
I'm not a big fan of disassembly tasks, but they come up almost every year. The first part of the problem, which I actually liked, was to write an interpreter for an elvish computer (i.e. one with a weird instruction set). This is really easy to do with a simple for loop and switching on the different opcodes.
The second half was a bit harder, as I had to understand what's going on in the program. We had to generate inputs that would force the program to print out its own source. Well, a quine is a computer program that takes no input and produces a copy of its own source code as its only output. Although our program was not a real quine, I couldn't help but smile when I read the problem description in the morning.
Fortunately, the algorithm was not that complicated once I realized that the next output number depends only on the lowest few bits of the input. Then the input gets shifted by a small amount, and this continues until the end.
There is some interconnection between the consecutive numbers, so one cannot just calculate the result independently for each. But I was able to come up with a recursive solution that generates the input backwards. Once we know the higher bits, we can try all combinations for the next 3 bits, and so on down to the first bit.
As an added bonus I implemented (part of) the emulator in VIC-20 BASIC. This is how it runs the second sample that prints out its own source code.
namespace AdventOfCode.Y2024.Day17;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
[ProblemName("Chronospatial Computer")]
class Solution : Solver {
enum Opcode {
Adv, Bxl, Bst, Jnz, Bxc, Out, Bdv, Cdv
}
public object PartOne(string input) {
var (state, program) = Parse(input);
return string.Join(",", Run(state, program));
}
public object PartTwo(string input) {
var (_, program) = Parse(input);
return GenerateA(program, program).Min();
}
List<long> Run(List<long> state, List<long> program) {
var combo = (int op) => op < 4 ? op : state[op - 4];
var res = new List<long>();
for (var ip = 0; ip < program.Count; ip += 2) {
switch ((Opcode)program[ip], (int)program[ip + 1]) {
case (Opcode.Adv, var op): state[0] = state[0] >> (int)combo(op); break;
case (Opcode.Bdv, var op): state[1] = state[0] >> (int)combo(op); break;
case (Opcode.Cdv, var op): state[2] = state[0] >> (int)combo(op); break;
case (Opcode.Bxl, var op): state[1] = state[1] ^ op; break;
case (Opcode.Bst, var op): state[1] = combo(op) % 8; break;
case (Opcode.Jnz, var op): ip = state[0] == 0 ? ip : op - 2; break;
case (Opcode.Bxc, var op): state[1] = state[1] ^ state[2]; break;
case (Opcode.Out, var op): res.Add(combo(op) % 8); break;
}
}
return res;
}
/*
Determines register A for the given output. The search works recursively and in
reverse order, starting from the last number to be printed and ending with the first.
*/
IEnumerable<long> GenerateA(List<long> program, List<long> output) {
if (!output.Any()) {
yield return 0;
yield break;
}
foreach (var ah in GenerateA(program, output[1..])) {
for (var al = 0; al < 8; al++) {
var a = ah * 8 + al;
if (Run([a, 0, 0], program).SequenceEqual(output)) {
yield return a;
}
}
}
}
(List<long> state, List<long> program) Parse(string input) {
var blocks = input.Split("\n\n").Select(ParseNums).ToArray();
return (blocks[0], blocks[1]);
}
List<long> ParseNums(string st) =>
Regex.Matches(st, @"\d+", RegexOptions.Multiline)
.Select(m => long.Parse(m.Value))
.ToList();
}
Please ☆ my repo if you like it!