# -*- coding: utf-8 -*- from abc import ABC from copy import copy from dataclasses import dataclass from typing import Tuple, Iterator, List, Set from aoc import BaseAssignment @dataclass class Coordinate: x: int y: int def __hash__(self): return tuple([self.x, self.y]).__hash__() def __sub__(self, other: "Coordinate"): return Coordinate(self.x - other.x, self.y - other.y) @property def polarity_x(self): try: return abs(self.x) / self.x except ZeroDivisionError: return 0 @property def polarity_y(self): try: return abs(self.y) / self.y except ZeroDivisionError: return 0 class Assignment(BaseAssignment, ABC): rope_length = NotImplemented def parse_line(self, item: str) -> Tuple[str, int]: direction, amount = item.split(" ") return direction, int(amount) def move(self, direction: str, amount: int): pass @staticmethod def next_head(head: Coordinate, direction: str): match direction: case "R": head.x += 1 case "L": head.x -= 1 case "U": head.y += 1 case "D": head.y -= 1 @staticmethod def next_knot(head: Coordinate, tail: Coordinate): delta = head - tail if abs(delta.x) > 1 and delta.y == 0: tail.x += delta.x - delta.polarity_x if abs(delta.x) > 1 and abs(delta.y) == 1: tail.x += delta.x - delta.polarity_x tail.y += delta.y if abs(delta.y) > 1 and delta.x == 0: tail.y += delta.y - delta.polarity_y if abs(delta.y) > 1 and abs(delta.x) == 1: tail.y += delta.y - delta.polarity_y tail.x += delta.x def tail_positions(self, input: Iterator[str], length: int) -> Iterator[Coordinate]: knots = [Coordinate(0, 0) for _ in range(length)] for line in input: direction, amount = self.parse_line(line) for _ in range(amount): for index in range(length): if index == 0: self.next_head(knots[index], direction) continue self.next_knot(knots[index - 1], knots[index]) yield knots[length - 1] def unique_tail_positions( self, input: Iterator[str], length: int ) -> Set[Coordinate]: unique_tail_positions = set() for position in self.tail_positions(input, length): unique_tail_positions.add(copy(position)) return unique_tail_positions def visualize(self, width: range, height: range, positions: Set[Coordinate]): rows = [] for y in height: items = [] for x in width: if x == 0 and y == 0: items.append("s") elif Coordinate(x, y) in positions: items.append("#") else: items.append(".") rows.append(items) return "\n".join(["".join(row) for row in reversed(rows)]) def run(self, input: Iterator) -> int: unique_positions = self.unique_tail_positions(input, length=self.rope_length) return len(unique_positions) class AssignmentOne(Assignment): example_result = 13 rope_length = 2 class AssignmentTwo(Assignment): example_result = 36 rope_length = 10