Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions puzzles/solutions/2022/d09/consts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
DIRECTION_TO_STEP = {"R": (1, 0), "U": (0, 1), "L": (-1, 0), "D": (0, -1)}
ADJACENT_DISTANCE = 1
FAR_DISTANCE = 2
57 changes: 57 additions & 0 deletions puzzles/solutions/2022/d09/knot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import dataclasses
import math

from typing import Self

import consts


@dataclasses.dataclass
class Knot:
"""A knot on a rope which can move."""

row: int
column: int

def move(self, step: tuple[int, int]) -> None:
"""
Move the knot by the given step.
:param step: pair of (row, column) movement
"""
x, y = step
self.row += x
self.column += y

def is_touching(self, other: Self) -> bool:
"""
:param other: other knot
:return: whether this knot and the other knot are touching
"""
return (
abs(self.row - other.row) < consts.FAR_DISTANCE
and abs(self.column - other.column) < consts.FAR_DISTANCE
)

def move_to_other(self, other: Self) -> None:
"""
Move this knot, so it will be touching the other knot.
:param other: other knot to move towards.
"""
if self.is_touching(other):
return

row_distance = self.row - other.row
column_distance = self.column - other.column

# If the distance is "far", we want to move the knot, so it will be on the same row/column, but not overlapping,
# so the distance to move should be 1.
# If the distance is "adjacent", it means we want to move diagonally (remember -- the knots are not touching,
# otherwise, we would return immediately at the beginning of this method), so the distance to move
# stays 1, too.
# If the distance is 0, no movement should occur, the condition is falsy, so the distance to move stays 0.
if abs(row_distance) == consts.FAR_DISTANCE:
row_distance = math.copysign(consts.ADJACENT_DISTANCE, row_distance)
if abs(column_distance) == consts.FAR_DISTANCE:
column_distance = math.copysign(consts.ADJACENT_DISTANCE, column_distance)

self.move((-int(row_distance), -int(column_distance)))
53 changes: 53 additions & 0 deletions puzzles/solutions/2022/d09/p1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import sys
from typing import Iterator

from knot import Knot
import consts


def get_steps(input_text: str) -> Iterator[tuple[int, int]]:
"""
:param input_text: puzzle input
:return: sequence of steps, one by one
"""
steps = []
for step_count in input_text.splitlines():
direction, count = step_count.split()
step = consts.DIRECTION_TO_STEP[direction]
count = int(count)
yield from count * [step]
return tuple(steps)


def get_visited_positions_amount(
steps: Iterator[tuple[int, int]], knots_amount: int
) -> int:
"""
:param steps: sequence of steps, one by one
:param knots_amount: amount of knots in the rope
:return: number of positions the tail of the rope visits at least once
"""
knots = [Knot(0, 0) for _ in range(knots_amount)]
head = knots[0]
tail = knots[-1]
visited_positions = set()
for step in steps:
head.move(step)
for index in range(1, knots_amount):
# Move each knot according to the knot it follows.
knots[index].move_to_other(knots[index - 1])
visited_positions.add((tail.row, tail.column))
return len(visited_positions)


def get_answer(input_text: str):
"""Return the number of positions the tail of the rope visits at least once, when the rope has total 2 knots."""
steps = get_steps(input_text)
return get_visited_positions_amount(steps, 2)


if __name__ == "__main__":
try:
print(get_answer(sys.argv[1]))
except IndexError:
pass # Don't crash if no input was passed through command line arguments.
16 changes: 16 additions & 0 deletions puzzles/solutions/2022/d09/p2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import sys

import p1


def get_answer(input_text: str):
"""Return the number of positions the tail of the rope visits at least once, when the rope has total 10 knots."""
steps = p1.get_steps(input_text)
return p1.get_visited_positions_amount(steps, 10)


if __name__ == "__main__":
try:
print(get_answer(sys.argv[1]))
except IndexError:
pass # Don't crash if no input was passed through command line arguments.