diff --git a/2018/ruby/day_19.rb b/2018/ruby/day_19.rb
new file mode 100644
index 0000000..63c8fc5
--- /dev/null
+++ b/2018/ruby/day_19.rb
@@ -0,0 +1,118 @@
+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
+
+Instruction = Struct.new(:opcode, :args)
+
+class Program
+ def self.parse(input)
+ lines = input.lines
+ ip = lines.shift[/^#ip (\d)+$/, 1].to_i
+ instructions = lines.map {|line|
+ opcode, *args = line.split
+ opcode = opcode.to_sym
+ args = args.map(&:to_i)
+ Instruction.new(opcode, args)
+ }
+ new(ip, instructions)
+ end
+
+ attr_reader :registers
+
+ def initialize(ip, instructions)
+ @ip, @instructions = ip, instructions
+ @registers = Array.new(6, 0)
+ end
+
+ def run
+ return enum_for(__method__) unless block_given?
+
+ while (0...@instructions.length).cover?(@registers[@ip])
+ instruction = @instructions.fetch(@registers[@ip])
+ OPCODES.fetch(instruction.opcode)[*instruction.args][@registers]
+ yield self
+ @registers[@ip] += 1
+ end
+ end
+end
+
+class TestProgram < Minitest::Test
+ def test_program
+ program = Program.parse(<<~PROGRAM)
+ #ip 0
+ seti 5 0 1
+ seti 6 0 2
+ addi 0 1 0
+ addr 1 2 3
+ setr 1 0 0
+ seti 8 0 4
+ seti 9 0 5
+ PROGRAM
+
+ run = program.run
+ loop do
+ run.next
+ end
+
+ assert_equal [7, 5, 6, 0, 0, 9], program.registers
+ end
+end
+
+def solve(input)
+ program = Program.parse(input)
+ run = program.run
+ loop do
+ run.next
+ end
+ program.registers[0]
+end
+
+if __FILE__ == $0
+ require "minitest/autorun" and exit if ENV["TEST"]
+
+ puts solve(ARGF.read)
+end