from collections import defaultdict from itertools import combinations from tools.aoc import AOCDay from tools.coordinate import Coordinate, Line from typing import Any def get_collisions(hailstones: list[tuple[Coordinate, Coordinate]], area_min: int, area_max: int) -> int: hailstone_lines = [(pos, Line(pos, pos + vel * area_max)) for pos, vel in hailstones] count = 0 for (pos1, line1), (pos2, line2) in combinations(hailstone_lines, 2): try: intersection = line1.get_intersection(line2) except ValueError: continue # do not cross if area_min <= intersection.x <= area_max and area_min <= intersection.y <= area_max: count += 1 return count class Day(AOCDay): inputs = [ [ (2, "input24_test"), (15558, "input24"), ], [ (47, "input24_test"), (None, "input24"), ], ] def parse_input(self, part2: bool = False) -> list[tuple[Coordinate, Coordinate]]: hailstones = [] for line in self.getInput(): p, v = line.split(" @ ") if part2: p = Coordinate(*map(int, p.split(", "))) v = Coordinate(*map(int, v.split(", "))) else: p = Coordinate(*map(int, p.split(", ")[:-1])) v = Coordinate(*map(int, v.split(", ")[:-1])) hailstones.append((p, v)) return hailstones def part1(self) -> Any: if "test" in self._current_test_file: area_min, area_max = 7, 27 else: area_min, area_max = 200_000_000_000_000, 400_000_000_000_000 hailstones = self.parse_input() return get_collisions(hailstones, area_min, area_max) def part2(self) -> Any: # moving to position 24, 13, 10 and throwing the rock at velocity -3, 1, 2. # Maybe we can find some parallel running hailstones and go from there somehow? hailstones = self.parse_input(True) return "" if __name__ == "__main__": day = Day(2023, 24) day.run(verbose=True)