# -*- coding: utf-8 -*- from abc import ABC from copy import copy from typing import Tuple, Iterator, List, Set from aoc import BaseAssignment from aoc.datastructures import Coordinate 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) -> Coordinate: match direction: case "R": return head + Coordinate(1, 0) case "L": return head + Coordinate(-1, 0) case "U": return head + Coordinate(0, 1) case "D": return head + Coordinate(0, -1) @staticmethod def next_knot(head: Coordinate, tail: Coordinate) -> Coordinate: delta = head - tail if abs(delta.x) < 2 and abs(delta.y) < 2: return tail elif abs(delta.x) > 1 and delta.y == 0: return tail + Coordinate( delta.x - delta.polarity_x, 0, ) elif abs(delta.x) > 1 and abs(delta.y) == 1: return tail + Coordinate(delta.x - delta.polarity_x, delta.y) elif abs(delta.y) > 1 and delta.x == 0: return tail + Coordinate( 0, delta.y - delta.polarity_y, ) elif abs(delta.y) > 1 and abs(delta.x) == 1: return tail + Coordinate( delta.x, delta.y - delta.polarity_y, ) elif abs(delta.x) > 1 and abs(delta.y) > 1: return tail + Coordinate( delta.x - delta.polarity_x, delta.y - delta.polarity_y ) 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: knots[0] = self.next_head(knots[0], direction) continue knots[index] = 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