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
|