Code for the Advent of Code event
aoc advent-of-code
at rust 312 lines 6.5 kB view raw
1# frozen_string_literal: true 2 3module AoC 4 module Intcode 5 # An Intcode CPU. 6 class CPU 7 # @return [Hash<Integer, Symbol>] 8 OPS = { 9 1 => :add, 10 2 => :mult, 11 3 => :read, 12 4 => :write, 13 5 => :jnz, 14 6 => :jz, 15 7 => :lt, 16 8 => :eq, 17 9 => :modrel, 18 99 => :halt 19 }.freeze 20 21 # @return [Hash<Symbol, Integer>] 22 ARG_COUNTS = { 23 add: 3, 24 mult: 3, 25 read: 1, 26 write: 1, 27 jnz: 2, 28 jz: 2, 29 lt: 3, 30 eq: 3, 31 modrel: 1 32 }.tap { |h| h.default = 0 }.freeze 33 34 # @return [Hash<Integer, Symbol>] 35 MODES = { 36 0 => :addr, 37 1 => :immediate, 38 2 => :relative 39 }.freeze 40 41 # @return [Array<Integer>] 42 attr_reader :memory 43 44 # @return [Array<Integer>] 45 attr_reader :output 46 47 # @return [Queue] 48 attr_reader :input 49 50 # @param program [Array<Integer>] 51 # @param print_output [Boolean] 52 # @param debug [Boolean] 53 # @param memory [Array<Integer>] 54 # @param ip [Integer] Instruction pointer 55 # @param rb [Integer] Relative base 56 def initialize(program = nil, print_output = true, debug = false, memory = nil, ip = 0, rb = 0) # rubocop:disable Style/OptionalBooleanParameter 57 @ip = ip 58 @input = Queue.new 59 @output = [] 60 @debug = debug 61 @program = program&.dup 62 @memory = memory || @program&.dup 63 @halted = false 64 @relative_base = rb 65 @print_output = print_output 66 end 67 68 # @param enabled [Boolean] 69 # @return [self] 70 def debug!(enabled) 71 @debug = enabled 72 self 73 end 74 75 # @param file [Array<Integer>, String] 76 # @return [self] 77 def load!(file) 78 if file.is_a? Array # rubocop:disable Style/ConditionalAssignment 79 @program = file 80 else 81 @program = File.read(file).split(',').map(&:to_i) 82 end 83 84 @memory = @program.dup 85 self 86 end 87 88 # @param value [Integer, String, Array<Integer, String>] 89 # @return [self] 90 def input!(value) 91 case value 92 when Array 93 value.each { |v| input! v } 94 when String 95 value.chars.map(&:ord).each { |v| input! v } 96 else 97 @input.enq value 98 end 99 self 100 end 101 102 # @param enabled [Boolean] 103 # @return [self] 104 def print_output!(enabled) 105 @print_output = enabled 106 self 107 end 108 109 # @return [void] 110 def clear_output! 111 @output.clear 112 end 113 114 # @return [self] 115 def run! 116 @running = true 117 118 while @running 119 opcode = OPS[get_opcode] 120 print '%4d: [%05d] %6s ' % [@ip, read_mem(@ip), opcode.to_s.upcase] if @debug 121 result = send(opcode) 122 @ip += 1 + ARG_COUNTS[opcode] unless result == :jumped || result == :block 123 end 124 125 self 126 end 127 128 # @return [self] 129 def reset! 130 @memory = @program.dup 131 @ip = 0 132 @input = Queue.new 133 @output = [] 134 self 135 end 136 137 # @return [Boolean] 138 def running? 139 @running == true 140 end 141 142 # @return [Boolean] 143 def halted? 144 @halted == true 145 end 146 147 # @return [CPU] 148 def dup 149 CPU.new @program.dup, @print_output, @debug, @memory.dup, @ip, @relative_base 150 end 151 152 private 153 154 # @return [void] 155 def add 156 a = get_arg 1 157 print ', ' if @debug 158 b = get_arg 2 159 print ', ' if @debug 160 addr = get_addr 3 161 puts if @debug 162 @memory[addr] = a + b 163 end 164 165 # @return [void] 166 def mult 167 a = get_arg 1 168 print ', ' if @debug 169 b = get_arg 2 170 print ', ' if @debug 171 addr = get_addr 3 172 puts if @debug 173 @memory[addr] = a * b 174 end 175 176 # @return [void] 177 def read 178 if @input.empty? 179 @running = false 180 puts '(BLOCK)' if @debug 181 return :block 182 end 183 val = @input.deq 184 addr = get_addr 1 185 puts if @debug 186 @memory[addr] = val 187 end 188 189 # @return [void] 190 def write 191 val = get_arg 1 192 puts if @debug 193 output << val 194 puts val if @print_output 195 end 196 197 # @return [void] 198 def jnz 199 a = get_arg 1 200 print ', ' if @debug 201 addr = get_arg 2 202 puts if @debug 203 204 return if a == 0 205 @ip = addr 206 :jumped 207 end 208 209 # @return [void] 210 def jz 211 a = get_arg 1 212 print ', ' if @debug 213 addr = get_arg 2 214 puts if @debug 215 216 return unless a == 0 217 218 @ip = addr 219 :jumped 220 end 221 222 # @return [void] 223 def lt 224 a = get_arg 1 225 print ', ' if @debug 226 b = get_arg 2 227 print ', ' if @debug 228 addr = get_addr 3 229 puts if @debug 230 231 @memory[addr] = a < b ? 1 : 0 232 end 233 234 # @return [void] 235 def eq 236 a = get_arg 1 237 print ', ' if @debug 238 b = get_arg 2 239 print ', ' if @debug 240 addr = get_addr 3 241 puts if @debug 242 243 @memory[addr] = a == b ? 1 : 0 244 end 245 246 # @return [void] 247 def modrel 248 amount = get_arg 1 249 puts if @debug 250 251 @relative_base += amount 252 end 253 254 # @return [void] 255 def halt 256 @running = false 257 @halted = true 258 puts if @debug 259 end 260 261 # @param addr [Integer] 262 # @return [Integer] 263 def read_mem(addr) 264 @memory[addr] || 0 265 end 266 267 # @return [Integer] 268 def get_opcode # rubocop:disable Naming/AccessorMethodName 269 read_mem(@ip) % 100 270 end 271 272 # @param pos [Integer] 273 # @return [Symbol] 274 def get_mode(pos) 275 MODES[(read_mem(@ip) / (10**(pos + 1))) % 10] 276 end 277 278 # @param pos [Integer] 279 # @return [Integer] 280 def get_addr(pos) 281 val = read_mem(@ip + pos) 282 mode = get_mode pos 283 284 if mode == :relative 285 print "R#{val}=" if @debug 286 val += @relative_base 287 mode = :addr 288 end 289 290 print "##{val}" if mode == :addr && @debug 291 292 val 293 end 294 295 # @param pos [Integer] 296 # @return [Integer] 297 def get_arg(pos) 298 val = read_mem(@ip + pos) 299 mode = get_mode pos 300 301 if mode != :immediate 302 addr = get_addr pos 303 print '=' if @debug 304 val = read_mem addr 305 end 306 307 print val if @debug 308 val 309 end 310 end 311 end 312end