summaryrefslogtreecommitdiffstats
path: root/day9/__init__.py
blob: e0f1d61031ebb2ca2b0634e13f4487c8a8101028 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# -*- 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) < 2 and abs(delta.y) < 2:
            pass
        elif abs(delta.x) > 1 and delta.y == 0:
            tail.x += delta.x - delta.polarity_x
        elif abs(delta.x) > 1 and abs(delta.y) == 1:
            tail.x += delta.x - delta.polarity_x
            tail.y += delta.y
        elif abs(delta.y) > 1 and delta.x == 0:
            tail.y += delta.y - delta.polarity_y
        elif abs(delta.y) > 1 and abs(delta.x) == 1:
            tail.y += delta.y - delta.polarity_y
            tail.x += delta.x
        elif abs(delta.x) > 1 and abs(delta.y) > 1:
            tail.x += delta.x - delta.polarity_x
            tail.y += 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:
                        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