From cc1fd86ede5d884b55a9455c564c22aec531411a Mon Sep 17 00:00:00 2001 From: Stefan Harmuth Date: Sun, 24 Dec 2023 12:13:58 +0100 Subject: [PATCH] NEW: Coordinate() now accepts floats; what can possibly go wrong? --- src/tools/coordinate.py | 223 ++++++++++++++-------------------------- 1 file changed, 76 insertions(+), 147 deletions(-) diff --git a/src/tools/coordinate.py b/src/tools/coordinate.py index 0d113fd..f552e22 100644 --- a/src/tools/coordinate.py +++ b/src/tools/coordinate.py @@ -14,19 +14,19 @@ class DistanceAlgorithm(Enum): class Coordinate(tuple): - def __new__(cls, x: int, y: int, z: int = None) -> Coordinate: + def __new__(cls, x: int | float, y: int | float, z: int | float | None = None): return tuple.__new__(cls, (x, y, z)) @property - def x(self): + def x(self) -> int | float: return self[0] @property - def y(self): + def y(self) -> int | float: return self[1] @property - def z(self): + def z(self) -> int | float: return self[2] def is3D(self) -> bool: @@ -37,7 +37,7 @@ class Coordinate(tuple): target: Coordinate | tuple, algorithm: DistanceAlgorithm = DistanceAlgorithm.EUCLIDEAN, includeDiagonals: bool = False, - ) -> Union[int, float]: + ) -> int | float: """ Get distance to target Coordinate @@ -49,7 +49,9 @@ class Coordinate(tuple): """ if algorithm == DistanceAlgorithm.EUCLIDEAN: if self[2] is None: - return sqrt(abs(self[0] - target[0]) ** 2 + abs(self[1] - target[1]) ** 2) + return sqrt( + abs(self[0] - target[0]) ** 2 + abs(self[1] - target[1]) ** 2 + ) else: return sqrt( abs(self[0] - target[0]) ** 2 @@ -90,12 +92,12 @@ class Coordinate(tuple): def inBoundaries( self, - minX: int, - minY: int, - maxX: int, - maxY: int, - minZ: int = -inf, - maxZ: int = inf, + minX: int | float, + minY: int | float, + maxX: int | float, + maxY: int | float, + minZ: int | float = -inf, + maxZ: int | float = inf, ) -> bool: if self[2] is None: return minX <= self[0] <= maxX and minY <= self[1] <= maxY @@ -108,14 +110,14 @@ class Coordinate(tuple): def getCircle( self, - radius: int = 1, + radius: int | float = 1, algorithm: DistanceAlgorithm = DistanceAlgorithm.EUCLIDEAN, - minX: int = -inf, - minY: int = -inf, - maxX: int = inf, - maxY: int = inf, - minZ: int = -inf, - maxZ: int = inf, + minX: int | float = -inf, + minY: int | float = -inf, + maxX: int | float = inf, + maxY: int | float = inf, + minZ: int | float = -inf, + maxZ: int | float = inf, ) -> list[Coordinate]: ret = [] if self[2] is None: # mode 2D @@ -152,12 +154,13 @@ class Coordinate(tuple): def getNeighbours( self, includeDiagonal: bool = True, - minX: int = -inf, - minY: int = -inf, - maxX: int = inf, - maxY: int = inf, - minZ: int = -inf, - maxZ: int = inf, + minX: int | float = -inf, + minY: int | float = -inf, + maxX: int | float = inf, + maxY: int | float = inf, + minZ: int | float = -inf, + maxZ: int | float = inf, + dist: int | float = 1, ) -> list[Coordinate]: """ Get a list of neighbouring coordinates. @@ -169,22 +172,23 @@ class Coordinate(tuple): :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 :param maxZ: ignore all neighbours that would have an Z value above this + :param dist: distance to neighbour coordinates :return: list of Coordinate """ if self[2] is None: if includeDiagonal: nb_list = [ - (-1, -1), - (-1, 0), - (-1, 1), - (0, -1), - (0, 1), - (1, -1), - (1, 0), - (1, 1), + (-dist, -dist), + (-dist, 0), + (-dist, dist), + (0, -dist), + (0, dist), + (dist, -dist), + (dist, 0), + (dist, dist), ] else: - nb_list = [(-1, 0), (1, 0), (0, -1), (0, 1)] + nb_list = [(-dist, 0), (dist, 0), (0, -dist), (0, dist)] for dx, dy in nb_list: if minX <= self[0] + dx <= maxX and minY <= self[1] + dy <= maxY: @@ -193,19 +197,19 @@ class Coordinate(tuple): if includeDiagonal: nb_list = [ (x, y, z) - for x in [-1, 0, 1] - for y in [-1, 0, 1] - for z in [-1, 0, 1] + for x in [-dist, 0, dist] + for y in [-dist, 0, dist] + for z in [-dist, 0, dist] ] nb_list.remove((0, 0, 0)) else: nb_list = [ - (-1, 0, 0), - (0, -1, 0), - (1, 0, 0), - (0, 1, 0), - (0, 0, 1), - (0, 0, -1), + (-dist, 0, 0), + (0, -dist, 0), + (dist, 0, 0), + (0, dist, 0), + (0, 0, dist), + (0, 0, -dist), ] for dx, dy, dz in nb_list: @@ -233,6 +237,7 @@ class Coordinate(tuple): return 180.0 + abs(angle) def getLineTo(self, target: Coordinate | tuple) -> List[Coordinate]: + """this will probably not yield what you expect, when using float coordinates""" if target == self: return [self] diff = target - self @@ -276,21 +281,25 @@ class Coordinate(tuple): if self[2] is None: return self.__class__(self[0] + other[0], self[1] + other[1]) else: - return self.__class__(self[0] + other[0], self[1] + other[1], self[2] + other[2]) + return self.__class__( + self[0] + other[0], self[1] + other[1], self[2] + other[2] + ) def __sub__(self, other: Coordinate | tuple) -> Coordinate: if self[2] is None: return self.__class__(self[0] - other[0], self[1] - other[1]) else: - return self.__class__(self[0] - other[0], self[1] - other[1], self[2] - other[2]) + return self.__class__( + self[0] - other[0], self[1] - other[1], self[2] - other[2] + ) - def __mul__(self, other: int) -> Coordinate: + def __mul__(self, other: int | float) -> Coordinate: if self[2] is None: return self.__class__(self[0] * other, self[1] * other) else: return self.__class__(self[0] * other, self[1] * other, self[2] * other) - def __mod__(self, other: int) -> Coordinate: + def __mod__(self, other: int | float) -> Coordinate: if self[2] is None: return self.__class__(self[0] % other, self[1] % other) else: @@ -303,19 +312,22 @@ class Coordinate(tuple): return self.__class__(self[0] // other, self[1] // other, self[2] // other) def __truediv__(self, other: int | float) -> Coordinate: - return self // other + if self[2] is None: + return self.__class__(self[0] / other, self[1] / other) + else: + return self.__class__(self[0] / other, self[1] / other, self[2] / other) def __str__(self): if self[2] is None: - return "(%d,%d)" % (self[0], self[1]) + return "({},{})".format(self[0], self[1]) else: - return "(%d,%d,%d)" % (self[0], self[1], self[2]) + return "({},{},{})".format(self[0], self[1], self[2]) def __repr__(self): if self[2] is None: - return "%s(x=%d, y=%d)" % (self.__class__.__name__, self[0], self[1]) + return "{}(x={}, y={})".format(self.__class__.__name__, self[0], self[1]) else: - return "%s(x=%d, y=%d, z=%d)" % ( + return "{}(x={}, y={}, z={})".format( self.__class__.__name__, self[0], self[1], @@ -325,112 +337,29 @@ class Coordinate(tuple): @classmethod def generate( cls, - from_x: int, - to_x: int, - from_y: int, - to_y: int, - from_z: int = None, - to_z: int = None, + from_x: int | float, + to_x: int | float, + from_y: int | float, + to_y: int | float, + from_z: int | float = None, + to_z: int | float = None, + step: int | float = 1, ) -> List[Coordinate]: if from_z is None or to_z is None: return [ cls(x, y) - for x in range(from_x, to_x + 1) - for y in range(from_y, to_y + 1) + for x in range(from_x, to_x + step, step) + for y in range(from_y, to_y + step, step) ] else: return [ cls(x, y, z) - for x in range(from_x, to_x + 1) - for y in range(from_y, to_y + 1) - for z in range(from_z, to_z + 1) + for x in range(from_x, to_x + step, step) + for y in range(from_y, to_y + step, step) + for z in range(from_z, to_z + step, step) ] -class HexCoordinate(Coordinate): - """ - https://www.redblobgames.com/grids/hexagons/#coordinates-cube - Treat as 3d Coordinate - +y -x +z - y x z - yxz - z x y - -z +x -y - """ - - neighbour_vectors = { - "ne": Coordinate(-1, 0, 1), - "nw": Coordinate(-1, 1, 0), - "e": Coordinate(0, -1, 1), - "w": Coordinate(0, 1, -1), - "sw": Coordinate(1, 0, -1), - "se": Coordinate(1, -1, 0), - } - - def __init__(self, x: int, y: int, z: int): - assert (x + y + z) == 0 - super(HexCoordinate, self).__init__(x, y, z) - - def get_length(self) -> int: - return (abs(self.x) + abs(self.y) + abs(self.z)) // 2 - - def getDistanceTo( - self, - target: Coordinate, - algorithm: DistanceAlgorithm = DistanceAlgorithm.EUCLIDEAN, - includeDiagonals: bool = True, - ) -> Union[int, float]: - # includeDiagonals makes no sense in a hex grid, it's just here for signature reasons - if algorithm == DistanceAlgorithm.MANHATTAN: - return (self - target).get_length() - - def getNeighbours( - self, - includeDiagonal: bool = True, - minX: int = -inf, - minY: int = -inf, - maxX: int = inf, - maxY: int = inf, - minZ: int = -inf, - maxZ: int = inf, - ) -> list[Coordinate]: - # includeDiagonals makes no sense in a hex grid, it's just here for signature reasons - return [ - self + x - for x in self.neighbour_vectors.values() - if minX <= (self + x).x <= maxX - and minY <= (self + x).y <= maxY - and minZ <= (self + x).z <= maxZ - ] - - -HexCoordinateR = HexCoordinate - - -class HexCoordinateF(HexCoordinate): - """ - https://www.redblobgames.com/grids/hexagons/#coordinates-cube - Treat as 3d Coordinate - +y -x - y x - -z z yxz z +z - x y - +x -y - """ - - neighbour_vectors = { - "ne": Coordinate(-1, 0, 1), - "nw": Coordinate(0, 1, -1), - "n": Coordinate(-1, 1, 0), - "s": Coordinate(1, -1, 0), - "sw": Coordinate(1, 0, -1), - "se": Coordinate(0, -1, 1), - } - - def __init__(self, x: int, y: int, z: int): - super(HexCoordinateF, self).__init__(x, y, z) - - class Shape: def __init__(self, top_left: Coordinate, bottom_right: Coordinate): """