示例#1
0
文件: context.py 项目: alekslis/Rogue
 def pixel_to_subtile(self, x: int, y: int) -> Tuple[float, float]:
     """Convert window pixel coordinates to sub-tile coordinates."""
     with ffi.new("double[2]", (x, y)) as xy:
         _check(
             lib.TCOD_context_screen_pixel_to_tile_d(
                 self._context_p, xy, xy + 1))
         return xy[0], xy[1]
示例#2
0
    def remap(self, codepoint: int, x: int, y: int = 0) -> None:
        """Reassign a codepoint to a character in this tileset.

        `codepoint` is the Unicode codepoint to assign.

        `x` and `y` is the position on the tilesheet to assign to `codepoint`.
        Large values of `x` will wrap to the next row, so `y` isn't necessary
        if you think of the tilesheet as a 1D array.

        This is normally used on loaded tilesheets.  Other methods of Tileset
        creation won't have reliable tile indexes.

        .. versionadded:: 11.12
        """
        tile_i = x + y * self._tileset_p.virtual_columns
        if not (0 <= tile_i < self._tileset_p.tiles_count):
            raise IndexError("Tile %i is non-existent and can't be assigned."
                             " (Tileset has %i tiles.)" %
                             (tile_i, self._tileset_p.tiles_count))
        _check(
            lib.TCOD_tileset_assign_tile(
                self._tileset_p,
                tile_i,
                codepoint,
            ))
示例#3
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
示例#4
0
    def remap(self, codepoint: int, x: int, y: int = 0) -> None:
        """Reassign a codepoint to a character in this tileset.

        `codepoint` is the Unicode codepoint to assign.

        `x` and `y` is the position of the tilesheet to assign to `codepoint`.
        This is the tile position itself, not the pixel position of the tile.
        Large values of `x` will wrap to the next row, so using `x` by itself
        is equivalent to `Tile Index` in the :any:`charmap-reference`.

        This is normally used on loaded tilesheets.  Other methods of Tileset
        creation won't have reliable tile indexes.

        .. versionadded:: 11.12
        """
        tile_i = x + y * self._tileset_p.virtual_columns
        if not (0 <= tile_i < self._tileset_p.tiles_count):
            raise IndexError("Tile %i is non-existent and can't be assigned."
                             " (Tileset has %i tiles.)" %
                             (tile_i, self._tileset_p.tiles_count))
        _check(
            lib.TCOD_tileset_assign_tile(
                self._tileset_p,
                tile_i,
                codepoint,
            ))
示例#5
0
文件: context.py 项目: alekslis/Rogue
 def pixel_to_tile(self, x: int, y: int) -> Tuple[int, int]:
     """Convert window pixel coordinates to tile coordinates."""
     with ffi.new("int[2]", (x, y)) as xy:
         _check(
             lib.TCOD_context_screen_pixel_to_tile_i(
                 self._context_p, xy, xy + 1))
         return xy[0], xy[1]
示例#6
0
 def change_tileset(self, tileset: Optional[tcod.tileset.Tileset]) -> None:
     """Change the active tileset used by this context."""
     _check(
         lib.TCOD_context_change_tileset(
             self._context_p, _handle_tileset(tileset)
         )
     )
示例#7
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
示例#8
0
    def rebuild_frontier(self) -> None:
        """Reconstruct the frontier using the current distance array.

        This is needed if the :any:`distance` array is changed manually.
        After you are finished editing :any:`distance` you must call this
        function before calling :any:`resolve`, :any:`path_from`, etc.
        """
        lib.TCOD_frontier_clear(self._frontier_p)
        self._update_heuristic(None)
        _check(
            lib.rebuild_frontier_from_distance(self._frontier_p,
                                               self._distance_p))
示例#9
0
 def _resolve(self, pathfinder: "Pathfinder") -> None:
     """Run the pathfinding algorithm for this graph."""
     rules, keep_alive = self._compile_rules()
     _check(
         lib.path_compute(
             pathfinder._frontier_p,
             pathfinder._distance_p,
             pathfinder._travel_p,
             len(rules),
             rules,
             pathfinder._heuristic_p,
         ))
示例#10
0
文件: context.py 项目: alekslis/Rogue
    def recommended_console_size(self,
                                 min_columns: int = 1,
                                 min_rows: int = 1) -> Tuple[int, int]:
        """Return the recommended (columns, rows) of a console for this
        context.

        `min_columns`, `min_rows` are the lowest values which will be returned.

        If result is only used to create a new console then you may want to
        call :any:`Context.new_console` instead.
        """
        with ffi.new("int[2]") as size:
            _check(
                lib.TCOD_context_recommended_console_size(
                    self._context_p, 1.0, size, size + 1))
            return max(min_columns, size[0]), max(min_rows, size[1])
示例#11
0
文件: context.py 项目: alekslis/Rogue
    def new_console(
        self,
        min_columns: int = 1,
        min_rows: int = 1,
        magnification: float = 1.0,
        order: Union[Literal["C"], Literal["F"]] = "C",
    ) -> tcod.console.Console:
        """Return a new console sized for this context.

        `min_columns` and `min_rows` are the minimum size to use for the new
        console.

        `magnification` determines the apparent size of the tiles on the output
        display.  A `magnification` larger then 1.0 will output smaller
        consoles, which will show as larger tiles when presented.
        `magnification` must be greater than zero.

        `order` is passed to :any:`tcod.console.Console` to determine the
        memory order of its NumPy attributes.

        The times where it is the most useful to call this method are:

        * After the context is created, even if the console was given a
          specific size.
        * After the :any:`change_tileset` method is called.
        * After any window resized event, or any manual resizing of the window.

        .. versionadded:: 11.18

        .. versionchanged:: 11.19
            Added `order` parameter.

        .. seealso::
            :any:`tcod.console.Console`
        """
        if magnification < 0:
            raise ValueError(
                "Magnification must be greater than zero. (Got %f)" %
                magnification)
        size = ffi.new("int[2]")
        _check(
            lib.TCOD_context_recommended_console_size(self._context_p,
                                                      magnification, size,
                                                      size + 1))
        width, height = max(min_columns, size[0]), max(min_rows, size[1])
        return tcod.console.Console(width, height, order=order)
示例#12
0
文件: context.py 项目: alekslis/Rogue
    def present(
        self,
        console: tcod.console.Console,
        *,
        keep_aspect: bool = False,
        integer_scaling: bool = False,
        clear_color: Tuple[int, int, int] = (0, 0, 0),
        align: Tuple[float, float] = (0.5, 0.5)
    ) -> None:
        """Present a console to this context's display.

        `console` is the console you want to present.

        If `keep_aspect` is True then the console aspect will be preserved with
        a letterbox.  Otherwise the console will be stretched to fill the
        screen.

        If `integer_scaling` is True then the console will be scaled in integer
        increments.  This will have no effect if the console must be shrunk.
        You can use :any:`tcod.console.recommended_size` to create a console
        which will fit the window without needing to be scaled.

        `clear_color` is an RGB tuple used to clear the screen before the
        console is presented, this will affect the border/letterbox color.

        `align` is an (x, y) tuple determining where the console will be placed
        when letter-boxing exists.  Values of 0 will put the console at the
        upper-left corner.  Values of 0.5 will center the console.
        """
        clear_rgba = (clear_color[0], clear_color[1], clear_color[2], 255)
        viewport_args = ffi.new(
            "TCOD_ViewportOptions*",
            {
                "tcod_version": lib.TCOD_COMPILEDVERSION,
                "keep_aspect": keep_aspect,
                "integer_scaling": integer_scaling,
                "clear_color": clear_rgba,
                "align_x": align[0],
                "align_y": align[1],
            },
        )
        _check(
            lib.TCOD_context_present(self._context_p, console.console_c,
                                     viewport_args))
示例#13
0
    def recommended_console_size(self,
                                 min_columns: int = 1,
                                 min_rows: int = 1) -> Tuple[int, int]:
        """Return the recommended (columns, rows) of a console for this
        context.

        The times where it's the most useful to call this method are:

        * After the context is created, even if the console was given a
          specific size.
        * After the :any:`change_tileset` method is called.
        * After any window resized event, or any manual resizing of the window.

        `min_columns`, `min_rows` are the lowest values which will be returned.
        """
        with ffi.new("int[2]") as size:
            _check(
                lib.TCOD_context_recommended_console_size(
                    self._context_p, size, size + 1))
            return max(min_columns, size[0]), max(min_rows, size[1])
示例#14
0
    def remap(self, codepoint: int, x: int, y: int = 0) -> None:
        """Reassign a codepoint to a character in this tileset.

        `codepoint` is the Unicode codepoint to assign.

        `x` and `y` is the position on the tilesheet to assign to `codepoint`.
        Large values of `x` will wrap to the next row, so `y` isn't necessary
        if you think of the tilesheet as a 1D array.

        This is normally used on loaded tilesheets.  Other methods of Tileset
        creation won't have reliable tile indexes.

        .. versionadded:: 11.12
        """
        _check(
            lib.TCOD_tileset_assign_tile(
                self._tileset_p,
                codepoint,
                x + y * self._tileset_p.virtual_columns,
            ))
示例#15
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
示例#16
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
示例#17
0
def dijkstra2d(distance: np.array,
               cost: np.array,
               cardinal: Optional[int] = None,
               diagonal: Optional[int] = None,
               *,
               edge_map: Any = None) -> None:
    """Return the computed distance of all nodes on a 2D Dijkstra grid.

    `distance` is an input/output array of node distances.  Is this often an
    array filled with maximum finite values and 1 or more points with a low
    value such as 0.  Distance will flow from these low values to adjacent
    nodes based the cost to reach those nodes.  This array is modified
    in-place.

    `cost` is an array of node costs.  Any node with a cost less than or equal
    to 0 is considered blocked off.  Positive values are the distance needed to
    reach that node.

    `cardinal` and `diagonal` are the cost multipliers for edges in those
    directions.  A value of None or 0 will disable those directions.  Typical
    values could be: ``1, None``, ``1, 1``, ``2, 3``, etc.

    `edge_map` is a 2D array of edge costs with the origin point centered on
    the array.  This can be used to define the edges used from one node to
    another.  This parameter can be hard to understand so you should see how
    it's used in the examples.

    Example::

        >>> import numpy as np
        >>> import tcod
        >>> cost = np.ones((3, 3), dtype=np.uint8)
        >>> cost[:2, 1] = 0
        >>> cost
        array([[1, 0, 1],
               [1, 0, 1],
               [1, 1, 1]], dtype=uint8)
        >>> dist = tcod.path.maxarray((3, 3), dtype=np.int32)
        >>> dist[0, 0] = 0
        >>> dist
        array([[         0, 2147483647, 2147483647],
               [2147483647, 2147483647, 2147483647],
               [2147483647, 2147483647, 2147483647]]...)
        >>> tcod.path.dijkstra2d(dist, cost, 2, 3)
        >>> dist
        array([[         0, 2147483647,         10],
               [         2, 2147483647,          8],
               [         4,          5,          7]]...)
        >>> path = tcod.path.hillclimb2d(dist, (2, 2), True, True)
        >>> path
        array([[2, 2],
               [2, 1],
               [1, 0],
               [0, 0]], dtype=int32)
        >>> path = path[::-1].tolist()
        >>> while path:
        ...     print(path.pop(0))
        [0, 0]
        [1, 0]
        [2, 1]
        [2, 2]

    `edge_map` is used for more complicated graphs.  The following example
    uses a 'knight move' edge map.

    Example::

        >>> import numpy as np
        >>> import tcod
        >>> knight_moves = [
        ...     [0, 1, 0, 1, 0],
        ...     [1, 0, 0, 0, 1],
        ...     [0, 0, 0, 0, 0],
        ...     [1, 0, 0, 0, 1],
        ...     [0, 1, 0, 1, 0],
        ... ]
        >>> dist = tcod.path.maxarray((8, 8))
        >>> dist[0,0] = 0
        >>> cost = np.ones((8, 8), int)
        >>> tcod.path.dijkstra2d(dist, cost, edge_map=knight_moves)
        >>> dist
        array([[0, 3, 2, 3, 2, 3, 4, 5],
               [3, 4, 1, 2, 3, 4, 3, 4],
               [2, 1, 4, 3, 2, 3, 4, 5],
               [3, 2, 3, 2, 3, 4, 3, 4],
               [2, 3, 2, 3, 4, 3, 4, 5],
               [3, 4, 3, 4, 3, 4, 5, 4],
               [4, 3, 4, 3, 4, 5, 4, 5],
               [5, 4, 5, 4, 5, 4, 5, 6]]...)
        >>> tcod.path.hillclimb2d(dist, (7, 7), edge_map=knight_moves)
        array([[7, 7],
               [5, 6],
               [3, 5],
               [1, 4],
               [0, 2],
               [2, 1],
               [0, 0]], dtype=int32)

    `edge_map` can also be used to define a hex-grid.
    See https://www.redblobgames.com/grids/hexagons/ for more info.
    The following example is using axial coordinates.

    Example::

        hex_edges = [
            [0, 1, 1],
            [1, 0, 1],
            [1, 1, 0],
        ]

    .. versionadded:: 11.2

    .. versionchanged:: 11.13
        Added the `edge_map` parameter.
    """
    dist = distance
    cost = np.asarray(cost)
    if dist.shape != cost.shape:
        raise TypeError("distance and cost must have the same shape %r != %r" %
                        (dist.shape, cost.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_cost_edges(edge_map)
        _check(lib.dijkstra2d(c_dist, _export(cost), n_edges, c_edges))
    else:
        if cardinal is None:
            cardinal = 0
        if diagonal is None:
            diagonal = 0
        _check(lib.dijkstra2d_basic(c_dist, _export(cost), cardinal, diagonal))
示例#18
0
文件: context.py 项目: alekslis/Rogue
 def renderer_type(self) -> int:
     """Return the libtcod renderer type used by this context."""
     return _check(lib.TCOD_context_get_renderer_type(self._context_p))
示例#19
0
文件: context.py 项目: alekslis/Rogue
 def save_screenshot(self, path: Optional[str] = None) -> None:
     """Save a screen-shot to the given file path."""
     c_path = path.encode("utf-8") if path is not None else ffi.NULL
     _check(lib.TCOD_context_save_screenshot(self._context_p, c_path))
示例#20
0
def dijkstra2d(
    distance: np.array,
    cost: np.array,
    cardinal: Optional[int],
    diagonal: Optional[int],
) -> None:
    """Return the computed distance of all nodes on a 2D Dijkstra grid.

    `distance` is an input/output array of node distances.  Is this often an
    array filled with maximum finite values and 1 or more points with a low
    value such as 0.  Distance will flow from these low values to adjacent
    nodes based the cost to reach those nodes.  This array is modified
    in-place.

    `cost` is an array of node costs.  Any node with a cost less than or equal
    to 0 is considered blocked off.  Positive values are the distance needed to
    reach that node.

    `cardinal` and `diagonal` are the cost multipliers for edges in those
    directions.  A value of None or 0 will disable those directions.  Typical
    values could be: ``1, None``, ``1, 1``, ``2, 3``, etc.

    Example:

        >>> import numpy as np
        >>> import tcod
        >>> cost = np.ones((3, 3), dtype=np.uint8)
        >>> cost[:2, 1] = 0
        >>> cost
        array([[1, 0, 1],
               [1, 0, 1],
               [1, 1, 1]], dtype=uint8)
        >>> dist = tcod.path.maxarray((3, 3), dtype=np.int32)
        >>> dist[0, 0] = 0
        >>> dist
        array([[         0, 2147483647, 2147483647],
               [2147483647, 2147483647, 2147483647],
               [2147483647, 2147483647, 2147483647]]...)
        >>> tcod.path.dijkstra2d(dist, cost, 2, 3)
        >>> dist
        array([[         0, 2147483647,         10],
               [         2, 2147483647,          8],
               [         4,          5,          7]]...)
        >>> path = tcod.path.hillclimb2d(dist, (2, 2), True, True)
        >>> path
        array([[2, 2],
               [2, 1],
               [1, 0],
               [0, 0]], dtype=int32)
        >>> path = path[::-1].tolist()
        >>> while path:
        ...     print(path.pop(0))
        [0, 0]
        [1, 0]
        [2, 1]
        [2, 2]

    .. versionadded:: 11.2
    """
    dist = distance
    cost = np.asarray(cost)
    if dist.shape != cost.shape:
        raise TypeError("distance and cost must have the same shape %r != %r" %
                        (dist.shape, cost.shape))
    if cardinal is None:
        cardinal = 0
    if diagonal is None:
        diagonal = 0
    _check(lib.dijkstra2d(_export(dist), _export(cost), cardinal, diagonal))