01020304050607080910111213141516171819202122232425

Advent of Code

2024/17

Chronospatial Computer

in C#

by encse

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.

vic20.gif

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!

© 2025 Advent of Code is a registered trademark in the US Images provided by Bing image creator