mirror of
https://github.com/cupcakearmy/advent-of-code.git
synced 2024-11-01 16:14:14 +01:00
113 lines
3.1 KiB
Python
113 lines
3.1 KiB
Python
|
#!/usr/bin/env python
|
||
|
|
||
|
from dataclasses import dataclass
|
||
|
from os.path import dirname, join
|
||
|
from typing import Literal
|
||
|
|
||
|
# Day 09
|
||
|
|
||
|
# Common
|
||
|
|
||
|
|
||
|
def read_input(filename):
|
||
|
data = join(dirname(__file__), '..', filename)
|
||
|
with open(data) as f:
|
||
|
return f.read().strip()
|
||
|
|
||
|
|
||
|
test = read_input('test.txt')
|
||
|
data = read_input('input.txt')
|
||
|
|
||
|
|
||
|
@dataclass(unsafe_hash=True)
|
||
|
class Point:
|
||
|
x: int
|
||
|
y: int
|
||
|
|
||
|
def __add__(self, other: 'Point'):
|
||
|
return Point(self.x + other.x, self.y + other.y)
|
||
|
|
||
|
def __sub__(self, other: 'Point'):
|
||
|
return Point(self.x - other.x, self.y - other.y)
|
||
|
|
||
|
def distance(self, other: 'Point') -> int:
|
||
|
return max(abs(self.x - other.x), abs(self.y-other.y))
|
||
|
|
||
|
def normalise(self):
|
||
|
self.x = max(-1, min(1, self.x))
|
||
|
self.y = max(-1, min(1, self.y))
|
||
|
return self
|
||
|
|
||
|
|
||
|
Directions = Literal['U', 'D', 'L', 'R']
|
||
|
Instruction = tuple[Directions, int]
|
||
|
|
||
|
MAPPING: dict[Directions, Point] = {'U': Point(0, 1), 'D': Point(0, -1), 'L': Point(-1, 0), 'R': Point(1, 0)}
|
||
|
|
||
|
|
||
|
class Rope:
|
||
|
def __init__(self, instructions: list[Instruction], links: int) -> None:
|
||
|
self.instructions = instructions
|
||
|
start = Point(0, 0)
|
||
|
self.touched: set[Point] = set([start])
|
||
|
self.links = [start for _ in range(links)]
|
||
|
|
||
|
def move(self, instruction: Instruction):
|
||
|
direction, travel = instruction
|
||
|
last = len(self.links) - 1
|
||
|
for _ in range(travel):
|
||
|
moved: list[Point] = []
|
||
|
for i, link in enumerate(self.links):
|
||
|
if i == 0:
|
||
|
link = link + MAPPING[direction]
|
||
|
else:
|
||
|
head = moved[i-1]
|
||
|
distance = head.distance(link)
|
||
|
if distance > 1:
|
||
|
link = link + (head - link).normalise()
|
||
|
moved.append(link)
|
||
|
if i == last:
|
||
|
self.touched.add(link)
|
||
|
self.links = moved
|
||
|
|
||
|
def run(self):
|
||
|
for instruction in self.instructions:
|
||
|
self.move(instruction)
|
||
|
return len(self.touched)
|
||
|
|
||
|
@staticmethod
|
||
|
def visualise(points: list[Point], numbered=True) -> str:
|
||
|
"""
|
||
|
Only used to visualise, not actual logic
|
||
|
"""
|
||
|
max_x = max(p.x for p in points)
|
||
|
min_x = min(p.x for p in points)
|
||
|
max_y = max(p.y for p in points)
|
||
|
min_y = min(p.y for p in points)
|
||
|
range_x = max_x - min_x
|
||
|
range_y = max_y - min_y
|
||
|
grid = [
|
||
|
['.' for _ in range(range_x+1)]
|
||
|
for _ in range(range_y+1)
|
||
|
]
|
||
|
for i, link in enumerate(points):
|
||
|
row = grid[range_y-(link.y - min_y)]
|
||
|
if row[link.x-min_x] == '.':
|
||
|
row[link.x-min_x] = str(i) if numbered else "#"
|
||
|
return '\n'.join(map(lambda line: ''.join(line), grid))
|
||
|
|
||
|
@staticmethod
|
||
|
def parse(data: str, links: int):
|
||
|
instructions = [
|
||
|
(i[0], int(i[1]))
|
||
|
for i in map(lambda line: line.split(), data.splitlines())
|
||
|
]
|
||
|
return Rope(instructions, links)
|
||
|
|
||
|
|
||
|
# Running
|
||
|
grid = Rope.parse(data, 2)
|
||
|
print(grid.run())
|
||
|
grid = Rope.parse(data, 10)
|
||
|
print(grid.run())
|