1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
require "minitest"
require "minitest/pride"

OPCODES = {
  addr: ->(a, b, c) { ->(registers) { registers[c] = registers[a] + registers[b] } },
  addi: ->(a, b, c) { ->(registers) { registers[c] = registers[a] + b } },
  mulr: ->(a, b, c) { ->(registers) { registers[c] = registers[a] * registers[b] } },
  muli: ->(a, b, c) { ->(registers) { registers[c] = registers[a] * b } },
  banr: ->(a, b, c) { ->(registers) { registers[c] = registers[a] & registers[b] } },
  bani: ->(a, b, c) { ->(registers) { registers[c] = registers[a] & b } },
  borr: ->(a, b, c) { ->(registers) { registers[c] = registers[a] | registers[b] } },
  bori: ->(a, b, c) { ->(registers) { registers[c] = registers[a] | b } },
  setr: ->(a, b, c) { ->(registers) { registers[c] = registers[a] } },
  seti: ->(a, b, c) { ->(registers) { registers[c] = a } },
  gtir: ->(a, b, c) { ->(registers) { registers[c] = (a > registers[b]) ? 1 : 0 } },
  gtri: ->(a, b, c) { ->(registers) { registers[c] = (registers[a] > b) ? 1 : 0 } },
  gtrr: ->(a, b, c) { ->(registers) { registers[c] = (registers[a] > registers[b]) ? 1 : 0 } },
  eqir: ->(a, b, c) { ->(registers) { registers[c] = (a == registers[b]) ? 1 : 0 } },
  eqri: ->(a, b, c) { ->(registers) { registers[c] = (registers[a] == b) ? 1 : 0 } },
  eqrr: ->(a, b, c) { ->(registers) { registers[c] = (registers[a] == registers[b]) ? 1 : 0 } },
}

class TestOpcodes < Minitest::Test
  def test_mulr
    registers = [3, 2, 1, 1]

    OPCODES[:mulr].call(2, 1, 2).call(registers)

    assert_equal [3, 2, 2, 1], registers
  end

  def test_addi
    registers = [3, 2, 1, 1]

    OPCODES[:addi].call(2, 1, 2).call(registers)

    assert_equal [3, 2, 2, 1], registers
  end

  def test_seti
    registers = [3, 2, 1, 1]

    OPCODES[:seti].call(2, 1, 2).call(registers)

    assert_equal [3, 2, 2, 1], registers
  end
end

def solve(input)
  samples = input.scan(/
                       Before:\s*((?~\n))\n
                       ((?~\n))\n
                       After:\s*((?~\n))\n
                       /mx)

  valid_opcodes = samples.map do |sample|
    before = sample[0].scan(/\d+/).map(&:to_i)
    instruction = sample[1].scan(/\d+/).map(&:to_i)
    after = sample[2].scan(/\d+/).map(&:to_i)
    opcode, *args = instruction

    opcodes = OPCODES.select {|_, instruction|
      registers = before.dup
      instruction[*args][registers]
      registers == after
    }.map(&:first)
    [opcode, opcodes]
  end

  # Part One
  # return valid_opcodes.count {|_,opcodes| opcodes.length >= 3 }

  opcode_map = {}
  until opcode_map.length == OPCODES.length
    number, opcodes = valid_opcodes.find {|_,opcodes| opcodes.length == 1 }
    opcode = opcodes[0]
    opcode_map[number] = opcode

    valid_opcodes.delete_if {|n,_| n == number }
    valid_opcodes.each do |_,opcodes|
      opcodes.delete(opcode)
    end
  end

  program = input[/[\d\s]+\Z/].lines.map {|line| line.scan(/\d+/).map(&:to_i) }.reject(&:empty?)
  registers = [0, 0, 0, 0]
  program.each do |instruction|
    number, *args = instruction
    opcode = opcode_map.fetch(number)
    OPCODES.fetch(opcode)[*args][registers]
  end
  registers[0]
end

if __FILE__ == $0
  require "minitest/autorun" and exit if ENV["TEST"]

  puts solve(ARGF.read)
end