Advent of Code 2016, Day 23: Safe Cracking

#ruby #advent of code 2016

Part A

On Day 23 we are playing again with assembunny code, but now we have one more instruction tgl x.

Here is the full solution:

require "io/console"
data = File.readlines("23.txt", chomp: true)

registers = { "a" => 7, "b" => 0, "c" => 0, "d" => 0 }
pc = 0
iterations = 0

while pc < data.size
  line = data[pc]
  jump = false

  if line =~ /cpy (-?\d+) ([a-d])/
    value = Regexp.last_match[1].to_i
    register = Regexp.last_match[2]

    registers[register] = value
  elsif line =~ /cpy ([a-d]) ([a-d])/
    source_register = Regexp.last_match[1]
    target_register = Regexp.last_match[2]

    registers[target_register] = registers[source_register]
  elsif line =~ /inc ([a-d])/
    register = Regexp.last_match[1]
    registers[register] += 1
  elsif line =~ /dec ([a-d])/
    register = Regexp.last_match[1]
    registers[register] -= 1
  elsif line =~ /jnz (-?\d+) (-?\d+)/
    value = Regexp.last_match[1].to_i
    offset = Regexp.last_match[2].to_i

    if value != 0
      pc += offset
      jump = true
    end
  elsif line =~ /jnz ([a-d]) (-?\d+)/
    register = Regexp.last_match[1]
    offset = Regexp.last_match[2].to_i

    if registers[register] != 0
      pc += offset
      jump = true
    end
  elsif line =~ /jnz (-?\d+) ([a-d])/
    register = Regexp.last_match[2]
    offset = registers[register]
    value = Regexp.last_match[1].to_i

    if value != 0
      pc += offset
      jump = true
    end
  elsif line =~ /tgl ([a-d])/
    register = Regexp.last_match[1]
    offset = registers[register]

    if data[pc + offset]
      case data[pc + offset][0..2]
      when "inc"
        data[pc + offset][0..2] = "dec"
      when "dec"
        data[pc + offset][0..2] = "inc"
      when "cpy"
        data[pc + offset][0..2] = "jnz"
      when "jnz"
        data[pc + offset][0..2] = "cpy"
      when "tgl"
        data[pc + offset][0..2] = "inc"
      end
    end
  end

  pc += 1 unless jump

  iterations += 1
end

puts registers.inspect

It is slightly modified solution for Day 12.

Part B

Now in second part we need to execute the same code, but now A register should be set to 12 instead of 7. There is an information in the description that now our computer is overheating. And yeah, if you run it is actually computing but not returning any value. There is a hint that we should use multiply instead of adding one.

So the task is to reverse engineer our program that looks like this:

cpy a b
dec b
cpy a d
cpy 0 a
cpy b c
inc a
dec c
jnz c -2
dec d
jnz d -5
dec b
cpy b c
cpy c d
dec d
inc c
jnz d -2
tgl c
cpy -16 c
jnz 1 c
cpy 84 c
jnz 75 d
inc a
inc d
jnz d -2
inc c
jnz c -5

We can implement a method to print out debugger screen from out computer with registers:

def render(data, pc, registers)
  $stdout.clear_screen
  data.each_with_index do |line, index|
    puts [index == pc ? "=> " : "   ", index.to_s.rjust(2), line].join(" ")
  end
  puts "A = #{registers["a"]}, B = #{registers["b"]}, C = #{registers["c"]}, D = #{registers["d"]}, PC = #{pc}"
end

I used this method to execute code line by line and see what it is doing. And on first 10 lines it is computing factorial, but using only addition. I didn’t execute that many lines, but I think it is a factorial of what is in the A register. For 7 it is fine, but for 12 it is a large number and a lot of add instructions.

What we can do is to implement multiplication instruction and rewrite our program. Here is updated solution with mul instruction:

require "io/console"
data = File.readlines("23mul.txt", chomp: true)

registers = { "a" => 12, "b" => 0, "c" => 0, "d" => 0 }
pc = 0

def render(data, pc, registers)
  $stdout.clear_screen
  data.each_with_index do |line, index|
    puts [index == pc ? "=> " : "   ", index.to_s.rjust(2), line].join(" ")
  end
  puts "A = #{registers["a"]}, B = #{registers["b"]}, C = #{registers["c"]}, D = #{registers["d"]}, PC = #{pc}"
end

iterations = 0

while pc < data.size
  line = data[pc]
  jump = false

  if line =~ /cpy (-?\d+) ([a-d])/
    value = Regexp.last_match[1].to_i
    register = Regexp.last_match[2]

    registers[register] = value
  elsif line =~ /cpy ([a-d]) ([a-d])/
    source_register = Regexp.last_match[1]
    target_register = Regexp.last_match[2]

    registers[target_register] = registers[source_register]
  elsif line =~ /inc ([a-d])/
    register = Regexp.last_match[1]
    registers[register] += 1
  elsif line =~ /dec ([a-d])/
    register = Regexp.last_match[1]
    registers[register] -= 1
  elsif line =~ /jnz (-?\d+) (-?\d+)/
    value = Regexp.last_match[1].to_i
    offset = Regexp.last_match[2].to_i

    if value != 0
      pc += offset
      jump = true
    end
  elsif line =~ /jnz ([a-d]) (-?\d+)/
    register = Regexp.last_match[1]
    offset = Regexp.last_match[2].to_i

    if registers[register] != 0
      pc += offset
      jump = true
    end
  elsif line =~ /jnz (-?\d+) ([a-d])/
    register = Regexp.last_match[2]
    offset = registers[register]
    value = Regexp.last_match[1].to_i

    if value != 0
      pc += offset
      jump = true
    end
  elsif line =~ /tgl ([a-d])/
    register = Regexp.last_match[1]
    offset = registers[register]

    if data[pc + offset]
      case data[pc + offset][0..2]
      when "inc"
        data[pc + offset][0..2] = "dec"
      when "dec"
        data[pc + offset][0..2] = "inc"
      when "cpy"
        data[pc + offset][0..2] = "jnz"
      when "jnz"
        data[pc + offset][0..2] = "cpy"
      when "tgl"
        data[pc + offset][0..2] = "inc"
      end
    end
  # additional instructions to execute multiplication instantly, only for 23mul.txt updated instruction set
  elsif line =~ /mul ([a-d]) ([a-d])/
    register_1 = Regexp.last_match[1]
    register_2 = Regexp.last_match[2]

    registers[register_1] *= registers[register_2]
  elsif line =~ /nop/

  end

  pc += 1 unless jump

  iterations += 1
end

puts registers.inspect

And here is updated program:

cpy a b
dec b
cpy a d
cpy 0 a
cpy b c
nop
mul c d
cpy c a
cpy 0 c
cpy 0 d
dec b
cpy b c
cpy c d
dec d
inc c
jnz d -2
tgl c
cpy -16 c
jnz 1 c
cpy 84 c
jnz 75 d
inc a
inc d
jnz d -2
inc c
jnz c -5

I also added nop instruction to just keep the same number of lines in this program, so all jump instructions work the same.