zerosleeps

Since 2010

Advent of Code 2022 day 10

My Python solution for Advent of Code 2022 day 10. Just the right amount of frustrating to keep it fun and interesting. I spent ages debugging part two because I kept confusing the “sprite” with the CRT’s current position - I was adding padding to the CRT’s position not the sprite’s position, so I kept getting answers that were always slightly off by weird amounts.

Anyway, here we go. (I’ve chopped out the “large example” input and the right-hand side of the part two equality test from this code block.)

import math
from unittest import TestCase
from utils import get_raw_input


class Process:
    def __init__(self, x_register=1):
        self.x_register = x_register
        self.current_instruction = None
        self.current_step = 0
        self.inputs = None

    def tick(self):
        if self.current_instruction:
            exec(f"self.{self.current_instruction}()")

    def new_instruction(self, instruction, inputs=None):
        if self.current_instruction != None:
            raise Exception("Already got an instruction")
        else:
            self.current_instruction = instruction
            self.current_step = 0
            self.inputs = inputs

    def noop(self):
        self.current_instruction = None

    def addx(self):
        if self.current_step < 1:
            self.current_step += 1
        else:
            self.x_register += int(self.inputs)
            self.current_instruction = None

    @property
    def ready(self):
        return not bool(self.current_instruction)


def run_program(process, instructions):
    cycles = [process.x_register]
    for i, instruction in enumerate(instructions):
        try:
            process.new_instruction(instruction.split()[0], instruction.split()[1])
        except IndexError:
            process.new_instruction(instruction)

        while not process.ready:
            process.tick()
            cycles.append(process.x_register)
    return cycles


def extract_signal_strength(cycles):
    return [(i + 1, value) for i, value in enumerate(cycles) if ((i % 40) - 19) == 0]


def calculate_signal_strength(signal_strengths):
    return sum([math.prod(strength) for strength in signal_strengths])


def build_screen(cycles):
    crt = []
    for y in range(6):
        row = []
        for x in range(40):
            sprite_centre = cycles[x + (y * 40)]
            if x in range(sprite_centre - 1, sprite_centre + 2):
                row.append("#")
            else:
                row.append(".")
        crt.append(row)
    return "\n".join(["".join(row) for row in crt])


def part_one(input):
    process = Process()
    cycles = run_program(process, input)
    return calculate_signal_strength(extract_signal_strength(cycles))


def part_two(input):
    process = Process()
    cycles = run_program(process, input)
    return build_screen(cycles)


class TestPartOne(TestCase):
    def setUp(self):
        self.small_example = [
            "noop",
            "addx 3",
            "addx -5",
        ]
        self.large_example = [
            REMOVED
        ]
        self.process = Process()

    def test_first_example_x_register(self):
        run_program(self.process, self.small_example)
        self.assertEqual(self.process.x_register, -1)

    def test_second_example_registers(self):
        cycles = run_program(self.process, self.large_example)
        self.assertEqual(cycles[19], 21)
        self.assertEqual(cycles[59], 19)
        self.assertEqual(cycles[99], 18)
        self.assertEqual(cycles[139], 21)
        self.assertEqual(cycles[179], 16)
        self.assertEqual(cycles[219], 18)

    def test_part_one_final_example(self):
        self.assertEqual(part_one(self.large_example), 13140)

    def test_part_two_example(self):
        self.assertEqual(
            part_two(self.large_example),
            REMOVED,
        )


if __name__ == "__main__":
    input = get_raw_input("day_10_input.txt")
    print(f"Part one: {part_one(input)}")
    print(f"Part two: \n{part_two(input)}")