Continuing with the computer, now I’m using it to power an arcade game. I’ll use the given intcodes to run the game, and I’m responsible for moving the joystick via input to the game. This challenge was awesome. I made a video of the game running in my terminal, which wasn’t necessary, but turned out pretty good.

Challenge

The puzzle can be found here. I need to write the code for an aracade game that is a take on the classic game Breakout. The program will draw the blocks on the screen. In part 1, it just paints a board, and I need to count the number of blocks. For part two, I change the input into game mode, and then I’m also responsible for moving the joystick and trying to keep the ball alive.

Part 1

Part 1 was really easy. I’ve already got the Computer. I just need to create a Computer object using the program input and then run it until it finishing, counting the number of times it returns a block type (every 3rd output) of 2.

comp = Computer(program_str)
count = 0
while not comp.done:
    _, _, block = comp.compute(None), comp.compute(None), comp.compute(None)
    if block == 2:
        count += 1

print(f'Part 1: {count}')

That runs and returns the answer:

$ time ./day13.py 13-puzzle_input.txt 
Part 1: 251

real    0m0.066s
user    0m0.059s
sys     0m0.004s

Part 2

Now I need to wrap the arcade game around this computer. I started to create a Breakout class, but then realized that I’d be doing the entire game in the __init__ function. I created a

program_str = '2' + program_str[1:]
comp = Computer(program_str)
screen = defaultdict(int)
joystick = 0
score = 0

while not comp.done:
    x = comp.compute(joystick)
    y = comp.compute(joystick)
    b = comp.compute(joystick)

    if (x, y) == (-1, 0):
        score = b
    else:
        screen[(x,y)] = b

Now I wanted to see the screen, so I created a print_screen function as well:

def print_screen(screen, score):
    icons = {0: ' ', 1: chr(9608), 2: '.', 3: '_', 4: 'o'}
    for y in range(20):
        output = ''
        for x in range(37):
            output += icons[screen[(x,y)]]
        print(output)
    print(f'  {"-"*16}{score:05d}{"-"*16}')

I’ll add a call at the end of each loop. The ranges or 30x37 comes from playing with it a few times to get the dimensions correct.

The first print looks like:

█                                    
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     








--000000--

Then a few more:

█████                                
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
--000000--

It’s building the top across the board. I’ll skip forward a bunch, and can see the board built, with walls and blocks, and then the ball is added:

█████████████████████████████████████
█                                   █
█     .. .. ..........  .  .. . ... █
█ ... .. ............ . ...... .    █
█    .... .  ..  ... ... .. . . . . █
█ .... ...... ....... ..  ...   ... █
█  ........ . ..  ... .. ... .    . █
█ .. .  .. .. .. ........   ... ..  █
█ ...  .... ..  .... . ........ .   █
█   .... ... .. .. ... ..   .. ..   █
█  . .   .. .   .  .. ... ... . ..  █
█ .....  . .. ...   ...  .. ... ..  █
█  ....  .  .... ...   .   .. . ... █
█ ..   ...  .    .... . ..  .  .... █
█                                   █
█               o                    
                                     
                                     
                                     
                                     
--000000--

A bunch more cycles and I get the paddle:

█████████████████████████████████████
█                                   █
█     .. .. ..........  .  .. . ... █
█ ... .. ............ . ...... .    █
█    .... .  ..  ... ... .. . . . . █
█ .... ...... ....... ..  ...   ... █
█  ........ . ..  ... .. ... .    . █
█ .. .  .. .. .. ........   ... ..  █
█ ...  .... ..  .... . ........ .   █
█   .... ... .. .. ... ..   .. ..   █
█  . .   .. .   .  .. ... ... . ..  █
█ .....  . .. ...   ...  .. ... ..  █
█  ....  .  .... ...   .   .. . ... █
█ ..   ...  .    .... . ..  .  .... █
█                                   █
█               o                   █
█                                   █
█                                   █
█                 _                 █
█                                   █
--000000--

Then the ball starts moving diagonally down to the right and bounces off the paddle:

█████████████████████████████████████
█                                   █
█     .. .. ..........  .  .. . ... █
█ ... .. ............ . ...... .    █
█    .... .  ..  ... ... .. . . . . █
█ .... ...... ....... ..  ...   ... █
█  ........ . ..  ... .. ... .    . █
█ .. .  .. .. .. ........   ... ..  █
█ ...  .... ..  .... . ........ .   █
█   .... ... .. .. ... ..   .. ..   █
█  . .   .. .   .  .. ... ... . ..  █
█ .....  . .. ...   ...  .. ... ..  █
█  ....  .  .... ...   .   .. . ... █
█ ..   ...  .    .... . ..  .  .... █
█                                   █
█                                   █
█                o                  █
█                                   █
█                 _                 █
█                                   █
--000000--

█████████████████████████████████████
█                                   █
█     .. .. ..........  .  .. . ... █
█ ... .. ............ . ...... .    █
█    .... .  ..  ... ... .. . . . . █
█ .... ...... ....... ..  ...   ... █
█  ........ . ..  ... .. ... .    . █
█ .. .  .. .. .. ........   ... ..  █
█ ...  .... ..  .... . ........ .   █
█   .... ... .. .. ... ..   .. ..   █
█  . .   .. .   .  .. ... ... . ..  █
█ .....  . .. ...   ...  .. ... ..  █
█  ....  .  .... ...   .   .. . ... █
█ ..   ...  .    .... . ..  .  .... █
█                                   █
█                                   █
█                                   █
█                 o                 █
█                 _                 █
█                                   █
--000000--
█████████████████████████████████████
█                                   █
█     .. .. ..........  .  .. . ... █
█ ... .. ............ . ...... .    █
█    .... .  ..  ... ... .. . . . . █
█ .... ...... ....... ..  ...   ... █
█  ........ . ..  ... .. ... .    . █
█ .. .  .. .. .. ........   ... ..  █
█ ...  .... ..  .... . ........ .   █
█   .... ... .. .. ... ..   .. ..   █
█  . .   .. .   .  .. ... ... . ..  █
█ .....  . .. ...   ...  .. ... ..  █
█  ....  .  .... ...   .   .. . ... █
█ ..   ...  .    .... . ..  .  .... █
█                                   █
█                                   █
█                  o                █
█                                   █
█                 _                 █
█                                   █
--000000--
█████████████████████████████████████
█                                   █
█     .. .. ..........  .  .. . ... █
█ ... .. ............ . ...... .    █
█    .... .  ..  ... ... .. . . . . █
█ .... ...... ....... ..  ...   ... █
█  ........ . ..  ... .. ... .    . █
█ .. .  .. .. .. ........   ... ..  █
█ ...  .... ..  .... . ........ .   █
█   .... ... .. .. ... ..   .. ..   █
█  . .   .. .   .  .. ... ... . ..  █
█ .....  . .. ...   ...  .. ... ..  █
█  ....  .  .... ...   .   .. . ... █
█ ..   ...  .    .... . ..  .  .... █
█                                   █
█                   o               █
█                                   █
█                                   █
█                 _                 █
█                                   █
--000000--

I’ll need to add code to watch the ball and set the joystick. I’ll find them using a list comprehension:

    ball = [p for p in screen if screen[p] == 4]
    paddle = [p for p in screen if screen[p] == 3]

Each of those should return [] before the item is added to the screen or if it’s taken off because it’s been moved, and [(x,y)] otherwise.

Now I’ll just tilt my joystick to follow the ball:

    if ball and paddle:
        if ball[0][0] < paddle[0][0]:
            joystick = -1
        elif ball[0][0] > paddle[0][0]:
            joystick = 1
        else:
            joystick = 0

At this point I have all I need to run to completion and print the score:

$ time ./day13.py 13-puzzle_input.txt
Part 1: 251
Part 2: 12779

real    0m2.627s
user    0m2.623s
sys     0m0.004s

But I wanted to add the graphic part to this. I’ll add a variable need_to_print, set to False at the start of each loop. If the score or the ball position changes, I’ll set it to true. I’ll print the graphic version if sys.argv[2] == watch. I’ll also print the code to clear the terminal before each print, so it looks like a video. It looks pretty good for terminal output:

Final Code

#!/usr/bin/env python3

import sys
import time
from collections import defaultdict


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()

    def get_address(self, mode, reg):
        value = self.program[self.eip + reg]
        if mode == "0":
            return value
        elif mode == "2":
            return self.rel_base + value
        else:
            print("Error: Invalid Address Mode")
            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:
                self.program[self.get_address(mode3, 3)] = self.get_param(
                    mode1, 1
                ) + self.get_param(mode2, 2)
                self.eip += 4
            elif op == 2:
                self.program[self.get_address(mode3, 3)] = self.get_param(
                    mode1, 1
                ) * self.get_param(mode2, 2)
                self.eip += 4
            elif op == 3:
                self.program[self.get_address(mode1, 1)] = signal
                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.program[self.get_address(mode3, 3)] = int(
                    self.get_param(mode1, 1) < self.get_param(mode2, 2)
                )
                self.eip += 4
            elif op == 8:
                self.program[self.get_address(mode3, 3)] = int(
                    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()


def print_screen(screen, score):
    print("\33[2J")
    icons = {0: " ", 1: chr(9608), 2: ".", 3: "_", 4: "o"}
    for y in range(20):
        output = "  "
        for x in range(37):
            output += icons[screen[(x, y)]]
        print(output)
    print(f'  {"-"*16}{score:05d}{"-"*16}')


with open(sys.argv[1], "r") as f:
    program_str = f.read().strip()


comp = Computer(program_str)
count = 0
while not comp.done:
    _, _, block = comp.compute(None), comp.compute(None), comp.compute(None)
    if block == 2:
        count += 1


watch = int(len(sys.argv) > 2 and sys.argv[2] == "watch")
program_str = "2" + program_str[1:]
comp = Computer(program_str)
screen = defaultdict(int)
joystick = 0
score = 0

while not comp.done:
    need_to_print = False
    x = comp.compute(joystick)
    y = comp.compute(joystick)
    b = comp.compute(joystick)

    if (x, y) == (-1, 0):
        score = b
        need_to_print = True
    else:
        screen[(x, y)] = b

    if b == 4:
        need_to_print = True

    ball = [p for p in screen if screen[p] == 4]
    paddle = [p for p in screen if screen[p] == 3]

    if ball and paddle:
        if ball[0][0] < paddle[0][0]:
            joystick = -1
        elif ball[0][0] > paddle[0][0]:
            joystick = 1
        else:
            joystick = 0

    if watch and need_to_print:
        print_screen(screen, score)
        time.sleep(0.05)

print(f"Part 1: {count}")
print(f"Part 2: {score}")