 Continuing with the computer, now I’m using it to power a robot. My robot will walk around, reading the current color, submitting that to the program, and getting back the color to paint the current square and instructions for where to move next.

## Challenge

The puzzle can be found here. I need to write the code for a robot that will start on an all black (0) grid at 0,0. It will input the color of the current panel to the computer, read the first output which tells it to pain the current square black or white (1). Then it reads another output from the computer which tells it to turn left (0) or right (1). Then it moves forward one space. In part 1, I just need to count the number of squares painted at least once. In part 2, I’ll need to change the initial square to white, and then print the output.

## Solution

### Part 1

I made some slight changes to my computer. I added back in the done flag that will be False until I get an opcode 99. I also now set compute to return with opcode 4, rather than print.

Then I wrote the robot. I’ll make another class. At start, it will have a position and velocity, as well as a defaultdict to track the panels. That’s really handy, because then to count the number of painted panels, I just have to count the keys in the defaultdict. I’ll also have a computer object.

    turn = {
0: {(0, 1): (-1, 0), (-1, 0): (0, -1), (0, -1): (1, 0), (1, 0): (0, 1)},
1: {(0, 1): (1, 0), (1, 0): (0, -1), (0, -1): (-1, 0), (-1, 0): (0, 1)},
}

def __init__(self, program):
self.pos = (0, 0)
self.vel = (0, 1)
self.panels = defaultdict(int)
self.comp = computer(program)


I’ve also included a constant dictionaru to help with turning. I’ll create a move function to handle the move:

    def move(self, direction):
self.vel = self.turn[direction][self.vel]


Now I’ll create a paint function that will run the program. It checks the done flag in the computer to know when to stop. Then it calls compute twice. The first time it passes in the current square color. The second time it passes in None. This will break things if the program tries to read in the second compute, but that’s what I want, since I’m assuming it won’t do that based on the puzzle description.

Running this gives me the answer instantly:

$time ./day11.py 11-puzzle_input.txt Part 1: 2041 real 0m0.124s user 0m0.120s sys 0m0.004s  ### Part 2 For part 2, I need to change one thing in how I initialize - the starting square can be white. I’ll update the __init__ to take an optional start_color argument (default 0), and set self.panels[self.pos] = start_color. I’ll also add another constant dictionary to help draw the robot:  icon = {(0, 1): "^", (-1, 0): "<", (0, -1): "v", (1, 0): ">"}  Now I just need to write a draw function. I’ll find the corners of the canvas first, then I’ll loop over y (remembering that y grows as it goes down, which is opposite my intuition). For each y, I’ll create a row of output by looping over the x range, and then printing it.  def draw(self): minx = min(self.panels, key=operator.itemgetter(0)) maxx = max(self.panels, key=operator.itemgetter(0)) miny = min(self.panels, key=operator.itemgetter(1)) maxy = max(self.panels, key=operator.itemgetter(1)) for y in range(maxy + 1, miny - 2, -1): out = "" for x in range(minx - 1, maxx + 2): if self.pos == (x, y): out += self.icon[self.vel] else: out += str(self.panels[(x, y)]) print(out.replace("1", chr(9608)).replace("0", " "))  Just like in day8, I’ll use the unicode block character to print nice solid squares, and space to print empty ones. This runs instantly, and paints 8 characters as expected: $ time ./day11.py 11-puzzle_input.txt
Part 1: 2041
Part 2:

████ ███  ████ ███  █  █ ████ ████ ███
█ █  █    █ █  █ █ █  █       █ █  █
█  █  █   █  █  █ ██   ███    █  █  █
█   ███   █   ███  █ █  █     █   ███  ^
█    █ █  █    █    █ █  █    █    █ █
████ █  █ ████ █    █  █ ████ ████ █  █

real    0m0.140s
user    0m0.136s
sys     0m0.004s


Here’s an image from my terminal in case the text doesn’t format right: ## Final Code

#!/usr/bin/env python3

import sys
import operator
from collections import defaultdict
from itertools import permutations

class computer:
def __init__(self, program):
self.program = defaultdict(int)
for i, v in enumerate(list(map(int, program.split(",")))):
self.program[i] = v
self.done = False
self.eip = 0
self.rel_base = 0

def get_param(self, mode, reg):
value = self.program[self.eip + reg]
if mode == "0":
return self.program[value]
elif mode == "1":
return value
elif mode == "2":
return self.program[self.rel_base + value]
else:
print("Error: Invalid Parameter Mode")
sys.exit()

value = self.program[self.eip + reg]
if mode == "0":
return value
elif mode == "2":
return self.rel_base + value
else:
sys.exit()

def compute(self, signal):
while True:
inst = self.program[self.eip]
op = inst % 100
mode3, mode2, mode1 = f"{inst // 100:03d}"
if op == 1:
mode1, 1
) + self.get_param(mode2, 2)
self.eip += 4
elif op == 2:
mode1, 1
) * self.get_param(mode2, 2)
self.eip += 4
elif op == 3:
self.eip += 2
elif op == 4:
self.eip += 2
return self.get_param(mode1, 1 - 2)
elif op == 5:
if self.get_param(mode1, 1) != 0:
self.eip = self.get_param(mode2, 2)
else:
self.eip += 3
elif op == 6:
if self.get_param(mode1, 1) == 0:
self.eip = self.get_param(mode2, 2)
else:
self.eip += 3
elif op == 7:
self.get_param(mode1, 1) < self.get_param(mode2, 2)
)
self.eip += 4
elif op == 8:
self.get_param(mode1, 1) == self.get_param(mode2, 2)
)
self.eip += 4
elif op == 9:
self.rel_base += self.get_param(mode1, 1)
self.eip += 2
elif op == 99:
self.done = True
return 0
else:
print("Error")
sys.exit()

class robot:
turn = {
0: {(0, 1): (-1, 0), (-1, 0): (0, -1), (0, -1): (1, 0), (1, 0): (0, 1)},
1: {(0, 1): (1, 0), (1, 0): (0, -1), (0, -1): (-1, 0), (-1, 0): (0, 1)},
}
icon = {(0, 1): "^", (-1, 0): "<", (0, -1): "v", (1, 0): ">"}

def __init__(self, program, start_color=0):
self.pos = (0, 0)
self.vel = (0, 1)
self.panels = defaultdict(int)
self.panels[self.pos] = start_color
self.comp = computer(program)

def move(self, direction):
self.vel = self.turn[direction][self.vel]

def paint(self):
while not self.comp.done:
self.panels[self.pos] = self.comp.compute(self.panels[self.pos])
self.move(self.comp.compute(None))

def draw(self):
minx = min(self.panels, key=operator.itemgetter(0))
maxx = max(self.panels, key=operator.itemgetter(0))
miny = min(self.panels, key=operator.itemgetter(1))
maxy = max(self.panels, key=operator.itemgetter(1))
for y in range(maxy + 1, miny - 2, -1):
out = ""
for x in range(minx - 1, maxx + 2):
if self.pos == (x, y):
out += self.icon[self.vel]
else:
out += str(self.panels[(x, y)])
print(out.replace("1", chr(9608)).replace("0", " "))

with open(sys.argv, "r") as f: