class Intcode attr_reader :memory, :pc, :stopped, :input, :output def initialize(memory) @initial_memory = memory @memory = memory.clone @pc = 0 @stopped = true @input = [] @output = [] end def self.for(string) new string.split(",").map(&:to_i) end def run! @stopped = false @pc = 0 step! until stopped end def reset! @memory = @initial_memory.clone @pc = 0 @output = [] @input = [] end def step! log "pc: #{pc}, instruction: #{instruction}" case opcode when 1 add! when 2 multiply! when 3 read_input! when 4 write_output! when 5 jump_if_true! when 6 jump_if_false! when 7 less_than! when 8 equals! when 99 halt! else raise "invalid opcode #{opcode} at #{pc}" end end def parameters parameter_modes.each_with_index.map do |mode, index| case mode when 0 peek(pc + 1 + index) when 1 pc + 1 + index else raise "Invalid parameter mode #{mode}" end end end def add! log "adding #{peek(parameters[0])} and #{(parameters[1])}" poke(parameters[2], peek(parameters[0]) + peek(parameters[1])) @pc += 4 end def multiply! log "multiplying #{peek(parameters[0])} and #{peek(parameters[1])}" poke(parameters[2], peek(parameters[0]) * peek(parameters[1])) @pc += 4 end def read_input! i = input.shift log "reading input #{i} into #{parameters[0]}" poke(parameters[0], i) @pc += 2 end def write_output! o = peek(parameters[0]) log "outputting #{o} from #{parameters[0]}" output.push o @pc += 2 end def jump_if_true! predicate = peek(parameters[0]) log "jumping to #{peek(parameters[1])} if #{predicate}" @pc = predicate > 0 ? peek(parameters[1]) : @pc + 3 end def jump_if_false! predicate = peek(parameters[0]) log "jumping to #{peek(parameters[1])} unless #{predicate}" @pc = predicate == 0 ? peek(parameters[1]) : @pc + 3 end def less_than! lt = peek(parameters[0]) < peek(parameters[1]) poke(parameters[2], lt ? 1 : 0) @pc += 4 end def equals! eq = peek(parameters[0]) == peek(parameters[1]) poke(parameters[2], eq ? 1 : 0) @pc += 4 end def halt! log "halting!" @stopped = true end def instruction peek pc end def opcode instruction % 100 end def parameter_modes [ instruction / 100 % 10, instruction / 1000 % 10, instruction / 10000 % 10, ] end def peek(address) memory[address] end def poke(address, value) log "poking #{value} into #{address}" memory[address] = value end def log(message) puts message if logging? end def logging? false end end