99 lines
		
	
	
		
			3.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			99 lines
		
	
	
		
			3.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								from typing import Callable, FrozenSet, List, Set, Tuple, Union
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from .position import Point2
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class PixelMap:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, proto, in_bits: bool = False):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        :param proto:
							 | 
						||
| 
								 | 
							
								        :param in_bits:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        self._proto = proto
							 | 
						||
| 
								 | 
							
								        # Used for copying pixelmaps
							 | 
						||
| 
								 | 
							
								        self._in_bits: bool = in_bits
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        assert self.width * self.height == (8 if in_bits else 1) * len(
							 | 
						||
| 
								 | 
							
								            self._proto.data
							 | 
						||
| 
								 | 
							
								        ), f"{self.width * self.height} {(8 if in_bits else 1)*len(self._proto.data)}"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def width(self) -> int:
							 | 
						||
| 
								 | 
							
								        return self._proto.size.x
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def height(self) -> int:
							 | 
						||
| 
								 | 
							
								        return self._proto.size.y
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def bits_per_pixel(self) -> int:
							 | 
						||
| 
								 | 
							
								        return self._proto.bits_per_pixel
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def bytes_per_pixel(self) -> int:
							 | 
						||
| 
								 | 
							
								        return self._proto.bits_per_pixel // 8
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __getitem__(self, pos: Tuple[int, int]) -> int:
							 | 
						||
| 
								 | 
							
								        """ Example usage: is_pathable = self._game_info.pathing_grid[Point2((20, 20))] != 0 """
							 | 
						||
| 
								 | 
							
								        assert 0 <= pos[0] < self.width, f"x is {pos[0]}, self.width is {self.width}"
							 | 
						||
| 
								 | 
							
								        assert 0 <= pos[1] < self.height, f"y is {pos[1]}, self.height is {self.height}"
							 | 
						||
| 
								 | 
							
								        return int(self.data_numpy[pos[1], pos[0]])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __setitem__(self, pos: Tuple[int, int], value: int):
							 | 
						||
| 
								 | 
							
								        """ Example usage: self._game_info.pathing_grid[Point2((20, 20))] = 255 """
							 | 
						||
| 
								 | 
							
								        assert 0 <= pos[0] < self.width, f"x is {pos[0]}, self.width is {self.width}"
							 | 
						||
| 
								 | 
							
								        assert 0 <= pos[1] < self.height, f"y is {pos[1]}, self.height is {self.height}"
							 | 
						||
| 
								 | 
							
								        assert (
							 | 
						||
| 
								 | 
							
								            0 <= value <= 254 * self._in_bits + 1
							 | 
						||
| 
								 | 
							
								        ), f"value is {value}, it should be between 0 and {254 * self._in_bits + 1}"
							 | 
						||
| 
								 | 
							
								        assert isinstance(value, int), f"value is of type {type(value)}, it should be an integer"
							 | 
						||
| 
								 | 
							
								        self.data_numpy[pos[1], pos[0]] = value
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def is_set(self, p: Tuple[int, int]) -> bool:
							 | 
						||
| 
								 | 
							
								        return self[p] != 0
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def is_empty(self, p: Tuple[int, int]) -> bool:
							 | 
						||
| 
								 | 
							
								        return not self.is_set(p)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def copy(self) -> "PixelMap":
							 | 
						||
| 
								 | 
							
								        return PixelMap(self._proto, in_bits=self._in_bits)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def flood_fill(self, start_point: Point2, pred: Callable[[int], bool]) -> Set[Point2]:
							 | 
						||
| 
								 | 
							
								        nodes: Set[Point2] = set()
							 | 
						||
| 
								 | 
							
								        queue: List[Point2] = [start_point]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        while queue:
							 | 
						||
| 
								 | 
							
								            x, y = queue.pop()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if not (0 <= x < self.width and 0 <= y < self.height):
							 | 
						||
| 
								 | 
							
								                continue
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if Point2((x, y)) in nodes:
							 | 
						||
| 
								 | 
							
								                continue
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if pred(self[x, y]):
							 | 
						||
| 
								 | 
							
								                nodes.add(Point2((x, y)))
							 | 
						||
| 
								 | 
							
								                queue += [Point2((x + a, y + b)) for a in [-1, 0, 1] for b in [-1, 0, 1] if not (a == 0 and b == 0)]
							 | 
						||
| 
								 | 
							
								        return nodes
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def flood_fill_all(self, pred: Callable[[int], bool]) -> Set[FrozenSet[Point2]]:
							 | 
						||
| 
								 | 
							
								        groups: Set[FrozenSet[Point2]] = set()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        for x in range(self.width):
							 | 
						||
| 
								 | 
							
								            for y in range(self.height):
							 | 
						||
| 
								 | 
							
								                if any((x, y) in g for g in groups):
							 | 
						||
| 
								 | 
							
								                    continue
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                if pred(self[x, y]):
							 | 
						||
| 
								 | 
							
								                    groups.add(frozenset(self.flood_fill(Point2((x, y)), pred)))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return groups
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def print(self, wide: bool = False) -> None:
							 | 
						||
| 
								 | 
							
								        for y in range(self.height):
							 | 
						||
| 
								 | 
							
								            for x in range(self.width):
							 | 
						||
| 
								 | 
							
								                print("#" if self.is_set((x, y)) else " ", end=(" " if wide else ""))
							 | 
						||
| 
								 | 
							
								            print("")
							 | 
						||
| 
								 | 
							
								
							 |