"""libtcod map attributes and field-of-view functions."""
from __future__ import annotations
import warnings
from typing import Any
import numpy as np
from numpy.typing import ArrayLike, NDArray
from typing_extensions import Literal
import tcod._internal
import tcod.constants
from tcod.cffi import ffi, lib
[docs]
class Map:
"""A map containing libtcod attributes.
.. versionchanged:: 4.1
`transparent`, `walkable`, and `fov` are now numpy boolean arrays.
.. versionchanged:: 4.3
Added `order` parameter.
Args:
width (int): Width of the new Map.
height (int): Height of the new Map.
order (str): Which numpy memory order to use.
Attributes:
width (int): Read only width of this Map.
height (int): Read only height of this Map.
transparent: A boolean array of transparent cells.
walkable: A boolean array of walkable cells.
fov: A boolean array of the cells lit by :any:'compute_fov'.
Example::
>>> import tcod
>>> m = tcod.map.Map(width=3, height=4)
>>> m.walkable
array([[False, False, False],
[False, False, False],
[False, False, False],
[False, False, False]]...)
# Like the rest of the tcod modules, all arrays here are
# in row-major order and are addressed with [y,x]
>>> m.transparent[:] = True # Sets all to True.
>>> m.transparent[1:3,0] = False # Sets (1, 0) and (2, 0) to False.
>>> m.transparent
array([[ True, True, True],
[False, True, True],
[False, True, True],
[ True, True, True]]...)
>>> m.compute_fov(0, 0)
>>> m.fov
array([[ True, True, True],
[ True, True, True],
[False, True, True],
[False, False, True]]...)
>>> m.fov[3,1]
False
.. deprecated:: 11.13
You no longer need to use this class to hold data for field-of-view
or pathfinding as those functions can now take NumPy arrays directly.
See :any:`tcod.map.compute_fov` and :any:`tcod.path`.
"""
def __init__(
self,
width: int,
height: int,
order: Literal["C", "F"] = "C",
) -> None:
warnings.warn(
"This class may perform poorly and is no longer needed.",
DeprecationWarning,
stacklevel=2,
)
self.width = width
self.height = height
self._order = tcod._internal.verify_order(order)
self.__buffer: NDArray[np.bool_] = np.zeros((height, width, 3), dtype=np.bool_)
self.map_c = self.__as_cdata()
def __as_cdata(self) -> Any: # noqa: ANN401
return ffi.new(
"struct TCOD_Map*",
(
self.width,
self.height,
self.width * self.height,
ffi.from_buffer("struct TCOD_MapCell*", self.__buffer),
),
)
@property
def transparent(self) -> NDArray[np.bool_]:
buffer: np.ndarray[Any, np.dtype[np.bool_]] = self.__buffer[:, :, 0]
return buffer.T if self._order == "F" else buffer
@property
def walkable(self) -> NDArray[np.bool_]:
buffer: np.ndarray[Any, np.dtype[np.bool_]] = self.__buffer[:, :, 1]
return buffer.T if self._order == "F" else buffer
@property
def fov(self) -> NDArray[np.bool_]:
buffer: np.ndarray[Any, np.dtype[np.bool_]] = self.__buffer[:, :, 2]
return buffer.T if self._order == "F" else buffer
[docs]
def compute_fov( # noqa: PLR0913
self,
x: int,
y: int,
radius: int = 0,
light_walls: bool = True,
algorithm: int = tcod.constants.FOV_RESTRICTIVE,
) -> None:
"""Compute a field-of-view on the current instance.
Args:
x (int): Point of view, x-coordinate.
y (int): Point of view, y-coordinate.
radius (int): Maximum view distance from the point of view.
A value of `0` will give an infinite distance.
light_walls (bool): Light up walls, or only the floor.
algorithm (int): Defaults to tcod.FOV_RESTRICTIVE
If you already have transparency in a NumPy array then you could use
:any:`tcod.map.compute_fov` instead.
"""
if not (0 <= x < self.width and 0 <= y < self.height):
warnings.warn(
f"Index ({x}, {y}) is outside of this maps shape ({self.width}, {self.height})."
"\nThis will raise an error in future versions.",
RuntimeWarning,
stacklevel=2,
)
lib.TCOD_map_compute_fov(self.map_c, x, y, radius, light_walls, algorithm)
def __setstate__(self, state: dict[str, Any]) -> None:
if "_Map__buffer" not in state: # deprecated
# remove this check on major version update
self.__buffer = np.zeros((state["height"], state["width"], 3), dtype=np.bool_)
self.__buffer[:, :, 0] = state["buffer"] & 0x01
self.__buffer[:, :, 1] = state["buffer"] & 0x02
self.__buffer[:, :, 2] = state["buffer"] & 0x04
del state["buffer"]
state["_order"] = "F"
self.__dict__.update(state)
self.map_c = self.__as_cdata()
def __getstate__(self) -> dict[str, Any]:
state = self.__dict__.copy()
del state["map_c"]
return state
[docs]
def compute_fov(
transparency: ArrayLike,
pov: tuple[int, int],
radius: int = 0,
light_walls: bool = True,
algorithm: int = tcod.constants.FOV_RESTRICTIVE,
) -> NDArray[np.bool_]:
"""Return a boolean mask of the area covered by a field-of-view.
`transparency` is a 2 dimensional array where all non-zero values are
considered transparent. The returned array will match the shape of this
array.
`pov` is the point-of-view origin point. Areas are visible if they can
be seen from this position. `pov` should be a 2D index matching the axes
of the `transparency` array, and must be within the bounds of the
`transparency` array.
`radius` is the maximum view distance from `pov`. If this is zero then
the maximum distance is used.
If `light_walls` is True then visible obstacles will be returned, otherwise
only transparent areas will be.
`algorithm` is the field-of-view algorithm to run. The default value is
`tcod.FOV_RESTRICTIVE`.
The options are:
* `tcod.FOV_BASIC`:
Simple ray-cast implementation.
* `tcod.FOV_DIAMOND`
* `tcod.FOV_SHADOW`:
Recursive shadow caster.
* `tcod.FOV_PERMISSIVE(n)`:
`n` starts at 0 (most restrictive) and goes up to 8 (most permissive.)
* `tcod.FOV_RESTRICTIVE`
* `tcod.FOV_SYMMETRIC_SHADOWCAST`
.. versionadded:: 9.3
.. versionchanged:: 11.0
The parameters `x` and `y` have been changed to `pov`.
.. versionchanged:: 11.17
Added `tcod.FOV_SYMMETRIC_SHADOWCAST` option.
Example:
>>> explored = np.zeros((3, 5), dtype=bool, order="F")
>>> transparency = np.ones((3, 5), dtype=bool, order="F")
>>> transparency[:2, 2] = False
>>> transparency # Transparent area.
array([[ True, True, False, True, True],
[ True, True, False, True, True],
[ True, True, True, True, True]]...)
>>> visible = tcod.map.compute_fov(transparency, (0, 0))
>>> visible # Visible area.
array([[ True, True, True, False, False],
[ True, True, True, False, False],
[ True, True, True, True, False]]...)
>>> explored |= visible # Keep track of an explored area.
.. seealso::
:any:`numpy.where`: For selecting between two arrays using a boolean
array, like the one returned by this function.
:any:`numpy.select`: Select between arrays based on multiple
conditions.
"""
transparency = np.asarray(transparency)
if len(transparency.shape) != 2: # noqa: PLR2004
raise TypeError("transparency must be an array of 2 dimensions" " (shape is %r)" % transparency.shape)
if isinstance(pov, int):
msg = "The tcod.map.compute_fov function has changed. The `x` and `y` parameters should now be given as a single tuple."
raise TypeError(msg)
if not (0 <= pov[0] < transparency.shape[0] and 0 <= pov[1] < transparency.shape[1]):
warnings.warn(
f"Given pov index {pov!r} is outside the array of shape {transparency.shape!r}."
"\nThis will raise an error in future versions.",
RuntimeWarning,
stacklevel=2,
)
map_buffer: NDArray[np.bool_] = np.empty(
transparency.shape,
dtype=[("transparent", bool), ("walkable", bool), ("fov", bool)],
)
map_cdata = ffi.new(
"struct TCOD_Map*",
(
map_buffer.shape[1],
map_buffer.shape[0],
map_buffer.shape[1] * map_buffer.shape[0],
ffi.from_buffer("struct TCOD_MapCell*", map_buffer),
),
)
map_buffer["transparent"] = transparency
lib.TCOD_map_compute_fov(map_cdata, pov[1], pov[0], radius, light_walls, algorithm)
return map_buffer["fov"] # type: ignore