7 votes

Day 11: Space Police

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


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>

8 comments

  1. [3]
    Hvv
    Link
    I was lucky enough to have a decently robust Intcode computer which didn't need to be touched at all, so this question was a nice break from messing with Intcode and whatever was going on...

    I was lucky enough to have a decently robust Intcode computer which didn't need to be touched at all, so this question was a nice break from messing with Intcode and whatever was going on yesterday.

    Part 1 Strategy There's a 99% chance that the robot won't go crazy coordinate-wise (100%, now that I know how it behaves), but just to make sure, we store the coordinates in a hash set instead of a grid.

    Our Intcode computer implementation plays nicely with I/O (input and output are queues, both of which can be messed with whenever the computer interrupts), so we just need to feed in the appropriate color number when it halts for input and keep track of its position/direction.

    We can do this by updating the position whenever the computer halts for input, since by then it will have output its paint color & turning direction.

    Then, in order:

    1. Paint the appropriate tile and note that this tile will have been painted at least once (store it in a hash/tree set)
    2. Update the robot's direction (which here is a single value mod 4)
    3. Update the robot's position (which we can do by drawing from a "movement" array stating how the row & column will change if we move any direction).

    Letting the code chug along until it halts (and hoping that there isn't a hidden bug in the computer), output the number of distinct elements in the "painted once" set.

    Side note: If you try to view the final output, the result resembles the output of a cellular automaton, likely a Turmite but one which I can't immediately recognize.

    Part 2 Strategy Since the code will happily buzz around with whatever configuration we give it, we just add the initial coordinate to the set of white tiles and run it again.

    We don't actually know where the tiles are, so that takes a little messing around to find (hard code) the proper coordinates.

    Once we have a decent enough window to view our tiles, just read off the letters (and maybe flip it if it turns out to be upside down).

    Part 1/2 Code
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    typedef vector<ll> vi;
    typedef pair<int,int> ii;
    typedef vector<ll> vl;
    class Comp {
    	vl w,oc;
    	queue<ll> ins,outs;
    	ll reb;
    	ll pc;
    	bool term;
    	public:
    	Comp() {
    	}
    	Comp(vl init) {
    		oc = init;
    		w = oc;
    		w.resize(1000000);
    		term = false;
    		reb = 0;
    		pc = 0;
    	}
    	void feed(ll val) {
    		ins.push(val);
    	}
    	bool empty_ins() {
    		return ins.empty();
    	}
    	bool empty_outs() {
    		return outs.empty();
    	}
    	ll poll() {
    		ll res = outs.front();
    		outs.pop();
    		return res;
    	}
    	void reset(){
    		w = oc;
    		term = false;
    		pc = 0;
    		reb = 0;
    		ins = queue<ll>();
    		outs = queue<ll>();
    	}
    	ll get(ll id) {
    		return w[id];
    	}
    	bool run() {
    		while(!term) {
    			if(pc >= w.size()) {
    				term = true;
    				cout << "dropout termination\n";
    				return term;
    			}
    			ll raw = w[pc];
    			ll id = w[pc];
    			if(id == 99) {
    				term = true;
    				return term;
    			}
    			ll params = id/100;
    			id %= 100;
    			ll pr[3];
    			for(int i=0;i<3;i++) {
    				pr[i] = (params%10);
    				params /= 10;
    			}
    			ll pt[3] = {-1,-1,-1};
    			ll nx[3] = {-1,-1,-1};
    			for(int i=0;i<3;i++) {
    				if(i+pc+1 >= w.size()) {
    					break;
    				} else {
    					pt[i] = nx[i] = w[i+pc+1];
    					if(pr[i] == 0) {
    						if(nx[i] < w.size()) {
    							nx[i] = w[nx[i]];
    						} else {
    						}
    					} else if(pr[i] == 2) {
    						pt[i] += reb;
    						if(nx[i]+reb < w.size()) {
    							nx[i] = w[nx[i]+reb];
    						} else {
    						}
    					} else {
    						//immediate mode
    					}
    				}
    			}
    			if(id == 3) {
    				if(ins.empty()) {
    					return false;
    				}
    				ll val = ins.front();
    				ins.pop();
    				w[pt[0]] = val;
    				pc += 2;
    			} else if(id == 4) {
    				outs.push(nx[0]);
    				pc += 2;
    			} else if(id == 5) {
    				if(nx[0] != 0) {
    					pc = nx[1];
    				} else {
    					pc += 3;
    				}
    			} else if(id == 6) {
    				if(nx[0] == 0) {
    					pc = nx[1];
    				} else {
    					pc += 3;
    				}
    			} else if(id == 7) {
    				nx[2] = pt[2];
    				if(nx[0] < nx[1]) {
    					w[nx[2]] = 1;
    				} else {
    					w[nx[2]] = 0;
    				}
    				pc += 4;
    			} else if(id == 8) {
    				nx[2] = pt[2];
    				if(nx[0] == nx[1]) {
    					w[nx[2]] = 1;
    				} else {
    					w[nx[2]] = 0;
    				}
    				pc += 4;
    			} else if(id == 1) {
    				nx[2] = pt[2];
    				w[nx[2]] = nx[0]+nx[1];
    				pc += 4;
    			} else if(id == 2) {
    				nx[2] = pt[2];
    				//cout << nx[0] << " * " << nx[1] << '\n';
    				w[nx[2]] = nx[0]*nx[1];
    				pc += 4;
    			} else if(id == 9) {
    				reb += nx[0];
    				pc += 2;
    			} else {
    				return false;
    			}
    		}
    		return term;
    	}
    	bool done() {
    		return term;
    	}
    };
    vl x;
    void input() {
    	string s;
    	cin >> s;
    	ll t = 0;
    	ll sgn = 1;
    	for(int i=0;i<s.size();i++) {
    		if(s[i] == ',') {
    			x.push_back(t*sgn);
    			t = 0;
    			sgn = 1;
    		} else if(s[i] == '-') {
    			sgn *= -1;
    		} else {
    			t *= 10;
    			t += (s[i]-'0');
    		}
    	}
    	x.push_back(t);
    }
    const int dr[4] = {0,1,0,-1};
    const int dc[4] = {1,0,-1,0};
    set<ii> bl;
    set<ii> pt;
    int main() {
    	int ct = 0;
    	input();
    	Comp C(x);
    	C.run();
    	int r = 200,c = 200;
    	int dir = 4;
    	//Part 2 solution: uncomment
    	//bl.insert({r,c});
    	while(!C.done()) {
    		if(bl.find({r,c}) != bl.end()) {
    			C.feed(1);
    		} else {
    			C.feed(0);
    		}
    		C.run();
    		int a = C.poll();
    		int b = C.poll();
    		pt.insert({r,c});
    		if(a) {
    			bl.insert({r,c});
    		} else {
    			bl.erase({r,c});
    		}
    		if(b == 0) {
    			dir = (dir+3)%4;
    		} else {
    			dir = (dir+1)%4;
    		}
    		r += dr[dir];
    		c += dc[dir];
    	}
    	for(int i=260;i>=180;i--) {
    		for(int j=180;j<260;j++) {
    			if(bl.find({i,j}) != bl.end()) {
    				cout << "#";
    			} else {
    				cout << ".";
    			}
    		}
    			cout << '\n';
    	}
    	cout << pt.size() << '\n';
    }
    
    3 votes
    1. [2]
      PapaNachos
      Link Parent
      Part 1 Discussion Did yours actually halt on its own? Mine seemed to get stuck in an infinite loop so I had to add code to terminate it after a certain number of iterations
      Part 1 Discussion

      Did yours actually halt on its own? Mine seemed to get stuck in an infinite loop so I had to add code to terminate it after a certain number of iterations

      3 votes
      1. Crespyl
        Link Parent
        Part 1 In my case, I put it in the middle of a 100x100 grid and it does halt after 100,010 instructions executed.
        Part 1

        In my case, I put it in the middle of a 100x100 grid and it does halt after 100,010 instructions executed.

        1 vote
  2. [3]
    PapaNachos
    (edited )
    Link
    I actually struggled more with finding how big of an input area I needed and formatting the Part 2 output than with the puzzle portion. I had to do a very minor modification to int-puter, but it...

    I actually struggled more with finding how big of an input area I needed and formatting the Part 2 output than with the puzzle portion. I had to do a very minor modification to int-puter, but it was something I knew was missing but hadn't bother to implement yet

    Part 1 & 2 Summary and Code - Python

    Basically I just made the robot feed back into itself and created a few new functions to manage interactions with the grid. I was afraid that Part 2 would require multiple robots or something silly, so I wanted to try to compartmentalize it a bit. The additional functionality I mentioned needing to add was the 'get_output_history' method. The earlier version of intputer could only look at the most recent output, but this required being able to look 1 further back.

    import math
    from itertools import permutations
    from itertools import cycle
    import itertools
    verbose_mode = False
    super_verbose_mode = False
    automatic_io = True
    
    
    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_output_history(self, index):
            return self.output_buffer[-index]
        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
    
    def change_direction(current_direction, turn):
        if current_direction == 'UP':
            if turn == 1:
                return 'RIGHT'
            else:
                return 'LEFT'
        elif current_direction == 'RIGHT':
            if turn == 1:
                return 'DOWN'
            else:
                return 'UP'
        elif current_direction == 'DOWN':
            if turn == 1:
                return 'LEFT'
            else:
                return 'RIGHT'
        elif current_direction == 'LEFT':
            if turn == 1:
                return 'UP'
            else:
                return 'DOWN'
        else:
            print('Something Fucking Broke')
    def move_robot(current_direction, location):
        if current_direction == 'UP':
            return [location[0], location[1] + 1]
        elif current_direction == 'RIGHT':
            return [location[0] + 1, location[1]]
        elif current_direction == 'DOWN':
            return [location[0], location[1] - 1]
        elif current_direction == 'LEFT':
            return [location[0] - 1, location[1]]
        else:
            print('Something Fucking Broke')
    def get_color(grid, current_location):
        if grid[current_location[0]][current_location[1]] == '.':
            #black seen
            return 0
        elif grid[current_location[0]][current_location[1]] == '#':
            #white seen
            return 1
        else:
            print('Something Fucking Broke')
    def paint_grid(grid, painted_grid, current_location, color):
        painted_grid[current_location[0]][current_location[1]] = True
        if color == 0:
            grid[current_location[0]][current_location[1]] = '.'
        elif color == 1:
            grid[current_location[0]][current_location[1]] = '#'
        else:
            print('Something Fucking Broke')
            
    initial_mem = "MEMORY GOES HERE"
    
    x_dimensions = 100
    y_dimensions = 100
    
    grid = []
    painted_grid = []
    for i in range(y_dimensions):
        row = list(['.'] * x_dimensions)
        grid.append(row)
        painted_row = list([False] * x_dimensions)
        painted_grid.append(painted_row)
    
    robot_location = [50, 50]
    robot_direction = 'UP'
    
    grid[robot_location[0]][robot_location[1]] = '#'
    #print(grid)
    puter = intputer(initial_mem, 'puter')
    
    max_loop_iterations = 10000
    loop_count = 0
    
    result = None
    while result != -1 and loop_count < max_loop_iterations:
        loop_count = loop_count + 1
        location_color = get_color(grid, robot_location)
        #print(f'At location {robot_location}, paint color seen: {location_color}')
        puter.add_input(location_color)
        puter.run_puter()
        paint_color = int(puter.get_output_history(2))
        paint_grid(grid, painted_grid, robot_location, paint_color)
        turn = int(puter.get_output())
        robot_direction = change_direction(robot_direction, turn)
        robot_location = move_robot(robot_direction, robot_location)
        
        
    #result = puter.run_puter()
    #for row in grid:
    #    print(str(row))
    painted_cells_count = 0
    for row in painted_grid:
        for cell in row:
            if cell == True:
                painted_cells_count = painted_cells_count + 1
    print(painted_cells_count)
    
    for row in grid:
        output_row = ""
        for cell in row:
            if cell == '.':
                output_row = output_row + 'â–ˆ'
            else:
                output_row = output_row + ' '
        print(output_row)
    
    Minor Tips - Not Really Spoilery I had to make Part 1 like 500x500 before I stopped getting index out of bounds. But for Part 2 I was able to shrink it down substantially, I used 100x100, but if I hadn't started in the middle it could be much smaller

    Of course, none of that matters if you're doing it in a way that automatically grows

    Minor Tips - A Bit Spoilery During Part 1 it seems that the robot gets into an infinite loop. I had it stop operating after 10000 iterations, but it probably starts repeating well before that

    Edit: This is apparently do to a bug in my code, not part of the base problem. Oops.

    3 votes
    1. [2]
      Hvv
      (edited )
      Link Parent
      Part 1 Spoilery Stuff That behavior is pretty weird, since for me, the robot did halt at some point. You know your code better than I do, but the infinite loop might be a bug resulting from not...
      Part 1 Spoilery Stuff That behavior is pretty weird, since for me, the robot did halt at some point.

      You know your code better than I do, but the infinite loop might be a bug resulting from not assigning result during the computer's running loop.

      When I use the line result = puter.run_puter() instead of raw puter.run_puter(), the loop does terminate and output the correct result.

      Edit: Turns out we were wondering the same thing at the same time!

      My input took 9791 loops to halt, so 10000 is just enough to be right anyway.

      2 votes
      1. PapaNachos
        Link Parent
        Part 1 - Spoilers Continued Fuck, I think you're right, I completely missed that part. I even have it commented out a few lines down. Thanks!
        Part 1 - Spoilers Continued

        Fuck, I think you're right, I completely missed that part. I even have it commented out a few lines down. Thanks!

        2 votes
  3. Crespyl
    Link
    Whoops! I accidentally did part 1 slightly wrong in such a way that I revealed the correct answer for part 2, which lead me down a long rabbit hole and had me questioning my sanity. During that...

    Whoops! I accidentally did part 1 slightly wrong in such a way that I revealed the correct answer for part 2, which lead me down a long rabbit hole and had me questioning my sanity.

    During that rabbit hole I ended up replacing my VM buffer-based input with one based on setting up a pair of I/O lambdas, and during the re-implementation I managed to fix part 1 so ¯\_(ツ)_/¯

    Part 1 & 2, Crystal
    #!/usr/bin/env crystal
    require "colorize"
    require "../lib/utils.cr"
    require "../lib/vm2.cr"
    
    def log(msg)
      if Utils.enable_debug_output?
        puts msg
      end
    end
    
    class Map
      property tiles : Array(Array(Symbol))
    
      def initialize(width, height)
        @tiles = [] of Array(Symbol)
        height.times do
          @tiles << [] of Symbol
          width.times do
            @tiles.last << :blank
          end
        end
      end
    
      def get(x,y)
        @tiles[y][x]
      end
    
      def set(x,y,val)
        @tiles[y][x] = val
      end
    
      def count_painted(color=nil)
        count = 0
        @tiles.each do |row|
          row.each do |tile|
            if (color && tile == color) || tile != :blank
              count += 1
            end
          end
        end
        count
      end
    end
    
    
    class Robot
      property cpu : VM2::VM
      property map : Map
    
      property x : Int32
      property y : Int32
    
      property facing : Symbol
    
      property paint_count : Int32
    
      property state : Symbol
    
      def initialize(map, program)
        @x, @y = 0,0
        @facing = :up
        @state = :wait_for_paint
        @paint_count = 0
    
        @map = map
    
        @cpu = VM2.from_string(program)
        @cpu.debug = false
        @cpu.input_fn = ->() { do_camera }
        @cpu.output_fn = ->(x: Int64) { do_action(x) }
      end
    
      def rotate_left
        case @facing
        when :up then @facing = :left
        when :left then @facing = :down
        when :down then @facing = :right
        when :right then @facing = :up
        else raise "can't rotate left from #{facing}"
        end
      end
    
      def rotate_right
        case @facing
        when :up then @facing = :right
        when :right then @facing = :down
        when :down then @facing = :left
        when :left then @facing = :up
        else raise "can't rotate right from #{@facing}"
        end
      end
    
      def move_forward
        case @facing
        when :up then @y -= 1
        when :right then @x += 1
        when :down then @y += 1
        when :left then @x -= 1
        else raise "can't move forward from #{@facing}"
        end
      end
    
      def do_camera
        input = map.get(x,y)
    
        if Utils.enable_debug_output?
          puts "robot #{@state} sees #{input} @ #{x},#{y}"
          puts
          print_map_robot(map, self)
          puts
        end
    
        case input
        when :white then 1_i64
        when :black then 0_i64
        when :blank then 0_i64
        else raise "can't send input: #{input}"
        end
      end
    
      def do_action(a : Int64)
        log "robot #{@state} does action #{a} @ #{@x},#{@y}"
        case @state
        when :wait_for_move
          log "  do move #{a}"
          case a
          when 0 then rotate_left
          when 1 then rotate_right
          else raise "robot can't handle move output #{a}"
          end
          move_forward
          @state = :wait_for_paint
        when :wait_for_paint
          log "  do paint #{a}"
          case a
          when 0 then map.set(x,y,:black)
          when 1 then map.set(x,y,:white)
          else raise "robot can't handle paint output #{a}"
          end
          @paint_count += 1
          @state = :wait_for_move
        end
        return nil
      end
    
      def run
        log "robot run"
        cpu.run
        log "robot stop"
      end
    end
    
    def print_map_robot(map, robot)
      map.tiles.each_with_index do |row,y|
        row.each_with_index do |tile,x|
          if robot.x == x && robot.y == y
            print '@'
          else
            case tile
            when :blank then print " ".colorize.back(:black)
            when :black then print " "
            when :white then print "#".colorize.back(:white)
            else print "?".colorize.back(:red)
            end
          end
        end
        print "\n"
      end
    end
    
    
    INPUT = Utils.get_input_file(Utils.cli_param_or_default(0, "day11/input.txt"))
    
    # part 1
    map = Map.new(100,100) # big enough I guess
    robot = Robot.new(map, INPUT)
    robot.x = 50
    robot.y = 50
    
    robot.run
    
    #print_map_robot(map, robot)
    puts "Robot stopped at: #{robot.x}, #{robot.y}"
    puts "Part 1: %i" % map.count_painted
    
    # part 2
    map = Map.new(50,10)
    robot = Robot.new(map, INPUT)
    robot.x = 2
    robot.y = 2
    
    map.set(robot.x,robot.y, :white)
    robot.run
    
    puts "Robot stopped at: #{robot.x}, #{robot.y}"
    puts "Part 2"
    print_map_robot(map, robot)
    
    3 votes
  4. Gyrfalcon
    Link
    Well I took this challenge as a chance to refactor my Intputer into an object, which I really should have done for the amplifier challenge but oh well. Once I had that done, I didn't think this...

    Well I took this challenge as a chance to refactor my Intputer into an object, which I really should have done for the amplifier challenge but oh well. Once I had that done, I didn't think this was too difficult, though I did benefit a little bit from @PapaNachos nonspoiler tips, and from doing a super overkill way of visualizing the output from Part 2.

    Python code below is split up by file that I used, not by part, parts 1 and 2 are mixed in.

    Intputer
    class Intputer(object):
    
        def __init__(self, instructions, pointer=0, relative_base=0, output_block=False):
    
            self.instructions = instructions
            self.pointer = pointer
            self.relative_base = relative_base
            self.curr_instruction = [int(num) for num in str(self.instructions[self.pointer])]
            self.output_block = output_block
            self.output_buffer = []
            self.input_buffer = []
            self.block = False
            self.isHalted = False
    
        def get_operands(self):
            for i in range(5 - len(self.curr_instruction)):
                self.curr_instruction.insert(0,0) # Pad to get the right number of 0s
    
            if self.curr_instruction[0] == 0:
                out_location = self.instructions[self.pointer + 3]
            elif self.curr_instruction[0] == 2:
                out_location = self.instructions[self.pointer + 3] + self.relative_base
            else:
                raise ValueError("Incorrect mode for output operand!")
    
            if self.curr_instruction[1] == 0:
                op_2_location = self.instructions[self.pointer+2]
            elif self.curr_instruction[1] == 1:
                op_2_location = self.pointer + 2
            elif self.curr_instruction[1] == 2:
                op_2_location = self.instructions[self.pointer+2] + self.relative_base
            else:
                raise ValueError("Incorrect mode for second operand!")
    
            if self.curr_instruction[2] == 0:
                op_1_location = self.instructions[self.pointer+1]
            elif self.curr_instruction[2] == 1:
                op_1_location = self.pointer + 1
            elif self.curr_instruction[2] == 2:
                op_1_location = self.instructions[self.pointer+1] + self.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 = self.instructions[op_1_location]
            except IndexError:
                op_1 = 0
    
            try:
                op_2 = self.instructions[op_2_location]
            except IndexError:
                op_2 = 0
    
            return [op_1, op_2, out_location]
    
        def get_io_operand(self):
    
            for i in range(3 - len(self.curr_instruction)):
                self.curr_instruction.insert(0,0) # Pad to get the right number of 0s
    
            if self.curr_instruction[0] == 0:
                op = self.instructions[self.pointer + 1]
            elif self.curr_instruction[0] == 1:
                op = self.pointer + 1
            elif self.curr_instruction[0] == 2:
                op = self.instructions[self.pointer + 1] + self.relative_base
            else:
                raise ValueError("Incorrect mode for I/O operand!")
    
            return op
    
        def write_result(self, result, location):
    
            # Extend memory if needed
            try:
                self.instructions[location] = result
            except IndexError:
                self.instructions.extend([0] * ((location + 1) - len(self.instructions)))
                self.instructions[location] = result
    
        def add_instruction(self):
            operands = self.get_operands()
    
            result = operands[0] + operands[1]
            self.write_result(result, operands[2])
            self.pointer += 4
    
        def mult_instruction(self):
            operands = self.get_operands()
    
            result = operands[0] * operands[1]
            self.write_result(result, operands[2])
            self.pointer += 4
    
        def output_instruction(self):
    
            operand = self.get_io_operand()
    
            self.output_buffer.append(self.instructions[operand])
    
            if self.output_block:
                self.block = True
    
            self.pointer += 2
    
        def input_instruction(self):
    
            # Check for no input, and block if input is empty
            if len(self.input_buffer) < 1:
                self.block = True
                return
    
            operand = self.get_io_operand()
    
            self.write_result(self.input_buffer.pop(), operand)
            self.pointer += 2
    
        def jump_instruction(self, jump_type):
    
            operands = self.get_operands()
    
            if ((operands[0] and jump_type) or (not operands[0] and not jump_type)):
                self.pointer = operands[1]
            else:
                self.pointer += 3
    
        def less_than_instruction(self):
    
            operands = self.get_operands()
    
            if operands[0] < operands[1]:
                self.write_result(1, operands[2])
            else:
                self.write_result(0, operands[2])
    
            self.pointer += 4
    
        def equals_instruction(self):
    
            operands = self.get_operands()
    
            if operands[0] == operands[1]:
                self.write_result(1, operands[2])
            else:
                self.write_result(0, operands[2])
    
            self.pointer += 4
    
        def modify_base_instruction(self):
    
            operand = self.get_io_operand()
    
            self.relative_base += self.instructions[operand]
    
            self.pointer += 2
    
        def process_instructions(self, inputs=None):
    
            if inputs is not None:
                try:
                    for item in inputs:
                        self.input_buffer.insert(0, item)
                except TypeError: # Catch singular inputs here
                    self.input_buffer.insert(0,inputs)
    
            while True:
    
                if self.pointer > (len(self.instructions) - 1):
                    break
                elif self.instructions[self.pointer] == 99:
                    self.isHalted = True
                    break
    
                self.curr_instruction = [int(num) for num in str(self.instructions[self.pointer])]
    
                if self.curr_instruction[-1] == 1:
                    self.add_instruction()
                elif self.curr_instruction[-1] == 2:
                    self.mult_instruction()
                elif self.curr_instruction[-1] == 3:
                    self.input_instruction()
                elif self.curr_instruction[-1] == 4:
                    self.output_instruction()
                elif self.curr_instruction[-1] == 5:
                    self.jump_instruction(True)
                elif self.curr_instruction[-1] == 6:
                    self.jump_instruction(False)
                elif self.curr_instruction[-1] == 7:
                    self.less_than_instruction()
                elif self.curr_instruction[-1] == 8:
                    self.equals_instruction()
                elif self.curr_instruction[-1] == 9:
                    self.modify_base_instruction()
    
                if self.block:
                    self.block = False
                    return
    
    PaintBot
    import matplotlib.pyplot as plt
    
    class PaintBot(object):
    
        def __init__(self, location=None, first_color=0):
    
            if location is None:
                self.location = [0,0]
            else:
                self.location = location
    
            self.map = {tuple(self.location) : first_color}
            self.heading = 1 # Corresponds to up
    
        def paint(self, color):
    
            self.map[tuple(self.location)] = color
    
        def update_map(self):
            if tuple(self.location) not in self.map:
                self.map[tuple(self.location)] = 0
    
        def move(self, turn_instruction):
    
            if turn_instruction == 1: # Turn right
                self.heading += 1
            elif turn_instruction == 0: # Turn left
                self.heading -= 1
            else:
                raise ValueError("Improper turn instruction given!")
    
            # Bound the heading
            if self.heading == 0:
                self.heading = 4
            elif self.heading == 5:
                self.heading = 1
    
            # Move the robot
            if self.heading == 1:
                self.location[1] += 1
            elif self.heading == 2:
                self.location[0] += 1
            elif self.heading == 3:
                self.location[1] -= 1
            elif self.heading == 4:
                self.location [0] -= 1
            else:
                raise ValueError("Heading at improper value!")
    
            self.update_map()
    
        def read_current_tile(self):
            return self.map[tuple(self.location)]
    
        # This is a totally overkill solution lol
        def show_painting(self):
    
            data = {"x":[], "y":[]}
            for coord, color in self.map.items():
                if color:
                    data["x"].append(coord[0])
                    data["y"].append(coord[1])
    
            plt.figure()
            plt.scatter(data["x"], data["y"], marker = 'o')
            plt.show()
    
    Main - SpacePolice
    import copy
    from Intputer import Intputer
    from PaintBot import PaintBot
    
    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])
    
    robot_computer = Intputer(copy.copy(instructions))
    
    painter_robot = PaintBot(first_color = 1)
    
    while not robot_computer.isHalted:
    
        current_paint = painter_robot.read_current_tile()
    
        robot_computer.process_instructions(current_paint)
    
        needed_color = robot_computer.output_buffer[-2]
        needed_turn = robot_computer.output_buffer[-1]
    
        painter_robot.paint(needed_color)
        painter_robot.move(needed_turn)
    
    print(len(painter_robot.map))
    
    painter_robot.show_painting()
    
    3 votes