mirror of
https://github.com/Crocmagnon/advent-of-code.git
synced 2024-11-21 14:08:11 +01:00
123 lines
3.3 KiB
Python
123 lines
3.3 KiB
Python
from __future__ import annotations
|
|
|
|
import dataclasses
|
|
|
|
|
|
def main(filename: str, expected_part_1: int = None, expected_part_2: int = None):
|
|
print(f"\n+ Running on {filename}")
|
|
with open(filename) as f:
|
|
data = f.read().strip().split("\n")
|
|
|
|
data = parse_data(data)
|
|
solution_part_1 = solve_part_1(data)
|
|
|
|
print(f"1. Found {solution_part_1}")
|
|
if expected_part_1:
|
|
assert expected_part_1 == solution_part_1
|
|
|
|
solution_part_2 = solve_part_2(data)
|
|
print(f"2. Found {solution_part_2}")
|
|
if expected_part_2:
|
|
assert expected_part_2 == solution_part_2
|
|
|
|
|
|
Instruction = tuple[str, int]
|
|
DataType = list[Instruction]
|
|
|
|
|
|
@dataclasses.dataclass
|
|
class Point:
|
|
x: int = 0
|
|
y: int = 0
|
|
|
|
def __eq__(self, other):
|
|
return (self.x, self.y) == (other.x, other.y)
|
|
|
|
def move(self, instruction: Instruction) -> None:
|
|
match instruction[0]:
|
|
case "R":
|
|
self.x += 1
|
|
case "L":
|
|
self.x -= 1
|
|
case "U":
|
|
self.y += 1
|
|
case "D":
|
|
self.y -= 1
|
|
|
|
def follow(self, other: Point) -> None:
|
|
if self == other:
|
|
return
|
|
if abs(self.x - other.x) <= 1 and abs(self.y - other.y) <= 1:
|
|
return
|
|
if other.x > self.x:
|
|
self.x += 1
|
|
elif other.x < self.x:
|
|
self.x -= 1
|
|
if other.y > self.y:
|
|
self.y += 1
|
|
elif other.y < self.y:
|
|
self.y -= 1
|
|
|
|
@property
|
|
def tuple(self) -> tuple[int, int]:
|
|
return self.x, self.y
|
|
|
|
|
|
def parse_data(data: list[str]) -> DataType:
|
|
instructions = []
|
|
for line in data:
|
|
direction, count = line.split()
|
|
instructions.append((direction, int(count)))
|
|
return instructions
|
|
|
|
|
|
def solve_part_1(instructions: DataType) -> int:
|
|
head = Point()
|
|
tail = Point()
|
|
visited = set()
|
|
for instruction in instructions:
|
|
count = instruction[1]
|
|
while count > 0:
|
|
head.move(instruction)
|
|
tail.follow(head)
|
|
tail_tuple = tail.tuple
|
|
visited.add(tail_tuple)
|
|
count -= 1
|
|
return len(visited)
|
|
|
|
|
|
def solve_part_2(instructions: DataType) -> int:
|
|
head = Point()
|
|
tails = [Point() for _ in range(9)]
|
|
visited = set()
|
|
for instruction in instructions:
|
|
print(instruction)
|
|
count = instruction[1]
|
|
while count > 0:
|
|
head.move(instruction)
|
|
tails[0].follow(head)
|
|
for i in range(1, len(tails)):
|
|
tails[i].follow(tails[i - 1])
|
|
tail_tuple = tails[-1].tuple
|
|
visited.add(tail_tuple)
|
|
count -= 1
|
|
# print_grid([head, *tails])
|
|
return len(visited)
|
|
|
|
|
|
def print_grid(rope: list[Point], size: tuple[int, int] = (21, 27)) -> None:
|
|
rope = [knot.tuple for knot in rope]
|
|
for row in range(int(-size[0] / 2), int(size[0] / 2)):
|
|
for col in range(int(-size[1] / 2), int(size[1] / 2)):
|
|
try:
|
|
char = rope.index((col, row))
|
|
except ValueError:
|
|
char = "."
|
|
print(char, end="")
|
|
print()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main("inputs/day09-test1", expected_part_1=13, expected_part_2=1)
|
|
main("inputs/day09-test2", expected_part_2=36)
|
|
main("inputs/day09", expected_part_1=5619, expected_part_2=2376)
|