Exemple #1
0
    def sample_mgrid(self, mgrid: np.ndarray) -> np.ndarray:
        """Sample a mesh-grid array and return the result.

        The :any:`sample_ogrid` method performs better as there is a lot of
        overhead when working with large mesh-grids.

        Args:
            mgrid (numpy.ndarray): A mesh-grid array of points to sample.
                A contiguous array of type `numpy.float32` is preferred.

        Returns:
            numpy.ndarray: An array of sampled points.

                This array has the shape: ``mgrid.shape[:-1]``.
                The ``dtype`` is `numpy.float32`.
        """
        mgrid = np.ascontiguousarray(mgrid, np.float32)
        if mgrid.shape[0] != self.dimensions:
            raise ValueError(
                "mgrid.shape[0] must equal self.dimensions, "
                "%r[0] != %r" % (mgrid.shape, self.dimensions)
            )
        out = np.ndarray(mgrid.shape[1:], np.float32)
        if mgrid.shape[1:] != out.shape:
            raise ValueError(
                "mgrid.shape[1:] must equal out.shape, "
                "%r[1:] != %r" % (mgrid.shape, out.shape)
            )
        lib.NoiseSampleMeshGrid(
            self._tdl_noise_c,
            out.size,
            ffi.cast("float*", mgrid.ctypes.data),
            ffi.cast("float*", out.ctypes.data),
        )
        return out
Exemple #2
0
    def sample_ogrid(self, ogrid: np.ndarray) -> np.ndarray:
        """Sample an open mesh-grid array and return the result.

        Args
            ogrid (Sequence[Sequence[float]]): An open mesh-grid.

        Returns:
            numpy.ndarray: An array of sampled points.

                The ``shape`` is based on the lengths of the open mesh-grid
                arrays.
                The ``dtype`` is `numpy.float32`.
        """
        if len(ogrid) != self.dimensions:
            raise ValueError(
                "len(ogrid) must equal self.dimensions, "
                "%r != %r" % (len(ogrid), self.dimensions)
            )
        ogrids = [np.ascontiguousarray(array, np.float32) for array in ogrid]
        out = np.ndarray([array.size for array in ogrids], np.float32)
        lib.NoiseSampleOpenMeshGrid(
            self._tdl_noise_c,
            len(ogrids),
            out.shape,
            [ffi.cast("float*", array.ctypes.data) for array in ogrids],
            ffi.cast("float*", out.ctypes.data),
        )
        return out
Exemple #3
0
    def __array_interface__(self) -> Dict[str, Any]:
        """Return an interface for this images pixel buffer.

        Use :any:`numpy.asarray` to get the read-write array of this Image.

        This will often return an RGB array, but could also return an RGBA
        array or fail silently.  Future versions might change what type of
        array is returned.

        You can use ``dtype=numpy.uint8`` to ensure that errors are not ignored
        by NumPy.

        .. versionadded:: 11.4
        """
        strides = None
        if self.image_c.mipmaps:  # Libtcod RGB array.
            depth = 3
            data = int(ffi.cast("size_t", self.image_c.mipmaps[0].buf))
        else:
            raise TypeError("Image has no initialized data.")
        return {
            "shape": (self.height, self.width, depth),
            "typestr": "|u1",
            "data": (data, False),
            "strides": strides,
            "version": 3,
        }
Exemple #4
0
    def __init__(
        self,
        algorithm: int = MERSENNE_TWISTER,
        seed: Optional[Hashable] = None,
    ):
        """Create a new instance using this algorithm and seed."""
        if seed is None:
            seed = random.getrandbits(32)
        elif not isinstance(seed, int):
            warnings.warn(
                "In the future this class will only accept integer seeds.",
                DeprecationWarning,
                stacklevel=2,
            )
            if __debug__ and "PYTHONHASHSEED" not in os.environ:
                warnings.warn(
                    "Python's hash algorithm is not configured to be"
                    " deterministic so this non-integer seed will not be"
                    " deterministic."
                    "\nYou should do one of the following to fix this error:"
                    "\n* Use an integer as a seed instead (recommended.)"
                    "\n* Set the PYTHONHASHSEED environment variable before"
                    " starting Python.",
                    RuntimeWarning,
                    stacklevel=2,
                )
            seed = hash(seed)

        self.random_c = ffi.gc(
            ffi.cast(
                "mersenne_data_t*",
                lib.TCOD_random_new_from_seed(algorithm, seed & 0xFFFFFFFF),
            ),
            lib.TCOD_random_delete,
        )
Exemple #5
0
    def path_from(self, index: Tuple[int, ...]) -> np.ndarray:
        """Return the shortest path from `index` to the nearest root.

        The return value is inclusive, including both the starting and ending
        points on the path.  If the root point is unreachable or `index` is
        already at a root then `index` will be the only point returned.

        This automatically calls :any:`resolve` if the pathfinder has not
        yet reached `index`.

        A common usage is to slice off the starting point and convert the array
        into a list.
        """
        self.resolve(index)
        assert len(index) == self._graph._ndim
        length = _check(
            lib.get_travel_path(
                self._graph._ndim,
                self._travel_p,
                index,
                ffi.NULL,
            ))
        path = np.ndarray((length, self._graph._ndim), dtype=np.intc)
        _check(
            lib.get_travel_path(
                self._graph._ndim,
                self._travel_p,
                index,
                ffi.cast("int*", path.ctypes.data),
            ))
        return path
Exemple #6
0
    def set_tile(self, codepoint: int, tile: np.ndarray) -> None:
        """Upload a tile into this array.

        The tile can be in 32-bit color (height, width, rgba), or grey-scale
        (height, width).  The tile should have a dtype of ``np.uint8``.

        This data may need to be sent to graphics card memory, this is a slow
        operation.
        """
        tile = np.ascontiguousarray(tile, dtype=np.uint8)
        if tile.shape == self.tile_shape:
            full_tile = np.empty(self.tile_shape + (4,), dtype=np.uint8)
            full_tile[:, :, :3] = 255
            full_tile[:, :, 3] = tile
            return self.set_tile(codepoint, full_tile)
        required = self.tile_shape + (4,)
        if tile.shape != required:
            raise ValueError(
                "Tile shape must be %r or %r, got %r."
                % (required, self.tile_shape, tile.shape)
            )
        lib.TCOD_tileset_set_tile_(
            self._tileset_p,
            codepoint,
            ffi.cast("struct TCOD_ColorRGBA*", tile.ctypes.data),
        )
Exemple #7
0
 def __init__(
     self,
     dimensions: int,
     algorithm: int = 2,
     implementation: int = SIMPLE,
     hurst: float = 0.5,
     lacunarity: float = 2.0,
     octaves: float = 4,
     seed: Optional[tcod.random.Random] = None,
 ):
     if not 0 < dimensions <= 4:
         raise ValueError(
             "dimensions must be in range 0 < n <= 4, got %r"
             % (dimensions,)
         )
     self._random = seed
     _random_c = seed.random_c if seed else ffi.NULL
     self._algorithm = algorithm
     self.noise_c = ffi.gc(
         ffi.cast(
             "struct TCOD_Noise*",
             lib.TCOD_noise_new(dimensions, hurst, lacunarity, _random_c),
         ),
         lib.TCOD_noise_delete,
     )
     self._tdl_noise_c = ffi.new(
         "TDLNoise*", (self.noise_c, dimensions, 0, octaves)
     )
     self.implementation = implementation  # sanity check
Exemple #8
0
def hillclimb2d(distance: np.array,
                start: Tuple[int, int],
                cardinal: Optional[bool] = None,
                diagonal: Optional[bool] = None,
                *,
                edge_map: Any = None) -> np.array:
    """Return a path on a grid from `start` to the lowest point.

    `distance` should be a fully computed distance array.  This kind of array
    is returned by :any:`dijkstra2d`.

    `start` is a 2-item tuple with starting coordinates.  The axes if these
    coordinates should match the axis of the `distance` array.
    An out-of-bounds `start` index will raise an IndexError.

    At each step nodes adjacent toe current will be checked for a value lower
    than the current one.  Which directions are checked is decided by the
    boolean values `cardinal` and `diagonal`.  This process is repeated until
    all adjacent nodes are equal to or larger than the last point on the path.

    If `edge_map` was used with :any:`tcod.path.dijkstra2d` then it should be
    reused for this function.  Keep in mind that `edge_map` must be
    bidirectional since hill-climbing will traverse the map backwards.

    The returned array is a 2D NumPy array with the shape: (length, axis).
    This array always includes both the starting and ending point and will
    always have at least one item.

    Typical uses of the returned array will be to either convert it into a list
    which can be popped from, or transpose it and convert it into a tuple which
    can be used to index other arrays using NumPy's advanced indexing rules.

    .. versionadded:: 11.2

    .. versionchanged:: 11.13
        Added `edge_map` parameter.
    """
    x, y = start
    dist = np.asarray(distance)
    if not (0 <= x < dist.shape[0] and 0 <= y < dist.shape[1]):
        raise IndexError("Starting point %r not in shape %r" %
                         (start, dist.shape))
    c_dist = _export(dist)
    if edge_map is not None:
        if cardinal is not None or diagonal is not None:
            raise TypeError("`edge_map` can not be set at the same time as"
                            " `cardinal` or `diagonal`.")
        c_edges, n_edges = _compile_bool_edges(edge_map)
        func = functools.partial(lib.hillclimb2d, c_dist, x, y, n_edges,
                                 c_edges)
    else:
        func = functools.partial(lib.hillclimb2d_basic, c_dist, x, y, cardinal,
                                 diagonal)
    length = _check(func(ffi.NULL))
    path = np.ndarray((length, 2), dtype=np.intc)
    c_path = ffi.cast("int*", path.ctypes.data)
    _check(func(c_path))
    return path
Exemple #9
0
def _export_dict(array: np.array) -> Dict[str, Any]:
    """Convert a NumPy array into a format compatible with CFFI."""
    return {
        "type": _INT_TYPES[array.dtype.type],
        "ndim": array.ndim,
        "data": ffi.cast("void*", array.ctypes.data),
        "shape": array.shape,
        "strides": array.strides,
    }
Exemple #10
0
 def __as_cdata(self) -> Any:
     return ffi.new(
         "struct TCOD_Map*",
         (
             self.width,
             self.height,
             self.width * self.height,
             ffi.cast("struct TCOD_MapCell*", self.__buffer.ctypes.data),
         ),
     )
Exemple #11
0
def _export(array: np.array) -> Any:
    """Convert a NumPy array into a ctype object."""
    return ffi.new(
        "struct NArray4*",
        (
            _INT_TYPES[array.dtype.type],
            ffi.cast("void*", array.ctypes.data),
            array.shape,
            array.strides,
        ),
    )
Exemple #12
0
    def __array_interface__(self) -> Dict[str, Any]:
        """Return an interface for this images pixel buffer.

        Use :any:`numpy.asarray` to get the read-write array of this Image.

        This will often return an RGB array, but could also return an RGBA
        array or fail silently.  Future versions might change what type of
        array is returned.

        You can use ``dtype=numpy.uint8`` to ensure that errors are not ignored
        by NumPy.

        .. versionadded:: 11.4
        """
        strides = None
        if self.image_c.mipmaps:  # Libtcod RGB array.
            depth = 3
            data = int(ffi.cast("size_t", self.image_c.mipmaps[0].buf))
        elif self.image_c.sys_img:  # SDL Surface.
            data = int(ffi.cast("size_t", self.image_c.sys_img.pixels))
            format = self.image_c.sys_img.format.format
            if format == lib.SDL_PIXELFORMAT_RGB24:
                depth = 3
            elif format == lib.SDL_PIXELFORMAT_RGBA32:
                depth = 4
            else:
                raise TypeError("Can't interface with format: %s" %
                                (_get_format_name(format), ))
            strides = (self.image_c.sys_img.pitch, depth, 1)
        else:
            raise TypeError("Image has no initialized data.")
        return {
            "shape": (self.height, self.width, depth),
            "typestr": "|u1",
            "data": (data, False),
            "strides": strides,
            "version": 3,
        }
Exemple #13
0
    def get_tcod_path_ffi(self) -> Tuple[Any, Any, Tuple[int, int]]:
        if len(self.shape) != 2:
            raise ValueError("Array must have a 2d shape, shape is %r" %
                             (self.shape, ))
        if self.dtype.type not in self._C_ARRAY_CALLBACKS:
            raise ValueError("dtype must be one of %r, dtype is %r" %
                             (self._C_ARRAY_CALLBACKS.keys(), self.dtype.type))

        array_type, callback = self._C_ARRAY_CALLBACKS[self.dtype.type]
        userdata = ffi.new(
            "struct PathCostArray*",
            (ffi.cast("char*", self.ctypes.data), self.strides),
        )
        return callback, userdata, self.shape
Exemple #14
0
 def __init__(
     self,
     algorithm: int = MERSENNE_TWISTER,
     seed: Optional[Hashable] = None,
 ):
     """Create a new instance using this algorithm and seed."""
     if seed is None:
         seed = random.getrandbits(32)
     self.random_c = ffi.gc(
         ffi.cast(
             "mersenne_data_t*",
             lib.TCOD_random_new_from_seed(algorithm,
                                           hash(seed) % (1 << 32)),
         ),
         lib.TCOD_random_delete,
     )
Exemple #15
0
    def get_tile(self, codepoint: int) -> np.ndarray:
        """Return a copy of a tile for the given codepoint.

        If the tile does not exist yet then a blank array will be returned.

        The tile will have a shape of (height, width, rgba) and a dtype of
        uint8.  Note that most grey-scale tiles will only use the alpha
        channel and will usually have a solid white color channel.
        """
        tile = np.zeros(self.tile_shape + (4,), dtype=np.uint8)
        lib.TCOD_tileset_get_tile_(
            self._tileset_p,
            codepoint,
            ffi.cast("struct TCOD_ColorRGBA*", tile.ctypes.data),
        )
        return tile
Exemple #16
0
    def _init_setup_console_data(self, order: str = "C") -> None:
        """Setup numpy arrays over libtcod data buffers."""
        global _root_console
        self._key_color = None
        if self.console_c == ffi.NULL:
            _root_console = self
            self._console_data = lib.TCOD_ctx.root
        else:
            self._console_data = ffi.cast("struct TCOD_Console*",
                                          self.console_c)

        self._tiles = np.frombuffer(
            ffi.buffer(self._console_data.tiles[0:self.width * self.height]),
            dtype=self.DTYPE,
        ).reshape((self.height, self.width))

        self._order = tcod._internal.verify_order(order)
Exemple #17
0
    def __init__(
        self,
        width: int,
        height: int,
        order: str = "C",
        buffer: Optional[np.ndarray] = None,
    ):
        self._key_color = None  # type: Optional[Tuple[int, int, int]]
        self._order = tcod._internal.verify_order(order)
        if buffer is not None:
            if self._order == "F":
                buffer = buffer.transpose()
            self._tiles = np.ascontiguousarray(buffer, self.DTYPE)
        else:
            self._tiles = np.ndarray((height, width), dtype=self.DTYPE)

        # libtcod uses the root console for defaults.
        default_bg_blend = 0
        default_alignment = 0
        if lib.TCOD_ctx.root != ffi.NULL:
            default_bg_blend = lib.TCOD_ctx.root.bkgnd_flag
            default_alignment = lib.TCOD_ctx.root.alignment

        self._console_data = self.console_c = ffi.new(
            "struct TCOD_Console*",
            {
                "w":
                width,
                "h":
                height,
                "elements":
                width * height,
                "tiles":
                ffi.cast("struct TCOD_ConsoleTile*", self._tiles.ctypes.data),
                "bkgnd_flag":
                default_bg_blend,
                "alignment":
                default_alignment,
                "fore": (255, 255, 255),
                "back": (0, 0, 0),
            },
        )

        if buffer is None:
            self.clear()
Exemple #18
0
    def __setstate__(self, state: Any) -> None:
        self._key_color = None
        if "_tiles" not in state:
            tiles = np.ndarray((self.height, self.width), dtype=self.DTYPE)
            tiles["ch"] = state["_ch"]
            tiles["fg"][..., :3] = state["_fg"]
            tiles["fg"][..., 3] = 255
            tiles["bg"][..., :3] = state["_bg"]
            tiles["bg"][..., 3] = 255
            state["_tiles"] = tiles
            del state["_ch"]
            del state["_fg"]
            del state["_bg"]

        self.__dict__.update(state)
        self._console_data["tiles"] = ffi.cast("struct TCOD_ConsoleTile*",
                                               self._tiles.ctypes.data)
        self._console_data = self.console_c = ffi.new("struct TCOD_Console*",
                                                      self._console_data)
Exemple #19
0
    def render(self, console: tcod.console.Console) -> np.ndarray:
        """Render an RGBA array, using console with this tileset.

        `console` is the Console object to render, this can not be the root
        console.

        The output array will be a np.uint8 array with the shape of:
        ``(con_height * tile_height, con_width * tile_width, 4)``.

        .. versionadded:: 11.9
        """
        if not console:
            raise ValueError("'console' must not be the root console.")
        width = console.width * self.tile_width
        height = console.height * self.tile_height
        out = np.empty((height, width, 4), np.uint8)
        out[:] = 9
        surface_p = ffi.gc(
            lib.SDL_CreateRGBSurfaceWithFormatFrom(
                ffi.cast("void*", out.ctypes.data),
                width,
                height,
                32,
                out.strides[0],
                lib.SDL_PIXELFORMAT_RGBA32,
            ),
            lib.SDL_FreeSurface,
        )
        with surface_p:
            with ffi.new("SDL_Surface**", surface_p) as surface_p_p:
                _check(
                    lib.TCOD_tileset_render_to_surface(
                        self._tileset_p,
                        _console(console),
                        ffi.NULL,
                        surface_p_p,
                    )
                )
        return out
Exemple #20
0
def hillclimb2d(distance: np.array, start: Tuple[int, int], cardinal: bool,
                diagonal: bool) -> np.array:
    """Return a path on a grid from `start` to the lowest point.

    `distance` should be a fully computed distance array.  This kind of array
    is returned by :any:`dijkstra2d`.

    `start` is a 2-item tuple with starting coordinates.  The axes if these
    coordinates should match the axis of the `distance` array.
    An out-of-bounds `start` index will raise an IndexError.

    At each step nodes adjacent toe current will be checked for a value lower
    than the current one.  Which directions are checked is decided by the
    boolean values `cardinal` and `diagonal`.  This process is repeated until
    all adjacent nodes are equal to or larger than the last point on the path.

    The returned array is a 2D NumPy array with the shape: (length, axis).
    This array always includes both the starting and ending point and will
    always have at least one item.

    Typical uses of the returned array will be to either convert it into a list
    which can be popped from, or transpose it and convert it into a tuple which
    can be used to index other arrays using NumPy's advanced indexing rules.

    .. versionadded:: 11.2
    """
    x, y = start
    dist = np.asarray(distance)
    if not (0 <= x < dist.shape[0] and 0 <= y < dist.shape[1]):
        raise IndexError("Starting point %r not in shape %r" %
                         (start, dist.shape))
    c_dist = _export(dist)
    length = _check(lib.hillclimb2d(c_dist, x, y, cardinal, diagonal,
                                    ffi.NULL))
    path = np.ndarray((length, 2), dtype=np.intc)
    c_path = ffi.cast("int*", path.ctypes.data)
    _check(lib.hillclimb2d(c_dist, x, y, cardinal, diagonal, c_path))
    return path
Exemple #21
0
 def __init__(self, pixels: np.array) -> None:
     self._array = np.ascontiguousarray(pixels, dtype=np.uint8)
     if len(self._array) != 3:
         raise TypeError("NumPy shape must be 3D [y, x, ch] (got %r)" %
                         (self._array.shape, ))
     if 3 <= self._array.shape[2] <= 4:
         raise TypeError(
             "NumPy array must have RGB or RGBA channels. (got %r)" %
             (self._array.shape, ))
     self.p = ffi.gc(
         lib.SDL_CreateRGBSurfaceFrom(
             ffi.cast("void*", self._array.ctypes.data),
             self._array.shape[1],  # Width.
             self._array.shape[0],  # Height.
             self._array.shape[2] * 8,  # Bit depth.
             self._array.stride[1],  # Pitch.
             0x000000FF,
             0x0000FF00,
             0x00FF0000,
             0xFF000000 if self._array.shape[2] == 4 else 0,
         ),
         lib.SDL_FreeSurface,
     )
Exemple #22
0
def _get_pathcost_func(
    name: str, ) -> Callable[[int, int, int, int, Any], float]:
    """Return a properly cast PathCostArray callback."""
    return ffi.cast(  # type: ignore
        "TCOD_path_func_t", ffi.addressof(lib, name))