from __future__ import annotations from .coordinate import Coordinate from enum import Enum from typing import Any, List, Union OFF = False ON = True Numeric = Union[int, float] class GridTransformation(Enum): FLIP_X = 1 FLIP_HORIZONTALLY = 1 # alias for FLIP_X; prep for 3d-transformations FLIP_VERTICALLY = 2 FLIP_DIAGONALLY = 3 FLIP_DIAGONALLY_REV = 4 ROTATE_RIGHT = 5 ROTATE_LEFT = 6 ROTATE_TWICE = 7 class Grid: def __init__(self, default=False): self.__default = default self.__grid = {} self.minX = 0 self.minY = 0 self.maxX = 0 self.maxY = 0 self.minZ = 0 self.maxZ = 0 self.mode3D = False def __trackBoundaries(self, pos: Coordinate): self.minX = min(self.minX, pos.x) self.minY = min(self.minY, pos.y) self.maxX = max(self.maxX, pos.x) self.maxY = max(self.maxY, pos.y) if self.mode3D: self.minZ = min(self.minZ, pos.z) self.maxZ = max(self.maxZ, pos.z) def toggle(self, pos: Coordinate): if pos in self.__grid: del self.__grid[pos] else: self.__trackBoundaries(pos) self.__grid[pos] = not self.__default def set(self, pos: Coordinate, value: Any = True) -> Any: if pos.z is not None: self.mode3D = True if (value == self.__default) and pos in self.__grid: del self.__grid[pos] elif value != self.__default: self.__trackBoundaries(pos) self.__grid[pos] = value return value def add(self, pos: Coordinate, value: Numeric = 1) -> Numeric: return self.set(pos, self.get(pos) + value) def sub(self, pos: Coordinate, value: Numeric = 1) -> Numeric: return self.set(pos, self.get(pos) - value) def mul(self, pos: Coordinate, value: Numeric = 1) -> Numeric: return self.set(pos, self.get(pos) * value) def div(self, pos: Coordinate, value: Numeric = 1) -> Numeric: return self.set(pos, self.get(pos) / value) def get(self, pos: Coordinate) -> Any: if pos in self.__grid: return self.__grid[pos] else: return self.__default def getOnCount(self) -> int: return len(self.__grid) def isSet(self, pos: Coordinate) -> bool: return pos in self.__grid def getCorners(self) -> List[Coordinate]: if not self.mode3D: return [ Coordinate(self.minX, self.minY), Coordinate(self.minX, self.maxY), Coordinate(self.maxX, self.minY), Coordinate(self.maxX, self.maxY), ] else: return [ Coordinate(self.minX, self.minY, self.minZ), Coordinate(self.minX, self.minY, self.maxZ), Coordinate(self.minX, self.maxY, self.minZ), Coordinate(self.minX, self.maxY, self.maxZ), Coordinate(self.maxX, self.minY, self.minZ), Coordinate(self.maxX, self.minY, self.maxZ), Coordinate(self.maxX, self.maxY, self.minZ), Coordinate(self.maxX, self.maxY, self.maxZ), ] def isCorner(self, pos: Coordinate) -> bool: return pos in self.getCorners() def isWithinBoundaries(self, pos: Coordinate) -> bool: if self.mode3D: return self.minX <= pos.x <= self.maxX and self.minY <= pos.y <= self.maxY \ and self.minZ <= pos.z <= self.maxZ else: return self.minX <= pos.x <= self.maxX and self.minY <= pos.y <= self.maxY def getActiveCells(self) -> List[Coordinate]: return list(self.__grid.keys()) def getSum(self, includeNegative: bool = True) -> Numeric: grid_sum = 0 for value in self.__grid.values(): if includeNegative or value > 0: grid_sum += value return grid_sum def getNeighboursOf(self, pos: Coordinate, includeDefault: bool = False, includeDiagonal: bool = True) \ -> List[Coordinate]: neighbours = pos.getNeighbours( includeDiagonal=includeDiagonal, minX=self.minX, minY=self.minY, minZ=self.minZ, maxX=self.maxX, maxY=self.maxY, maxZ=self.maxZ ) if includeDefault: return neighbours else: return [x for x in neighbours if self.get(x) != self.__default] def getNeighbourSum(self, pos: Coordinate, includeNegative: bool = True, includeDiagonal: bool = True) -> Numeric: neighbour_sum = 0 for neighbour in pos.getNeighbours( includeDiagonal=includeDiagonal, minX=self.minX, minY=self.minY, minZ=self.minZ, maxX=self.maxX, maxY=self.maxY, maxZ=self.maxZ ): if neighbour in self.__grid: if includeNegative or self.__grid[neighbour] > 0: neighbour_sum += self.__grid[neighbour] return neighbour_sum def flip(self, c1: Coordinate, c2: Coordinate): buf = self.get(c1) self.set(c1, self.get(c2)) self.set(c2, buf) def transform(self, mode: GridTransformation): if self.mode3D: raise NotImplementedError() # that will take some time and thought if mode == GridTransformation.FLIP_HORIZONTALLY: for x in range(self.minX, (self.maxX - self.minX) // 2 + 1): for y in range(self.minY, self.maxY + 1): self.flip(Coordinate(x, y), Coordinate(self.maxX - x, y)) elif mode == GridTransformation.FLIP_VERTICALLY: for y in range(self.minY, (self.maxY - self.minY) // 2 + 1): for x in range(self.minX, self.maxX + 1): self.flip(Coordinate(x, y), Coordinate(x, self.maxY - y)) elif mode == GridTransformation.FLIP_DIAGONALLY: self.transform(GridTransformation.ROTATE_LEFT) self.transform(GridTransformation.FLIP_HORIZONTALLY) elif mode == GridTransformation.FLIP_DIAGONALLY_REV: self.transform(GridTransformation.ROTATE_RIGHT) self.transform(GridTransformation.FLIP_HORIZONTALLY) elif mode == GridTransformation.ROTATE_LEFT: newGrid = Grid() for x in range(self.maxX, self.minX - 1, -1): for y in range(self.minY, self.maxY + 1): newGrid.set(Coordinate(y, self.maxX - x), self.get(Coordinate(x, y))) self.__dict__.update(newGrid.__dict__) elif mode == GridTransformation.ROTATE_RIGHT: newGrid = Grid() for x in range(self.minX, self.maxX + 1): for y in range(self.maxY, self.minY - 1, -1): newGrid.set(Coordinate(self.maxY - y, x), self.get(Coordinate(x, y))) self.__dict__.update(newGrid.__dict__) elif mode == GridTransformation.ROTATE_TWICE: self.transform(GridTransformation.ROTATE_RIGHT) self.transform(GridTransformation.ROTATE_RIGHT) def print(self, spacer: str = ""): for y in range(self.minY, self.maxY + 1): for x in range(self.minX, self.maxX + 1): print(self.get(Coordinate(x, y)), end="") print(spacer, end="") print()