|
2 | 2 | # Author: Joth (https://github.com/joth00) |
3 | 3 |
|
4 | 4 | from os import path |
| 5 | +import math |
| 6 | + |
| 7 | + |
| 8 | +class Location: |
| 9 | + def __init__(self, x, y): |
| 10 | + self.x = x |
| 11 | + self.y = y |
| 12 | + |
| 13 | + def __repr__(self): |
| 14 | + return f'({self.x}, {self.y})' |
| 15 | + |
| 16 | + |
| 17 | +class Asteroid: |
| 18 | + def __init__(self, location): |
| 19 | + self.location = location |
| 20 | + self.detectable_asteroids = [] |
| 21 | + |
| 22 | + def is_between(self, asteroid1, asteroid2): |
| 23 | + x, y = self.location.x, self.location.y |
| 24 | + x1, y1 = asteroid1.location.x, asteroid1.location.y |
| 25 | + x2, y2 = asteroid2.location.x, asteroid2.location.y |
| 26 | + |
| 27 | + min_x, max_x = min(x1, x2), max(x1, x2) |
| 28 | + min_y, max_y = min(y1, y2), max(y1, y2) |
| 29 | + |
| 30 | + if min_x <= x <= max_x and min_y <= y <= max_y: |
| 31 | + return y - y1 == ((y2 - y1)/(x2 - x1))*(x - x1) if x1 != x2 else x1 == x |
| 32 | + |
| 33 | + return False |
| 34 | + |
| 35 | + def get_positive_angle_from(self, location): |
| 36 | + angle = math.atan2(self.location.y - location.y, self.location.x - location.x) |
| 37 | + return (angle + math.pi/2 + 2*math.pi) % (2*math.pi) |
| 38 | + |
| 39 | + def __repr__(self): |
| 40 | + return repr(self.location) |
| 41 | + |
| 42 | + |
| 43 | +class MonitoringStation(Asteroid): |
| 44 | + def __init__(self, location): |
| 45 | + super().__init__(location) |
| 46 | + |
| 47 | + @staticmethod |
| 48 | + def from_asteroid(asteroid): |
| 49 | + monitoring_station = MonitoringStation(asteroid.location) |
| 50 | + monitoring_station.detectable_asteroids = asteroid.detectable_asteroids |
| 51 | + return monitoring_station |
| 52 | + |
| 53 | + def get_nth_vaporized_asteroid_in_map(self, n, space_map): |
| 54 | + last_vaporized_asteroids = self.detectable_asteroids[:] |
| 55 | + vaporized_asteroids_count = 0 |
| 56 | + while vaporized_asteroids_count < n: |
| 57 | + space_map.determinate_detectable_asteroids_for(self) |
| 58 | + last_vaporized_asteroids = self.detectable_asteroids[:] |
| 59 | + space_map.vaporize_asteroids(last_vaporized_asteroids) |
| 60 | + vaporized_asteroids_count += len(last_vaporized_asteroids) |
| 61 | + |
| 62 | + last_vaporized_asteroids.sort(key=lambda x: x.get_positive_angle_from(self.location), reverse=True) |
| 63 | + return last_vaporized_asteroids[vaporized_asteroids_count - n - 1] |
| 64 | + |
| 65 | + |
| 66 | +class SpaceMap: |
| 67 | + |
| 68 | + class Symbols: |
| 69 | + EMPTY = '.' |
| 70 | + ASTEROID = '#' |
| 71 | + |
| 72 | + def __init__(self, raw_map): |
| 73 | + self._map = list(list(line) for line in raw_map.splitlines()) |
| 74 | + self._HEIGHT = len(self._map) |
| 75 | + self._WIDTH = len(self._map[0]) |
| 76 | + self._asteroids = [] |
| 77 | + self._detect_asteroids() |
| 78 | + self._evaluate_every_asteroid() |
| 79 | + |
| 80 | + def _detect_asteroids(self): |
| 81 | + for y in range(self._HEIGHT): |
| 82 | + for x in range(self._WIDTH): |
| 83 | + location = Location(x, y) |
| 84 | + if self._get(location) == SpaceMap.Symbols.ASTEROID: |
| 85 | + asteroid = Asteroid(location) |
| 86 | + self._set(location, asteroid) |
| 87 | + self._asteroids.append(asteroid) |
| 88 | + else: |
| 89 | + self._set(location, None) |
| 90 | + |
| 91 | + def _evaluate_every_asteroid(self): |
| 92 | + for asteroid in self._asteroids: |
| 93 | + self.determinate_detectable_asteroids_for(asteroid) |
| 94 | + |
| 95 | + def get_best_evaluated_asteroid(self): |
| 96 | + return max(self._asteroids, key=lambda x: len(x.detectable_asteroids)) |
| 97 | + |
| 98 | + def determinate_detectable_asteroids_for(self, asteroid): |
| 99 | + asteroid.detectable_asteroid_count = 0 |
| 100 | + asteroid.detectable_asteroids = [] |
| 101 | + for possible_detectable_asteroid in [x for x in self._asteroids if x != asteroid]: |
| 102 | + for possible_blocking_asteroid in [x for x in self._asteroids if x != possible_detectable_asteroid and x != asteroid]: |
| 103 | + if possible_blocking_asteroid.is_between(asteroid, possible_detectable_asteroid): |
| 104 | + break |
| 105 | + else: |
| 106 | + asteroid.detectable_asteroids.append(possible_detectable_asteroid) |
| 107 | + |
| 108 | + def vaporize_asteroids(self, asteroids): |
| 109 | + for asteroid in asteroids: |
| 110 | + self._set(asteroid.location, None) |
| 111 | + self._asteroids.remove(asteroid) |
| 112 | + |
| 113 | + def _is_asteroid(self, location): |
| 114 | + return isinstance(self._get(location), Asteroid) |
| 115 | + |
| 116 | + def _is_empty(self, location): |
| 117 | + return self._get(location) is None |
| 118 | + |
| 119 | + def _get(self, location): |
| 120 | + return self._map[location.y][location.x] |
| 121 | + |
| 122 | + def _set(self, location, value): |
| 123 | + self._map[location.y][location.x] = value |
5 | 124 |
|
6 | 125 |
|
7 | 126 | def main(): |
8 | 127 | text_input = get_raw_input() |
9 | | - pass |
| 128 | + space_map = SpaceMap(text_input) |
| 129 | + monitoring_station = MonitoringStation.from_asteroid(space_map.get_best_evaluated_asteroid()) |
| 130 | + |
| 131 | + location_of_200th_vaporized = monitoring_station.get_nth_vaporized_asteroid_in_map(200, space_map).location |
| 132 | + |
| 133 | + print('Location of the 200th vaporized asteroid:', location_of_200th_vaporized) |
| 134 | + print('Result:', location_of_200th_vaporized.x*100 + location_of_200th_vaporized.y) |
10 | 135 |
|
11 | 136 |
|
12 | 137 | def get_raw_input(): |
|
0 commit comments