from __future__ import annotations from dataclasses import dataclass from math import sqrt, inf, atan2, degrees from typing import Union, List @dataclass(frozen=True, order=True) class Coordinate: x: int y: int def getDistanceTo(self, target: Coordinate, mode: int = 1, includeDiagonals: bool = False) -> Union[int, float]: """ Get distance to target Coordinate :param target: :param mode: Calculation Mode (0 = Manhattan, 1 = Pythagoras) :param includeDiagonals: in Manhattan Mode specify if diagonal movements are allowed (counts as 1.4) :return: Distance to Target """ assert isinstance(target, Coordinate) assert mode in [0, 1] assert isinstance(includeDiagonals, bool) if mode == 1: return sqrt(abs(self.x - target.x) ** 2 + abs(self.y - target.y) ** 2) elif mode == 0: if not includeDiagonals: return abs(self.x - target.x) + abs(self.y - target.y) else: x_dist = abs(self.x - target.x) y_dist = abs(self.y - target.y) o_dist = max(x_dist, y_dist) - min(x_dist, y_dist) return o_dist + 1.4 * min(x_dist, y_dist) def getNeighbours(self, includeDiagonal: bool = True, minX: int = -inf, minY: int = -inf, maxX: int = inf, maxY: int = inf) -> list[Coordinate]: """ Get a list of neighbouring coordinates :param includeDiagonal: include diagonal neighbours :param minX: ignore all neighbours that would have an X value below this :param minY: ignore all neighbours that would have an Y value below this :param maxX: ignore all neighbours that would have an X value above this :param maxY: ignore all neighbours that would have an Y value above this :return: list of Coordinate """ neighbourList = [] if includeDiagonal: nb_list = [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)] else: nb_list = [(-1, 0), (1, 0), (0, -1), (0, 1)] for dx, dy in nb_list: tx = self.x + dx ty = self.y + dy if tx < minX or tx > maxX or ty < minY or ty > maxY: continue neighbourList.append(Coordinate(tx, ty)) return neighbourList def getAngleTo(self, target: Coordinate, normalized: bool = False) -> float: """normalized returns an angle going clockwise with 0 starting in the 'north'""" dx = target.x - self.x dy = target.y - self.y if not normalized: return degrees(atan2(dy, dx)) else: angle = degrees(atan2(dx, dy)) if dx >= 0: return 180.0 - angle else: return 180.0 + abs(angle) @staticmethod def generate(from_x: int, to_x: int, from_y: int, to_y: int) -> List[Coordinate]: return [Coordinate(x, y) for x in range(from_x, to_x + 1) for y in range(from_y, to_y + 1)]