AoC 2016 Day 8: Tiny Screen Simulator

Source: Two-Factor Authentication

Part 1: Implement a 50x6 pixel screen with the following commands:

  • rect AxB turn on a rectangle of pixels in the top left corner
  • rotate row y=A by B rotates row A right by B pixels
  • rotate column x=A by B rotates column A down by B pixels

After a given sequence of commands, how many pixels are on?

Let’s make this one object oriented:

class Screen(object):
    def __init__(self, width, height):
        self.width = width
        self.height = height
        self.data = [([False] * self.width) for i in range(self.height)]

    def __str__(self):
        return 'Screen<{}, {}>\n{}'.format(
            self.width,
            self.height,
            '\n'.join(''.join(('#' if el else '-') for el in row) for row in self.data),
        )

    def __len__(self):
        return sum(
            (1 if self[x, y] else 0)
            for x in range(self.width)
            for y in range(self.height)
        )

    def __getitem__(self, pt):
        (x, y) = pt
        return self.data[y % self.height][x % self.width]

    def __setitem__(self, pt, value):
        (x, y) = pt
        self.data[y % self.height][x % self.width] = value

This will allow us to use len(screen) to determine the number of lit pixels and screen[x, y] = True to turn on pixels1.

With that, we can implement drawing and use len for the final answer:

screen = Screen(args.width, args.height)

with open(args.input, 'r') as fin:
    for line in fin:
        print(line)

        m_rect = re_rect.match(line)
        m_rotate = re_rotate.match(line)

        if m_rect:
            for x in range(int(m_rect.group('width'))):
                for y in range(int(m_rect.group('height'))):
                    screen[x, y] = True

        elif m_rotate:
            offset = int(m_rotate.group('offset'))
            index = int(m_rotate.group('index'))

            if m_rotate.group('mode') == 'column':
                new_data = [screen[index, y + yd] for yd in range(screen.height)]
                for yd in range(screen.height):
                    screen[index, y + yd + offset] = new_data[yd]
            else:
                new_data = [screen[x + xd, index] for xd in range(screen.width)]
                for xd in range(screen.width):
                    screen[x + xd + offset, index] = new_data[xd]

        print(screen)
        print()

print('active cells', len(screen))

Part 2: What is the screen displaying?

I didn’t actually automate this one (although it’s certainly possible). Instead, I just printed out the current screen, which shows a string of capital letters in a minimal font:

> $ python3 tiny-screen-simulator.py input.txt

...

Screen<50, 6>
-##--####-#----####-#-----##--#---#####--##---###-
#--#-#----#----#----#----#--#-#---##----#--#-#----
#----###--#----###--#----#--#--#-#-###--#----#----
#----#----#----#----#----#--#---#--#----#-----##--
#--#-#----#----#----#----#--#---#--#----#--#----#-
-##--#----####-####-####--##----#--#-----##--###--

active cells 106

That’s actually pretty cool. I wonder how hard it would be to write a script to generate a minimal set of commands to generate text like that?


  1. How does that work? It turns out that the comma creates tuples, not the paranthesis. So screen[x, y] is calling Screen.__getitem__ and passing a single argument2: (x, y)↩︎

  2. Other than self of course. ↩︎