Advertisement
JonathanGupton

Advent of Code 2022 - Day 9

Dec 9th, 2022
335
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 4.53 KB | None | 0 0
  1. from __future__ import annotations
  2.  
  3.  
  4. class Coordinate:
  5.     def __init__(self, x: int, y: int):
  6.         self.x = x
  7.         self.y = y
  8.  
  9.     def __str__(self):
  10.         return f"(x={self.x}, y={self.y})"
  11.  
  12.     def __repr__(self):
  13.         return f"{self.__class__.__name__}({self.x}, {self.y})"
  14.  
  15.     def __iter__(self):
  16.         yield self.x
  17.         yield self.y
  18.  
  19.     def is_adjacent(self, other: Coordinate) -> bool:
  20.         """Check if Coordinate is adjacent to other Coordinate"""
  21.         return (abs(self.x - other.x) <= 1) and (abs(self.y - other.y) <= 1)
  22.  
  23.     def update_position(self, position) -> Coordinate:
  24.         return Coordinate(self.x + position[0], self.y + position[1])
  25.  
  26.     def distance(self, other: Coordinate) -> int:
  27.         """Absolute distance between two coordinates"""
  28.         return abs(self.x - other.x) + abs(self.y - other.y)
  29.  
  30.  
  31. class Rope:
  32.     def __init__(self, n_segments=2):
  33.         self.segments = [Coordinate(0, 0) for _ in range(n_segments)]
  34.  
  35.     def __str__(self):
  36.         return str([str(segment) for segment in self.segments])
  37.  
  38.     @property
  39.     def head(self):
  40.         return self.segments[0]
  41.  
  42.     @head.setter
  43.     def head(self, value):
  44.         self.segments[0] = value
  45.  
  46.     @head.getter
  47.     def head(self):
  48.         return self.segments[0]
  49.  
  50.     @property
  51.     def tail(self):
  52.         return self.segments[-1]
  53.  
  54.  
  55. class RopeMover:
  56.     DIRECTIONS = {"R": (1, 0), "L": (-1, 0), "D": (0, -1), "U": (0, 1)}
  57.  
  58.     def __init__(self, n_segments=2):
  59.         self.rope = Rope(n_segments=n_segments)
  60.         self.tail_positions = {tuple(self.rope.tail)}
  61.         self.seen_count = 0
  62.  
  63.     def __repr__(self):
  64.         return str(self.rope)
  65.  
  66.     def process_instruction(self, instruction: list[str]):
  67.         direction, distance = self.DIRECTIONS[instruction[0]], int(instruction[1])
  68.         for _ in range(distance):
  69.             self.move_head(direction)
  70.             self.update_body()
  71.             self.assess_tail_position()
  72.  
  73.     def move_head(self, direction):
  74.         self.rope.head = self.rope.head.update_position(direction)
  75.  
  76.     def update_body(self):
  77.         for i, segment in enumerate(self.rope.segments[1:], start=1):
  78.             if segment.is_adjacent(self.rope.segments[i - 1]):
  79.                 continue
  80.             else:
  81.                 self.move_segment(i)
  82.  
  83.     def move_segment(self, segment_index: int):
  84.         x_move, y_move = 0, 0
  85.  
  86.         # horizontal or vertical move
  87.         if (
  88.             self.rope.segments[segment_index].distance(
  89.                 self.rope.segments[segment_index - 1]
  90.             )
  91.             == 2
  92.         ):
  93.             prev, current = (
  94.                 self.rope.segments[segment_index - 1],
  95.                 self.rope.segments[segment_index],
  96.             )
  97.             x_move, y_move = 0.5 * (prev.x - current.x), 0.5 * (prev.y - current.y)
  98.  
  99.         # diagonal move
  100.         elif (
  101.             self.rope.segments[segment_index].distance(
  102.                 self.rope.segments[segment_index - 1]
  103.             )
  104.             == 3
  105.         ):
  106.             prev, current = (
  107.                 self.rope.segments[segment_index - 1],
  108.                 self.rope.segments[segment_index],
  109.             )
  110.             x_dist, y_dist = (prev.x - current.x), (prev.y - current.y)
  111.             if abs(x_dist) > abs(y_dist):
  112.                 x_move, y_move = x_dist * 0.5, y_dist
  113.             else:
  114.                 x_move, y_move = x_dist, y_dist * 0.5
  115.  
  116.         elif (
  117.             self.rope.segments[segment_index].distance(
  118.                 self.rope.segments[segment_index - 1]
  119.             )
  120.             == 4
  121.         ):
  122.             prev, current = (
  123.                 self.rope.segments[segment_index - 1],
  124.                 self.rope.segments[segment_index],
  125.             )
  126.             x_dist, y_dist = (prev.x - current.x), (prev.y - current.y)
  127.             x_move, y_move = x_dist * 0.5, y_dist * 0.5
  128.  
  129.         self.rope.segments[segment_index] = self.rope.segments[
  130.             segment_index
  131.         ].update_position((int(x_move), int(y_move)))
  132.  
  133.     def assess_tail_position(self):
  134.         self.tail_positions.add(tuple(self.rope.tail))
  135.  
  136.  
  137. def part_one(filepath):
  138.     rm = RopeMover(2)
  139.     with open(filepath, "r") as f:
  140.         for line in f.readlines():
  141.             rm.process_instruction(line.strip().split())
  142.     return len(rm.tail_positions)
  143.  
  144.  
  145. def part_two(filepath):
  146.     rm = RopeMover(10)
  147.     with open(filepath, "r") as f:
  148.         for line in f.readlines():
  149.             rm.process_instruction(line.strip().split())
  150.     return len(rm.tail_positions)
  151.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement