Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- from __future__ import annotations
- class Coordinate:
- def __init__(self, x: int, y: int):
- self.x = x
- self.y = y
- def __str__(self):
- return f"(x={self.x}, y={self.y})"
- def __repr__(self):
- return f"{self.__class__.__name__}({self.x}, {self.y})"
- def __iter__(self):
- yield self.x
- yield self.y
- def is_adjacent(self, other: Coordinate) -> bool:
- """Check if Coordinate is adjacent to other Coordinate"""
- return (abs(self.x - other.x) <= 1) and (abs(self.y - other.y) <= 1)
- def update_position(self, position) -> Coordinate:
- return Coordinate(self.x + position[0], self.y + position[1])
- def distance(self, other: Coordinate) -> int:
- """Absolute distance between two coordinates"""
- return abs(self.x - other.x) + abs(self.y - other.y)
- class Rope:
- def __init__(self, n_segments=2):
- self.segments = [Coordinate(0, 0) for _ in range(n_segments)]
- def __str__(self):
- return str([str(segment) for segment in self.segments])
- @property
- def head(self):
- return self.segments[0]
- @head.setter
- def head(self, value):
- self.segments[0] = value
- @head.getter
- def head(self):
- return self.segments[0]
- @property
- def tail(self):
- return self.segments[-1]
- class RopeMover:
- DIRECTIONS = {"R": (1, 0), "L": (-1, 0), "D": (0, -1), "U": (0, 1)}
- def __init__(self, n_segments=2):
- self.rope = Rope(n_segments=n_segments)
- self.tail_positions = {tuple(self.rope.tail)}
- self.seen_count = 0
- def __repr__(self):
- return str(self.rope)
- def process_instruction(self, instruction: list[str]):
- direction, distance = self.DIRECTIONS[instruction[0]], int(instruction[1])
- for _ in range(distance):
- self.move_head(direction)
- self.update_body()
- self.assess_tail_position()
- def move_head(self, direction):
- self.rope.head = self.rope.head.update_position(direction)
- def update_body(self):
- for i, segment in enumerate(self.rope.segments[1:], start=1):
- if segment.is_adjacent(self.rope.segments[i - 1]):
- continue
- else:
- self.move_segment(i)
- def move_segment(self, segment_index: int):
- x_move, y_move = 0, 0
- # horizontal or vertical move
- if (
- self.rope.segments[segment_index].distance(
- self.rope.segments[segment_index - 1]
- )
- == 2
- ):
- prev, current = (
- self.rope.segments[segment_index - 1],
- self.rope.segments[segment_index],
- )
- x_move, y_move = 0.5 * (prev.x - current.x), 0.5 * (prev.y - current.y)
- # diagonal move
- elif (
- self.rope.segments[segment_index].distance(
- self.rope.segments[segment_index - 1]
- )
- == 3
- ):
- prev, current = (
- self.rope.segments[segment_index - 1],
- self.rope.segments[segment_index],
- )
- x_dist, y_dist = (prev.x - current.x), (prev.y - current.y)
- if abs(x_dist) > abs(y_dist):
- x_move, y_move = x_dist * 0.5, y_dist
- else:
- x_move, y_move = x_dist, y_dist * 0.5
- elif (
- self.rope.segments[segment_index].distance(
- self.rope.segments[segment_index - 1]
- )
- == 4
- ):
- prev, current = (
- self.rope.segments[segment_index - 1],
- self.rope.segments[segment_index],
- )
- x_dist, y_dist = (prev.x - current.x), (prev.y - current.y)
- x_move, y_move = x_dist * 0.5, y_dist * 0.5
- self.rope.segments[segment_index] = self.rope.segments[
- segment_index
- ].update_position((int(x_move), int(y_move)))
- def assess_tail_position(self):
- self.tail_positions.add(tuple(self.rope.tail))
- def part_one(filepath):
- rm = RopeMover(2)
- with open(filepath, "r") as f:
- for line in f.readlines():
- rm.process_instruction(line.strip().split())
- return len(rm.tail_positions)
- def part_two(filepath):
- rm = RopeMover(10)
- with open(filepath, "r") as f:
- for line in f.readlines():
- rm.process_instruction(line.strip().split())
- return len(rm.tail_positions)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement