9 votes

Day 9: Sensor Boost

Today's problem description: https://adventofcode.com/2019/day/9


Join the Tildes private leaderboard! You can do that on this page, by entering join code 730956-de85ce0c.

Please post your solutions in your own top-level comment. Here's a template you can copy-paste into your comment to format it nicely, with the code collapsed by default inside an expandable section with syntax highlighting (you can replace python with any of the "short names" listed in this page of supported languages):

<details>
<summary>Part 1</summary>

```python
Your code here.
```

</details>

7 comments

  1. Crespyl
    (edited )
    Link
    I spent a few minutes fighting the Crystal compiler to convert from Int32 to Int64s everywhere I needed them, so I hopped back over to my Ruby version to slap together the necessary bits to get...

    I spent a few minutes fighting the Crystal compiler to convert from Int32 to Int64s everywhere I needed them, so I hopped back over to my Ruby version to slap together the necessary bits to get the puzzle answers, and then back to Crystal afterwards.

    My Ruby Intcode module hasn't had quite as much love as the Crystal version, but at least changes there didn't require me to go back and update previous days (I have every day with Intcode linking into the same library).

    Day 9 Intcode VM

    VM Module

    require "./intcode.cr"
    
    module Intcode
      # This class holds the current state of a running interpreter: the memory
      # (`Array(Int64)`), the program counter, and a "halted/running" flag, and
      # buffers for input/output values.
      class VM
        # Name for debugging
        property name : String
    
        # Memory
        property mem : Array(Int64)
    
        # Registers
    
        # The program counter, the address in memory to read the next instruction
        property pc : Int64
    
        # Relative base, used in opcode 9 and relative addressing
        property rel_base : Int64
    
        # Status flags
    
        # Indicates whether the machine has executed a HALT instruction
        property halted : Bool
    
        # Indicates whether the machine is blocked waiting on input
        property needs_input : Bool
    
        # Input buffer
        property inputs : Array(Int64)
        # Output buffer
        property outputs : Array(Int64)
    
        def initialize(mem : Array(Int64))
          @name = "VM"
          @pc = 0
          @rel_base = 0
          @halted = false
          @needs_input = false
          @mem = Array(Int64).new(4096) { |i| mem.size > i ? mem[i] : 0_i64 }
          @inputs = [] of Int64
          @outputs = [] of Int64
        end
    
        # Get the value of the provided parameter, based on the addressing mode
        protected def read_param(p : Parameter)
          case p.mode
          when :position then mem[p.val]
          when :relative then mem[@rel_base + p.val]
          when :literal then p.val
          else
            raise "Unsupported addressing mode for #{p}"
          end
        end
    
        # Set the address indicated by the parameter to the given value
        protected def write_param_value(p : Parameter, val : Int64)
          case p.mode
          when :position then mem[p.val] = val
          when :relative then mem[@rel_base + p.val] = val
          when :literal then raise "Cannot write to literal"
          else
            raise "Unsupported addressing mode for #{p}"
          end
        end
    
        # Run the machine until it stops for some reason
        #
        # Returns a Symbol with the machine status after execution stops for any
        # reason, see `VM#status` for details
        def run
          log("Running...") if status == :ok
    
          while status == :ok
            # fetch the next Opcode and its Parameters
            opcode, params = Opcode.get_opcode_and_params(self, pc)
            raise "INVALID OPCODE AT #{pc}: #{mem[pc]}" unless opcode
    
            log("%5i %05i: %s" % [pc, mem[pc], opcode.debug(self, params)])
            opcode.exec(self, params)
          end
    
          log("Stopped (#{status})")
          return status
        end
    
        # Get the status of the VM
        #
        # Result can be any of the following
        #
        #   `:halted` => the machine executed a HALT instruction and will not run any
        #   further
        #
        #   `:needs_input` => the machine attempted to execut an INPUT instruction
        #   while the input buffer was empty. The machine can be resumed after
        #   adding input to the buffer with `VM#send_input`
        #
        #   `:pc_range_err` => the program counter ran past the end of the machines
        #   memory
        #
        #   `:ok` => the machine is ready to execute
        def status
          if @halted
            :halted
          elsif @needs_input
            :needs_input
          elsif @pc >= @mem.size
            :pc_range_err
          else
            :ok
          end
        end
    
        # Add a value to the back of the input buffer
        def send_input(val : Int64)
          inputs << val
          @needs_input = false
        end
    
        # Read a value from the amps output buffer, or nil if the buffer is empty
        def read_output()
          outputs.shift?
        end
    
        # Remove and return a value from the front of the input buffer.
        #
        # If the input buffer is empty, sets the appropriate flag and returns nil
        protected def get_input
          if input = inputs.shift?
            log "%50s" % "< #{input}"
            return input
          else
            @needs_input = true
            return nil
          end
        end
    
        # Add a value to the output buffer
        protected def write_output(val)
          outputs << val
          log "%50s" % "> #{val}"
        end
    
        private def log(msg)
          Intcode.log("(#{name}) #{msg}")
        end
    
        def self.from_file(filename)
          VM.new(Intcode.load_file(filename))
        end
    
        def self.from_string(str)
          VM.new(Intcode.read_intcode(str))
        end
      end
    
    end
    

    Opcodes

    require "./intcode.cr"
    
    module Intcode
      # Each supported opcode is represented as instance of the Opcode class, with
      # the implementation attached as a Proc, along with a function to generate
      # simplistic human-readable string for debugging
      class Opcode
        property sym : Symbol
        property size : Int32
        property impl : Proc(VM, Array(Parameter), Int64)
        property disasm : Proc(VM, Array(Parameter), String)
    
        def initialize(@sym, @size, @impl, @disasm)
        end
    
        # We use opcode size to indicate both how far the PC should move after
        # execution and the number of parameters. Number of parameters is always
        # `size-1` since size includes the instruction itself.
        def num_params
          size - 1
        end
    
        # Execute this opcode in the context of the given VM, with the provided
        # params.  Delagates to the attached Proc.
        def exec(vm, params)
          impl.call(vm, params)
        end
    
        # Return a (more or less) human-readable string describing this instruction
        def debug(vm, params)
          disasm.call(vm, params)
        end
    
        # Returns a tuple with the `Opcode` corresponding to the instruction at the
        # *address* and an array holding the `Parameter`s that follow it
        def self.get_opcode_and_params(vm : VM, address : Int64)
          instr = vm.mem[address]
          opcode, modes = get_opcode_and_modes(instr)
          {opcode, modes.map_with_index { |m,i| Parameter.new(m, vm.mem[address + i + 1]) }}
        end
    
        # Get the opcode and addressing modes for a given instruction, returns a
        # tuple with the `Opcode` instance and an array of addressing mode symbols
        private def self.get_opcode_and_modes(instr : Int64)
          opcode = OPCODES[instr % 100]
          modes = [] of Symbol
    
          instr //= 100 # cut off the opcode so we're left with just the mode part
          while modes.size < opcode.num_params
            modes << Intcode.lookup_addressing_mode(instr % 10)
            instr //= 10
          end
    
          {opcode, modes}
        end
    
      end
    
      # Map from addressing mode digit to symbol
      protected def self.lookup_addressing_mode(m)
        case m
        when 0 then :position
        when 1 then :literal
        when 2 then :relative
        else raise "Unsupported addressing mode #{m}"
        end
      end
    
      # Here we define the mapping from integer to `Opcode`, along with the actual
      # implementation of each as a `Proc`
      OPCODES = {
        1 => Opcode.new(:add, 4,
                        ->(vm: VM, params: Array(Parameter)) {
                          x, y, dest = params
                          vm.write_param_value(dest, vm.read_param(x) + vm.read_param(y))
                          vm.pc += params.size+1
                        },
                        ->(vm: VM, params: Array(Parameter)) {
                          "ADD %5s, %5s -> %5s" % params.map { |p| p.debug }
                        }),
        2 => Opcode.new(:mul, 4,
                        ->(vm: VM, params: Array(Parameter)) {
                          x, y, dest = params
                          vm.write_param_value(dest, vm.read_param(x) * vm.read_param(y))
                          vm.pc += params.size+1
                        },
                        ->(vm: VM, params: Array(Parameter)) {
                          "MUL %5s, %5s -> %5s" % params.map { |p| p.debug }
                        }),
        3 => Opcode.new(:input, 2,
                        ->(vm: VM, params: Array(Parameter)) {
                          dest = params.first
                          if input = vm.get_input
                            vm.write_param_value(dest, input)
                            vm.pc += params.size+1
                          else
                            0_i64
                          end
                        },
                        ->(vm: VM, params: Array(Parameter)) {
                          "IN  -> %5s" % params.map { |p| p.debug }
                        }),
        4 => Opcode.new(:output, 2,
                        ->(vm: VM, params: Array(Parameter)) {
                          x = params.first
                          vm.write_output(vm.read_param(x))
                          vm.pc += params.size+1
                        },
                        ->(vm: VM, params: Array(Parameter)) {
                          "OUT %5s" % params.map { |p| p.debug }
                        }),
        5 => Opcode.new(:jt, 3,
                        ->(vm: VM, params: Array(Parameter)) {
                          x, dest = params
                          if vm.read_param(x) != 0
                            vm.pc = vm.read_param(dest)
                          else
                            vm.pc += params.size+1
                          end
                        },
                        ->(vm: VM, params: Array(Parameter)) {
                          "JT  %5s, %5s" % params.map { |p| p.debug }
                        }),
        6 => Opcode.new(:jf, 3,
                        ->(vm: VM, params: Array(Parameter)) {
                          x, dest = params
                          if vm.read_param(x) == 0
                            vm.pc = vm.read_param(dest)
                          else
                            vm.pc += params.size+1
                          end
                        },
                        ->(vm: VM, params: Array(Parameter)) {
                          "JF  %5s, %5s" % params.map { |p| p.debug }
                        }),
        7 => Opcode.new(:lt, 4,
                        ->(vm: VM, params: Array(Parameter)) {
                          x, y, dest = params
                          if vm.read_param(x) < vm.read_param(y)
                            vm.write_param_value(dest, 1)
                          else
                            vm.write_param_value(dest, 0)
                          end
                          vm.pc += params.size+1
                        },
                        ->(vm: VM, params: Array(Parameter)) {
                          "LT  %5s, %5s -> %5s" % params.map { |p| p.debug }
                        }),
        8 => Opcode.new(:eq, 4,
                        ->(vm: VM, params: Array(Parameter)) {
                          x, y, dest = params
                          if vm.read_param(x) == vm.read_param(y)
                            vm.write_param_value(dest, 1)
                          else
                            vm.write_param_value(dest, 0)
                          end
                          vm.pc += 4
                        },
                        ->(vm: VM, params: Array(Parameter)) {
                          "EQ  %5s, %5s -> %5s" % params.map { |p| p.debug }
                        }),
        9 => Opcode.new(:adj_rel_base,
                        2,
                        ->(vm: VM, params: Array(Parameter)) {
                          x = params.first
                          vm.rel_base += vm.read_param(x)
                          vm.pc += 2
                        },
                        ->(vm: VM, params: Array(Parameter)) {
                          "REL  %5s" % params.map { |p| p.debug }
                        }),
        99 => Opcode.new(:halt, 1,
                         ->(vm: VM, params : Array(Parameter)) {
                           vm.halted = true
                           1_i64
                         },
                         ->(vm: VM, params : Array(Parameter)) {
                           "HALT"
                         }),
      }
    
    end
    

    Now that we (presumably) aren't going to be adding any major new features to the VM, I went back and refactored out a lot of the indirection in my setup. Interestingly, when compiled in release mode, the performance of the new version is almost identical to the original. However in debug mode, or with the ruby version, it is a good bit faster now.

    Intcode VM - Take 2
    module VM2
    
      class VM
        property name : String
        property debug : Bool
    
        property mem : Array(Int64)
        property pc : Int64
        property rel_base : Int64
    
        property status : Symbol
    
        property input : Array(Int64)
        property output : Array(Int64)
    
        def initialize(mem : Array(Int64))
          @name = "VM"
          @debug = false
          @pc = 0
          @rel_base = 0
          @opcode = 0
          @modes = [] of Symbol
          @status = :ok
          @mem = mem
          @input = [] of Int64
          @output = [] of Int64
        end
    
        def send_input(val : Int64)
          @input << val
          @status = :ok if @status == :needs_input
        end
    
        def read_output
          @output.shift?
        end
    
        def read_input
          if ! @input.empty?
            @input.shift
          else
            @status = :needs_input
            nil
          end
        end
    
        def read_mem(address)
          (address-mem.size+1).times { |_| mem << 0 } if mem.size <= address
          mem[address]
        end
    
        def write_mem(address, val)
          (address-mem.size+1).times { |_| mem << 0 } if mem.size <= address
          mem[address] = val
        end
    
        def read_p(p)
          case p[0]
          when :position then read_mem(p[1])
          when :literal then p[1]
          when :relative then read_mem(rel_base + p[1])
          else raise "Unsupported addressing mode for read #{p}"
          end
        end
    
        def write_p(p, val : Int64)
          case p[0]
          when :position then write_mem(p[1], val)
          when :relative then write_mem(rel_base + p[1], val)
          when :literal then raise "Cannot write to literal #{p} #{val}"
          end
        end
    
        def mode(m)
          case m
          when 0 then :position
          when 1 then :literal
          when 2 then :relative
          else raise "Unsupported addressing mode #{m}"
          end
        end
    
        def decode(n_params=3)
          instr = mem[pc]
          opcode = instr % 100
    
          instr //= 100
          {opcode,
           (1..n_params).map { |i| m = mode(instr % 10); instr //= 10; {m, read_mem(pc + i)} }
          }
        end
    
        def exec
          opcode, params = decode()
          log "%5i : %05i : %s" % [pc, mem[pc], VM2.disasm(opcode, params)]
    
          case opcode
          when 1 # add
            write_p(params[2], read_p(params[0]) + read_p(params[1]))
            @pc += 4
          when 2 # mul
            write_p(params[2], read_p(params[0]) * read_p(params[1]))
            @pc += 4
          when 3 # input
            if i = read_input
              write_p(params[0], i)
              @pc += 2
            else
              return :needs_input
            end
          when 4 # output
            output << read_p(params[0])
            @pc += 2
          when 5 # jt
            if read_p(params[0]) != 0
              @pc = read_p(params[1])
            else
              @pc += 3
            end
          when 6 # jf
            if read_p(params[0]) == 0
              @pc = read_p(params[1])
            else
              @pc += 3
            end
          when 7 # lt
            if read_p(params[0]) < read_p(params[1])
              write_p(params[2], 1)
            else
              write_p(params[2], 0)
            end
            @pc += 4
          when 8 # eq
            if read_p(params[0]) == read_p(params[1])
              write_p(params[2], 1)
            else
              write_p(params[2], 0)
            end
            @pc += 4
          when 9 # rel
            @rel_base += read_p(params[0])
            @pc += 2
          when 99
            @status = :halted
            @pc += 1
          else
            raise "invalid opcode at #{pc}"
          end
    
        end
    
        def run
          while status != :halted && status != :needs_input && pc < mem.size
            exec
          end
          return status
        end
    
        def log(msg)
          puts "(#{name}) #{msg}" if @debug
        end
    
      end
    
      def self.disasm(opcode, params)
        name, args = case opcode
                     when  1 then {" ADD",  3}
                     when  2 then {" MUL",  3}
                     when  3 then {"  IN",  1}
                     when  4 then {" OUT",  1}
                     when  5 then {"  JT",  2}
                     when  6 then {"  JF",  2}
                     when  7 then {"  LT",  3}
                     when  8 then {"  EQ",  3}
                     when  9 then {" REL",  1}
                     when 99 then {"HALT",  0}
                     else {"???", 3}
                     end
        "%3s: %s" % [name, params.first(args).map { |p| case p[0]
                                                    when :literal then p[1]
                                                    when :position then "@%i" % p[1]
                                                    when :relative then "$%i" % p[1]
                                                    else "?%i" % p[1]
                                                    end }.join(", ")]
      end
    
      def self.from_file(filename)
        from_string(File.read(filename))
      end
    
      def self.from_string(str)
        mem = str.chomp.split(',').map{ |s| Int64.new(s) }
        VM.new(mem)
      end
    
    end
    
    2 votes
  2. [2]
    Macil
    Link
    I'm really excited to see how the intcode computer gets used in further problems. I wonder what tricky problems could come up next now that we have the intcode computer established. Maybe we'll...

    I'm really excited to see how the intcode computer gets used in further problems. I wonder what tricky problems could come up next now that we have the intcode computer established. Maybe we'll have to repair an intcode problem or do some kind of analysis with one.

    2 votes
    1. Crespyl
      Link Parent
      This is the farthest I've gone on any of the Advent series so far, but based on some of the authors other work I do expect exactly that. The Synacor puzzle had a tricky "disassemble -> reverse...

      This is the farthest I've gone on any of the Advent series so far, but based on some of the authors other work I do expect exactly that.

      The Synacor puzzle had a tricky "disassemble -> reverse engineer -> optimize" challenge towards the end.

      I really enjoyed the Day 7 coordination/communication puzzle though, I hope we get more of that style too.

      1 vote
  3. undu
    Link
    Used today to clean up the intcode VM and convert it to using the result monad instead of exceptions inside the VM. Now that the step function, that calculates state according to the previous one...

    Used today to clean up the intcode VM and convert it to using the result monad instead of exceptions inside the VM. Now that the step function, that calculates state according to the previous one is stateless, it should be quite simple to go for day 7 part 2.

    Intcode module
    type reason = Halt | Input | UnknownOpCode of int | UnknownMode of char
    
    type registers = { pc : int; base : int }
    
    module IntMap = Map.Make (Int)
    
    type t = {
        memory : int IntMap.t
      ; registers : registers
      ; input : int list
      ; output : int list
    }
    
    let memory_of_list (mem : int list) =
      mem |> List.mapi (fun i x -> (i, x)) |> List.to_seq |> IntMap.of_seq
    
    let get_cell memory position =
      match IntMap.find_opt position memory with Some value -> value | None -> 0
    
    let value_read state value = function
      | '0' -> Ok (get_cell state.memory value) (* absolute *)
      | '1' -> Ok value (* immediate *)
      | '2' ->
          Ok (get_cell state.memory (state.registers.base + value)) (* relative *)
      | mode -> Error (UnknownMode mode)
    
    let value_write state value = function
      | '0' -> Ok value (* immediate *)
      | '2' -> Ok (state.registers.base + value) (* relative *)
      | mode -> Error (UnknownMode mode)
    
    let update (memory : int IntMap.t) address value =
      IntMap.add address value memory
    
    let step (state : t) =
      let ( >>= ) = Result.bind in
      let pc, base = (state.registers.pc, state.registers.base) in
      let pc_value = get_cell state.memory pc in
      let op = pc_value mod 100 in
      let mode = pc_value / 100 |> Printf.sprintf "%03u" |> Utils.string_rev in
      let cell_read value mode = value_read state value mode in
      let cell_write value mode = value_write state value mode in
    
      let arithmetic (func : int -> int -> int) =
        let op_left = get_cell state.memory (pc + 1) in
        let mode_left = mode.[0] in
        let op_right = get_cell state.memory (pc + 2) in
        let mode_right = mode.[1] in
        let op_target = get_cell state.memory (pc + 3) in
        let mode_target = mode.[2] in
        cell_read op_left mode_left >>= fun left ->
        cell_read op_right mode_right >>= fun right ->
        cell_write op_target mode_target >>= fun target ->
        let memory = update state.memory target (func left right) in
        Ok { state with memory; registers = { pc = pc + 4; base } }
      in
      let jump comp =
        let pc, base = (state.registers.pc, state.registers.base) in
        let op_left = get_cell state.memory (pc + 1) in
        let mode_left = mode.[0] in
        let op_right = get_cell state.memory (pc + 2) in
        let mode_right = mode.[1] in
        cell_read op_left mode_left >>= fun left ->
        cell_read op_right mode_right >>= fun right ->
        let next = if comp left then right else pc + 3 in
        Ok { state with registers = { pc = next; base } }
      in
      let boolean comp = arithmetic (fun a b -> Utils.int_of_bool @@ comp a b) in
    
      match op with
      | 99 -> Error Halt
      | 1 -> arithmetic ( + )
      | 2 -> arithmetic ( * )
      | 3 -> (
          match state.input with
          | [] -> Error Input
          | in_value :: input ->
              let op_value = get_cell state.memory (pc + 1) in
              let mode = mode.[0] in
              let registers = { pc = pc + 2; base } in
              cell_write op_value mode >>= fun position ->
              let memory = update state.memory position in_value in
              Ok { state with memory; registers; input } )
      | 4 ->
          let op_value = get_cell state.memory (pc + 1) in
          let mode = mode.[0] in
          let registers = { pc = pc + 2; base } in
          cell_read op_value mode >>= fun out_value ->
          let output = out_value :: state.output in
          Ok { state with registers; output }
      | 5 -> jump (( <> ) 0)
      | 6 -> jump (( = ) 0)
      | 7 -> boolean ( < )
      | 8 -> boolean ( = )
      | 9 ->
          let op_value = get_cell state.memory (pc + 1) in
          let mode = mode.[0] in
          cell_read op_value mode >>= fun base_offset ->
          let registers = { pc = pc + 2; base = base + base_offset } in
          Ok { state with registers }
      | op -> Error (UnknownOpCode op)
    
    let run_until_halt ~memory ~input =
      let rec loop state =
        Result.fold (step state) ~ok:loop ~error:(function
          | Halt -> state
          | Input -> raise (Failure "Expected more input items")
          | UnknownOpCode op ->
              raise
                (Invalid_argument (Printf.sprintf "Processed unknown opcode: %i" op))
          | UnknownMode mo ->
              raise
                (Invalid_argument (Printf.sprintf "Processed unknown opcode: %c" mo)))
      in
      loop
        {
          memory = memory_of_list memory
        ; registers = { pc = 0; base = 0 }
        ; input
        ; output = []
        }
    
    The exercise itself was pretty mundane, however:
    Part 1 and 2
    let input =
      Utils.read_lines ~path:"input"
      |> List.hd
      |> String.split_on_char ','
      |> List.map int_of_string
    
    let () =
      let memory = input in
      let out_1 = (Intcode.run_until_halt ~memory ~input:[ 1 ]).output in
      let out_2 = (Intcode.run_until_halt ~memory ~input:[ 2 ]).output in
    
      Printf.printf "Part 1: %u\n" (List.hd out_1);
      Printf.printf "Part 2: %u\n" (List.hd out_2)
    
    2 votes
  4. PapaNachos
    Link
    So now the intputer is fully functional. Part 2 isn't actually any different than part 1, which on one hand felt a bit disappointing, but on the other a free star is a nice reward for all the work...

    So now the intputer is fully functional. Part 2 isn't actually any different than part 1, which on one hand felt a bit disappointing, but on the other a free star is a nice reward for all the work spent on the intputer

    Part 1 & 2 - Python This is almost exactly the same intputer as the one I used for day 7, but read_value and write_value were modified to enable parameter mode 2, and I added opcode 9, which was pretty simple
    import math
    from itertools import permutations
    from itertools import cycle
    import itertools
    verbose_mode = False
    super_verbose_mode = False
    automatic_io = False
    
    
    class intputer:
        def __init__(self,initial_memory, name):
            #makes the int-puter
            self.memory = [int(x) for x in initial_memory.split(",")]
            self.memory = self.memory + ([0] * 10000)
            self.input_buffer = []
            self.output_buffer = []
            self.input_index = 0
            self.instruction_pointer = 0
            self.name = name
            self.op_code, self.parameters = self.read_instruction(self.memory[self.instruction_pointer])
            self.relative_base = 0
        def add_input(self,val):
            self.input_buffer.append(val)
        def get_output(self):
            return self.output_buffer[-1]
        def get_name(self):
            return self.name
        def read_instruction(self,instruction):
            #In order to deal with shorter length instructions we're going to 0-pad everything out to len==5
            #and then divide it back up
            if verbose_mode:
                print(f"Reading Instruction: {instruction}")
            instruction_components = [int(d) for d in str(instruction)]
            if len(instruction_components) == 1:
                #Pads opcode with non-functional 0, now len==2
                instruction_components.insert(0,0)
            if len(instruction_components) == 2:
                #Brings parameter_1 from null to 0, now len==3
                instruction_components.insert(0,0)
            if len(instruction_components) == 3:
                #Brings parameter_2 from null to 0, now len==4
                instruction_components.insert(0,0)
            if len(instruction_components) == 4:
                #Brings parameter_3 from null to 0, now len==5
                instruction_components.insert(0,0)
            opcode_portion = instruction_components[-2:]
            parameters_reversed = instruction_components[0:3]
    
    
    
            parameters = [int(parameters_reversed[2]), int(parameters_reversed[1]), int(parameters_reversed[0])]
    
            opcode = int((10 * opcode_portion[0]) + opcode_portion[1])
    
            if verbose_mode:
                print(f"Op Code {opcode}")
                print(f"Parameters {parameters}")
    
            return opcode, parameters
    
        def execute_instruction(self, op_code, parameter_codes):
            address_0 = self.instruction_pointer + 1
            address_1 = self.instruction_pointer + 2
            address_2 = self.instruction_pointer + 3
    
            if super_verbose_mode:
                print(self.memory)
    
            if op_code == 1:
                val = self.read_value(address_0, parameter_codes[0]) + self.read_value(address_1, parameter_codes[1])
                self.write_value(address_2, parameter_codes[2], val)
                self.instruction_pointer = self.instruction_pointer + 4
            elif op_code == 2:
                val = self.read_value(address_0, parameter_codes[0]) * self.read_value(address_1, parameter_codes[1])
                self.write_value(address_2, parameter_codes[2], val)
                self.instruction_pointer = self.instruction_pointer + 4
            elif op_code == 3:
                if automatic_io == True:
                    if self.input_index >= len(self.input_buffer):
                        #We have less input than we need, wait
                        return self.instruction_pointer, 0
                    val = self.input_buffer[self.input_index]
                    self.input_index = self.input_index + 1
                else:
                    val = input("Input Requested: ")
                self.write_value(address_0, parameter_codes[0], val)
                self.instruction_pointer = self.instruction_pointer + 2
            elif op_code == 4:
                if automatic_io == True:
                    self.output_buffer.append(self.read_value(address_0, parameter_codes[0]))
                else:
                    print(self.read_value(address_0, parameter_codes[0]))
                self.instruction_pointer = self.instruction_pointer + 2
            elif op_code == 5:
                val = self.read_value(address_0, parameter_codes[0])
                if val != 0:
                    self.instruction_pointer = self.read_value(address_1, parameter_codes[1])
                else:
                    self.instruction_pointer = self.instruction_pointer + 3
            elif op_code == 6:
                val = self.read_value(address_0, parameter_codes[0])
                if val == 0:
                    self.instruction_pointer = self.read_value(address_1, parameter_codes[1])
                else:
                    self.instruction_pointer = self.instruction_pointer + 3
            elif op_code == 7:
                val_0 = self.read_value(address_0, parameter_codes[0])
                val_1 = self.read_value(address_1, parameter_codes[1])
    
                if val_0 < val_1:
                    self.write_value(address_2, parameter_codes[2], 1)
                else:
                    self.write_value(address_2, parameter_codes[2], 0)
                self.instruction_pointer = self.instruction_pointer + 4
            elif op_code == 8:
                val_0 = self.read_value(address_0, parameter_codes[0])
                val_1 = self.read_value(address_1, parameter_codes[1])
    
                if val_0 == val_1:
                    self.write_value(address_2, parameter_codes[2], 1)
                else:
                    self.write_value(address_2, parameter_codes[2], 0)
                self.instruction_pointer = self.instruction_pointer + 4
            elif op_code == 9:
                #adjust relative base
                
                if verbose_mode:
                    print("Adjusting Relative Base by {self.read_value(address_0, parameter_codes[0])}")
                    print("Initial Relative Base {self.relative_base}")
                self.relative_base = self.relative_base + self.read_value(address_0, parameter_codes[0])
                self.instruction_pointer = self.instruction_pointer + 2
                if verbose_mode:
                    print("New Relative Base: {self.relative_base}")
            else:
                print('Something fucking broke')
    
            return self.instruction_pointer, 1
    
        def read_value(self,address, parameter):
            if verbose_mode:
                print("Reading memory")
                print(f"Address: {address}")
                print(f"Parameter: {parameter}")
            if super_verbose_mode:
                print(self.memory)
            #If parameter is 0, use the address stored in address
            if parameter == 0:
                new_address = self.memory[address]
                value = self.memory[new_address]
            #If parameter is 1, us the value stored in address
            elif parameter == 1:
                value = self.memory[address]
            #If parameter is 2, use relative base mode
            elif parameter == 2:
                new_address = self.relative_base + self.memory[address]
                value = self.memory[new_address]
            return int(value)
    
        def write_value(self, address, parameter, value):
            if parameter == 0 or parameter == 1:
                new_address = self.memory[address]
                self.memory[new_address] = value
            elif parameter == 2:
                new_address = self.relative_base + self.memory[address]
                self.memory[new_address] = value
    
        def run_puter(self):
            #SWITCH THIS TO JUST RUN 1 STEP AT A TIME
            status = 1
            while True:
                if self.op_code == 99:
                    #computer is done executing
                    return -1
                elif status == 0:
                    #pause to resume later
                    #IF WE PAUSE, LEAVE THE OPCODE AND PARAMETERS THE SAME, WE'RE WAITING FOR INPUT
                    return 0
                else:
                    self.instruction_pointer, status = self.execute_instruction(self.op_code, self.parameters)
                    self.op_code, self.parameters = self.read_instruction(self.memory[self.instruction_pointer])
            return self.memory
    
    initial_mem = "MEMORY GOES HERE"
    
    puter = intputer(initial_mem, 'puter')
    result = puter.run_puter()
    
    1 vote
  5. Crestwave
    (edited )
    Link
    Well, that was easy, partly because AWK (or at least nawk) isn't strict so everything but the new mode came in free. Although I sort of cheated here and hardcoded the input handling. I'll try to...

    Well, that was easy, partly because AWK (or at least nawk) isn't strict so everything but the new mode came in free. Although I sort of cheated here and hardcoded the input handling. I'll try to clean it up later as I'm not feeling well right now.

    Parts 1 & 2
    #!/usr/bin/awk -f
    { prog = prog $0 }
    
    END {
        base = 0
        split(prog, init, ",")
        for (i = 1; i <= length(init); ++i)
            mem[i-1] = init[i]
    
        for (ptr = 0; ptr < length(mem);) {
            split(substr(mem[ptr], 1, length(mem[ptr])-2), modes, "")
            c = substr(mem[ptr], length(mem[ptr])-1) + 0
    
            for (i = 1; i <= 3; ++i) {
                mode = modes[length(modes)-i+1]
                if (mode == 0)
                    params[i] = (i == 3 || c == 3) ? \
                          mem[ptr+i] : \
                          mem[mem[ptr+i]]
                else if (mode == 1)
                    params[i] = mem[ptr+i]
                else if (mode == 2)
                    params[i] = (i == 3 || c == 3) ? \
                          mem[ptr+i] + base : \
                          mem[mem[ptr+i] + base]
            }
    
            if (c == 1) {
                mem[params[3]] = params[1] + params[2]
                ptr += 4
            } else if (c == 2) {
                mem[params[3]] = params[1] * params[2]
                ptr += 4
            } else if (c == 3) {
                getline input < "-"
                mem[params[1]] = input + 0
                ptr += 2
            } else if (c == 4) {
                print params[1]
                ptr += 2
            } else if (c == 5) {
                ptr = params[1] ? params[2] : ptr+3
            } else if (c == 6) {
                ptr = params[1] ? ptr+3 : params[2]
            } else if (c == 7) {
                mem[params[3]] = params[1] < params[2]
                ptr += 4
            } else if (c == 8) {
                mem[params[3]] = params[1] == params[2]
                ptr += 4
            } else if (c == 9) {
                base += params[1]
                ptr += 2
            } else if (c == 99) {
                break
            } else {
                exit(1)
            }
        }
    }
    

    EDIT: Cleaned up a bit.

    1 vote
  6. Gyrfalcon
    Link
    Well, I had a busy day yesterday and didn't get to this, but it's done now. Assuming we see the Intputer again, I fully expect to spend time refactoring it into an object. That would make it much...

    Well, I had a busy day yesterday and didn't get to this, but it's done now. Assuming we see the Intputer again, I fully expect to spend time refactoring it into an object. That would make it much easier to be compatible both with the new features from these challenges, and the management of several simultaneous Intputers from before. Anyway, Python solution below:

    Parts 1&2
    import copy
    
    def get_operands(instructions, curr_instruction, pointer, relative_base):
    
        for i in range(5 - len(curr_instruction)):
            curr_instruction.insert(0,0) # Pad to get the right number of 0s
    
        if curr_instruction[0] == 0:
            out_location = instructions[pointer + 3]
        elif curr_instruction[0] == 2:
            out_location = instructions[pointer + 3] + relative_base
        else:
            raise ValueError("Incorrect mode for output operand!")
    
        if curr_instruction[1] == 0:
            op_2_location = instructions[pointer+2]
        elif curr_instruction[1] == 1:
            op_2_location = pointer + 2
        elif curr_instruction[1] == 2:
            op_2_location = instructions[pointer+2] + relative_base
        else:
            raise ValueError("Incorrect mode for second operand!")
    
        if curr_instruction[2] == 0:
            op_1_location = instructions[pointer+1]
        elif curr_instruction[2] == 1:
            op_1_location = pointer + 1
        elif curr_instruction[2] == 2:
            op_1_location = instructions[pointer+1] + relative_base
        else:
            raise ValueError("Incorrect mode for first operand!")
    
        # We handle big memory on write, so this covers it on read
        try:
            op_1 = instructions[op_1_location]
        except IndexError:
            op_1 = 0
    
        try:
            op_2 = instructions[op_2_location]
        except IndexError:
            op_2 = 0
    
        return [op_1, op_2, out_location]
    
    def get_io_operand(instructions, curr_instruction, pointer, relative_base):
    
        for i in range(3 - len(curr_instruction)):
            curr_instruction.insert(0,0) # Pad to get the right number of 0s
    
        if curr_instruction[0] == 0:
            op = instructions[pointer + 1]
        elif curr_instruction[0] == 1:
            op = pointer + 1
        elif curr_instruction[0] == 2:
            op = instructions[pointer + 1] + relative_base
        else:
            raise ValueError("Incorrect mode for I/O operand!")
    
        return op
    
    def write_result(instructions, result, location):
    
        # Extend memory if needed
        try:
            instructions[location] = result
        except IndexError:
            instructions.extend([0] * ((location + 1) - len(instructions)))
            instructions[location] = result
    
        return instructions
    
    def add_instruction(instructions, curr_instruction, pointer, relative_base):
        operands = get_operands(instructions, curr_instruction, pointer, relative_base)
    
        result = operands[0] + operands[1]
        return write_result(instructions, result, operands[2])
    
    def mult_instruction(instructions, curr_instruction, pointer, relative_base):
        operands = get_operands(instructions, curr_instruction, pointer, relative_base)
    
        result = operands[0] * operands[1]
        return write_result(instructions, result, operands[2])
    
    def output_instruction(instructions, curr_instruction, pointer, relative_base):
    
        operand = get_io_operand(instructions, curr_instruction, pointer, relative_base)
    
        return instructions[operand]
    
    def input_instruction(instructions, curr_instruction, pointer, user_input, relative_base):
    
        operand = get_io_operand(instructions, curr_instruction, pointer, relative_base)
    
        return write_result(instructions, user_input, operand)
    
    def jump_instruction(instructions, curr_instruction, pointer, jump_type, relative_base):
    
        operands = get_operands(instructions, curr_instruction, pointer, relative_base)
    
        if ((operands[0] and jump_type) or (not operands[0] and not jump_type)):
            return operands[1]
        else:
            return (pointer + 3)
    
    def less_than_instruction(instructions, curr_instruction, pointer, relative_base):
    
        operands = get_operands(instructions, curr_instruction, pointer, relative_base)
    
        if operands[0] < operands[1]:
            return write_result(instructions, 1, operands[2])
        else:
            return write_result(instructions, 0, operands[2])
    
    def equals_instruction(instructions, curr_instruction, pointer, relative_base):
    
        operands = get_operands(instructions, curr_instruction, pointer, relative_base)
    
        if operands[0] == operands[1]:
            return write_result(instructions, 1, operands[2])
        else:
            return write_result(instructions, 0, operands[2])
    
    def modify_base_instruction(instructions, curr_instruction, pointer, relative_base):
    
    
        operand = get_io_operand(instructions, curr_instruction, pointer, relative_base)
    
        return relative_base + instructions[operand]
    
    def process_instructions(instructions, inputs=None, pointer=0):
    
        output = []
        if inputs is None:
            inputs = []
        inputs = inputs[::-1]
        relative_base = 0
    
        while True:
    
            if pointer > (len(instructions) - 1):
                break
            elif instructions[pointer] == 99:
                break
    
            curr_instruction = [int(num) for num in str(instructions[pointer])]
    
            if curr_instruction[-1] == 1:
                instructions = add_instruction(instructions, curr_instruction, pointer, relative_base)
                pointer += 4
            elif curr_instruction[-1] == 2:
                instructions = mult_instruction(instructions, curr_instruction, pointer, relative_base)
                pointer += 4
            elif curr_instruction[-1] == 3:
                if len(inputs) < 1: # Block on empty input
                    return output, instructions, pointer
                instructions = input_instruction(instructions, curr_instruction, pointer, inputs.pop(), relative_base)
                pointer += 2
            elif curr_instruction[-1] == 4:
                output.append(output_instruction(instructions, curr_instruction, pointer, relative_base))
                pointer += 2
            elif curr_instruction[-1] == 5:
                pointer = jump_instruction(instructions, curr_instruction, pointer, True, relative_base)
            elif curr_instruction[-1] == 6:
                pointer = jump_instruction(instructions, curr_instruction, pointer, False, relative_base)
            elif curr_instruction[-1] == 7:
                instructions = less_than_instruction(instructions, curr_instruction, pointer, relative_base)
                pointer += 4
            elif curr_instruction[-1] == 8:
                instructions = equals_instruction(instructions, curr_instruction, pointer, relative_base)
                pointer += 4
            elif curr_instruction[-1] == 9:
                relative_base = modify_base_instruction(instructions, curr_instruction, pointer, relative_base)
                pointer += 2
    
        #print("PROGRAM TERMINATED")
    
        return output, instructions, pointer, relative_base
    
    
    input_file = "PuzzleInput.txt"
    
    with open(input_file, 'r') as fid:
        data = fid.readline()
    
    instructions = data.strip().split(',')
    
    for idx in range(len(instructions)):
        instructions[idx] = int(instructions[idx])
    
    output, _, _, _ = process_instructions(copy.copy(instructions), [1])
    
    print(output)
    
    output, _, _, _ = process_instructions(copy.copy(instructions), [2])
    
    print(output)
    
    1 vote