def __init__(self, array: Any): self._array = np.ascontiguousarray(array, dtype=np.uint8) height, width, depth = self._array.shape if depth != 3: raise TypeError( "Array must have RGB channels. Shape is: %r" % (self._array.shape,) ) self._buffer = ffi.from_buffer("TCOD_color_t[]", self._array) self._mipmaps = ffi.new( "struct TCOD_mipmap_*", { "width": width, "height": height, "fwidth": width, "fheight": height, "buf": self._buffer, "dirty": True, }, ) self.image_c = ffi.new( "TCOD_Image*", { "nb_mipmaps": 1, "mipmaps": self._mipmaps, "has_key_color": False, }, )
def _get_size(self) -> Tuple[int, int]: """Return the (width, height) for this Image. Returns: Tuple[int, int]: The (width, height) of this Image """ w = ffi.new("int *") h = ffi.new("int *") lib.TCOD_image_get_size(self.image_c, w, h) return w[0], h[0]
def heightmap_get_minmax(hm: np.ndarray) -> Tuple[float, float]: """Return the min and max values of this heightmap. Args: hm (numpy.ndarray): A numpy.ndarray formatted for heightmap functions. Returns: Tuple[float, float]: The (min, max) values. .. deprecated:: 2.0 Use ``hm.min()`` or ``hm.max()`` instead. """ mi = ffi.new("float *") ma = ffi.new("float *") lib.TCOD_heightmap_get_minmax(_heightmap_cdata(hm), mi, ma) return mi[0], ma[0]
def heightmap_kernel_transform( hm: np.ndarray, kernelsize: int, dx: Sequence[int], dy: Sequence[int], weight: Sequence[float], minLevel: float, maxLevel: float, ) -> None: """Apply a generic transformation on the map, so that each resulting cell value is the weighted sum of several neighbour cells. This can be used to smooth/sharpen the map. Args: hm (numpy.ndarray): A numpy.ndarray formatted for heightmap functions. kernelsize (int): Should be set to the length of the parameters:: dx, dy, and weight. dx (Sequence[int]): A sequence of x coorinates. dy (Sequence[int]): A sequence of y coorinates. weight (Sequence[float]): A sequence of kernelSize cells weight. The value of each neighbour cell is scaled by its corresponding weight minLevel (float): No transformation will apply to cells below this value. maxLevel (float): No transformation will apply to cells above this value. See examples below for a simple horizontal smoothing kernel : replace value(x,y) with 0.33*value(x-1,y) + 0.33*value(x,y) + 0.33*value(x+1,y). To do this, you need a kernel of size 3 (the sum involves 3 surrounding cells). The dx,dy array will contain: * dx=-1, dy=0 for cell (x-1, y) * dx=1, dy=0 for cell (x+1, y) * dx=0, dy=0 for cell (x, y) * The weight array will contain 0.33 for each cell. Example: >>> import numpy as np >>> heightmap = np.zeros((3, 3), dtype=np.float32) >>> heightmap[:,1] = 1 >>> dx = [-1, 1, 0] >>> dy = [0, 0, 0] >>> weight = [0.33, 0.33, 0.33] >>> tcod.heightmap_kernel_transform(heightmap, 3, dx, dy, weight, ... 0.0, 1.0) """ cdx = ffi.new("int[]", dx) cdy = ffi.new("int[]", dy) cweight = ffi.new("float[]", weight) lib.TCOD_heightmap_kernel_transform( _heightmap_cdata(hm), kernelsize, cdx, cdy, cweight, minLevel, maxLevel )
def get() -> Iterator[Any]: """Return an iterator for all pending events. Events are processed as the iterator is consumed. Breaking out of, or discarding the iterator will leave the remaining events on the event queue. It is also safe to call this function inside of a loop that is already handling events (the event iterator is reentrant.) Example:: for event in tcod.event.get(): if event.type == "QUIT": print(event) raise SystemExit() elif event.type == "KEYDOWN": print(event) elif event.type == "MOUSEBUTTONDOWN": print(event) elif event.type == "MOUSEMOTION": print(event) else: print(event) # For loop exits after all current events are processed. """ sdl_event = ffi.new("SDL_Event*") while lib.SDL_PollEvent(sdl_event): if sdl_event.type in _SDL_TO_CLASS_TABLE: yield _SDL_TO_CLASS_TABLE[sdl_event.type].from_sdl_event(sdl_event) else: yield Undefined.from_sdl_event(sdl_event)
def get() -> Iterator[Any]: """Return an iterator for all pending events. Events are processed as the iterator is consumed. Breaking out of, or discarding the iterator will leave the remaining events on the event queue. Example:: for event in tcod.event.get(): if event.type == "QUIT": print(event) raise SystemExit() elif event.type == "KEYDOWN": print(event) elif event.type == "MOUSEBUTTONDOWN": print(event) elif event.type == "MOUSEMOTION": print(event) else: print(event) """ sdl_event = ffi.new("SDL_Event*") while lib.SDL_PollEvent(sdl_event): if sdl_event.type in _SDL_TO_CLASS_TABLE: yield _SDL_TO_CLASS_TABLE[sdl_event.type].from_sdl_event(sdl_event) else: yield Undefined.from_sdl_event(sdl_event)
def heightmap_add_voronoi( hm: np.ndarray, nbCoef: int, coef: Sequence[float], rnd: Optional[tcod.random.Random] = None, ) -> None: """Add values from a Voronoi diagram to the heightmap. Args: hm (numpy.ndarray): A numpy.ndarray formatted for heightmap functions. nbCoef (int): The diagram value is calculated from the nbCoef closest sites. coef (Sequence[float]): The distance to each site is scaled by the corresponding coef. Closest site : coef[0], second closest site : coef[1], ... rnd (Optional[Random]): A Random instance, or None. """ nbPoints = len(coef) ccoef = ffi.new("float[]", coef) lib.TCOD_heightmap_add_voronoi( _heightmap_cdata(hm), nbPoints, nbCoef, ccoef, rnd.random_c if rnd else ffi.NULL, )
def _pixel_to_tile(x: float, y: float) -> Tuple[float, float]: """Convert pixel coordinates to tile coordinates.""" if not lib.TCOD_ctx.engine: return 0, 0 xy = ffi.new("double[2]", (x, y)) lib.TCOD_sys_pixel_to_tile(xy, xy + 1) return xy[0], xy[1]
def _setstate_old(self, state: Any) -> None: self._random = state[0] self.noise_c = ffi.new("struct TCOD_Noise*") self.noise_c.ndim = state[3] ffi.buffer(self.noise_c.map)[:] = state[4] ffi.buffer(self.noise_c.buffer)[:] = state[5] self.noise_c.H = state[6] self.noise_c.lacunarity = state[7] ffi.buffer(self.noise_c.exponent)[:] = state[8] if state[9]: # high change of this being prematurely garbage collected! self.__waveletTileData = ffi.new("float[]", 32 * 32 * 32) ffi.buffer(self.__waveletTileData)[:] = state[9] self.noise_c.noise_type = state[10] self._tdl_noise_c = ffi.new( "TDLNoise*", (self.noise_c, self.noise_c.ndim, state[1], state[2]))
def recommended_size() -> Tuple[int, int]: """Return the recommended size of a console for the current active window. The return value from this function can be passed to :any:`Console`. This function will raise RuntimeError if libtcod has not been initialized. .. versionadded:: 11.8 .. seealso:: :any:`tcod.console_init_root` :any:`tcod.console_flush` """ if not lib.TCOD_ctx.engine: raise RuntimeError("The libtcod engine was not initialized first.") window = lib.TCOD_sys_get_sdl_window() renderer = lib.TCOD_sys_get_sdl_renderer() with ffi.new("int[2]") as xy: if renderer: lib.SDL_GetRendererOutputSize(renderer, xy, xy + 1) else: # Assume OpenGL if a renderer does not exist. lib.SDL_GL_GetDrawableSize(window, xy, xy + 1) w = max(1, xy[0] // lib.TCOD_ctx.tileset.tile_width) h = max(1, xy[1] // lib.TCOD_ctx.tileset.tile_height) return w, h
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
def recommended_size() -> Tuple[int, int]: """Return the recommended size of a console for the current active window. The return is determined from the active tileset size and active window size. This result should be used create an :any:`Console` instance. This function will raise RuntimeError if libtcod has not been initialized. .. versionadded:: 11.8 .. seealso:: :any:`tcod.console_init_root` :any:`tcod.console_flush` .. deprecated:: 11.13 This function does not support contexts. Use :any:`Context.recommended_console_size` instead. """ if not lib.TCOD_ctx.engine: raise RuntimeError("The libtcod engine was not initialized first.") window = lib.TCOD_sys_get_sdl_window() renderer = lib.TCOD_sys_get_sdl_renderer() with ffi.new("int[2]") as xy: if renderer: lib.SDL_GetRendererOutputSize(renderer, xy, xy + 1) else: # Assume OpenGL if a renderer does not exist. lib.SDL_GL_GetDrawableSize(window, xy, xy + 1) w = max(1, xy[0] // lib.TCOD_ctx.tileset.tile_width) h = max(1, xy[1] // lib.TCOD_ctx.tileset.tile_height) return w, h
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]
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]
def _compile_bool_edges(edge_map: Any) -> Tuple[Any, int]: """Return an edge array using a boolean map.""" edge_map = np.copy(edge_map) edge_center = edge_map.shape[0] // 2, edge_map.shape[1] // 2 edge_map[edge_center] = 0 edge_array = np.transpose(edge_map.nonzero()) edge_array -= edge_center return ffi.new("int[]", list(edge_array.flat)), len(edge_array)
def _handle_title(title: Optional[str]) -> Any: """Return title as a CFFI string. If title is None then return a decent default title is returned. """ if title is None: title = os.path.basename(sys.argv[0]) return ffi.new("char[]", title.encode("utf-8"))
def __setstate__(self, state: Any) -> None: """Create a new cdata object with the stored paramaters.""" try: cdata = state["random_c"] except KeyError: # old/deprecated format cdata = state["cdata"] del state["cdata"] state["random_c"] = ffi.new("mersenne_data_t*", cdata) self.__dict__.update(state)
def get_mouse_state() -> MouseState: """Return the current state of the mouse. .. versionadded:: 9.3 """ xy = ffi.new("int[2]") buttons = lib.SDL_GetMouseState(xy, xy + 1) x, y = _pixel_to_tile(*xy) return MouseState((xy[0], xy[1]), (int(x), int(y)), buttons)
def new_window( width: int, height: int, *, renderer: Optional[int] = None, tileset: Optional[tcod.tileset.Tileset] = None, vsync: bool = True, sdl_window_flags: Optional[int] = None, title: Optional[str] = None ) -> Context: """Create a new context with the desired pixel size. `width` and `height` is the desired pixel resolution of the window. `renderer` is the desired libtcod renderer to use. Typical options are :any:`tcod.context.RENDERER_OPENGL2` for a faster renderer or :any:`tcod.context.RENDERER_SDL2` for a reliable renderer. `tileset` is the font/tileset for the new context to render with. The fall-back tileset available from passing None is useful for prototyping, but will be unreliable across platforms. `vsync` is the Vertical Sync option for the window. The default of True is recommended but you may want to use False for benchmarking purposes. `sdl_window_flags` is a bit-field of SDL window flags, if None is given then a default of :any:`tcod.context.SDL_WINDOW_RESIZABLE` is used. There's more info on the SDL documentation: https://wiki.libsdl.org/SDL_CreateWindow#Remarks `title` is the desired title of the window. After the context is created you can use :any:`Context.recommended_console_size` to figure out the size of the console for the context. """ context_pp = ffi.new("TCOD_Context**") if renderer is None: renderer = RENDERER_SDL2 if sdl_window_flags is None: sdl_window_flags = SDL_WINDOW_RESIZABLE tileset_p = _handle_tileset(tileset) title = _handle_title(title) _check_warn( lib.TCOD_context_new_window( width, height, renderer, tileset_p, vsync, sdl_window_flags, title.encode("utf-8"), context_pp, ) ) return Context._claim(context_pp[0])
def get_path(self, x: int, y: int) -> List[Tuple[int, int]]: """Return a list of (x, y) steps to reach the goal point, if possible. """ lib.TCOD_dijkstra_path_set(self._path_c, x, y) path = [] pointer_x = ffi.new("int[2]") pointer_y = pointer_x + 1 while lib.TCOD_dijkstra_path_walk(self._path_c, pointer_x, pointer_y): path.append((pointer_x[0], pointer_y[0])) return path
def __as_cdata(self) -> Any: return ffi.new( "struct TCOD_Map*", ( self.width, self.height, self.width * self.height, ffi.from_buffer("struct TCOD_MapCell*", self.__buffer), ), )
def __setstate__(self, state: Any) -> None: if isinstance(state, tuple): # deprecated format return self._setstate_old(state) # unpack wavelet tile data if it exists if "_waveletTileData" in state: state["_waveletTileData"] = ffi.new("float[]", state["_waveletTileData"]) state["noise_c"]["waveletTileData"] = state["_waveletTileData"] else: state["noise_c"]["waveletTileData"] = ffi.NULL # unpack TCOD_Noise and link to Random instance state["noise_c"]["rand"] = state["_random"].random_c state["noise_c"] = ffi.new("struct TCOD_Noise*", state["noise_c"]) # unpack TDLNoise and link to libtcod noise state["_tdl_noise_c"]["noise"] = state["noise_c"] state["_tdl_noise_c"] = ffi.new("TDLNoise*", state["_tdl_noise_c"]) self.__dict__.update(state)
def size(self) -> Tuple[int, int]: """Return the pixel (width, height) of the window. This attribute can be set to change the size of the window but the given size must be greater than (1, 1) or else an exception will be raised. """ xy = ffi.new("int[2]") lib.SDL_GetWindowSize(self.p, xy, xy + 1) return xy[0], xy[1]
def position(self) -> Tuple[int, int]: """Return the (x, y) position of the window. This attribute can be set the move the window. The constants tcod.lib.SDL_WINDOWPOS_CENTERED or tcod.lib.SDL_WINDOWPOS_UNDEFINED can be used. """ xy = ffi.new("int[2]") lib.SDL_GetWindowPosition(self.p, xy, xy + 1) return xy[0], xy[1]
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), ), )
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, ), )
def _heightmap_cdata(array: np.ndarray) -> ffi.CData: """Return a new TCOD_heightmap_t instance using an array. Formatting is verified during this function. """ if array.flags["F_CONTIGUOUS"]: array = array.transpose() if not array.flags["C_CONTIGUOUS"]: raise ValueError("array must be a contiguous segment.") if array.dtype != np.float32: raise ValueError("array dtype must be float32, not %r" % array.dtype) height, width = array.shape pointer = ffi.from_buffer("float *", array) return ffi.new("TCOD_heightmap_t *", (width, height, pointer))
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
def _compile_rules(self) -> Any: """Compile this graph into a C struct array.""" if not self._edge_rules_p: self._edge_rules_keep_alive = [] rules = [] for rule_ in self._graph.values(): rule = rule_.copy() rule["edge_count"] = len(rule["edge_list"]) # Edge rule format: [i, j, cost, ...] etc. edge_obj = ffi.new("int[]", len(rule["edge_list"]) * (self._ndim + 1)) edge_obj[0:len(edge_obj)] = itertools.chain(*rule["edge_list"]) self._edge_rules_keep_alive.append(edge_obj) rule["edge_array"] = edge_obj self._edge_rules_keep_alive.append(rule["cost"]) rule["cost"] = _export_dict(rule["cost"]) if "condition" in rule: self._edge_rules_keep_alive.append(rule["condition"]) rule["condition"] = _export_dict(rule["condition"]) del rule["edge_list"] rules.append(rule) self._edge_rules_p = ffi.new("struct PathfinderRule[]", rules) return self._edge_rules_p, self._edge_rules_keep_alive
def heightmap_get_normal( hm: np.ndarray, x: float, y: float, waterLevel: float ) -> Tuple[float, float, float]: """Return the map normal at given coordinates. Args: hm (numpy.ndarray): A numpy.ndarray formatted for heightmap functions. x (float): The x coordinate. y (float): The y coordinate. waterLevel (float): The heightmap is considered flat below this value. Returns: Tuple[float, float, float]: An (x, y, z) vector normal. """ cn = ffi.new("float[3]") lib.TCOD_heightmap_get_normal(_heightmap_cdata(hm), x, y, cn, waterLevel) return tuple(cn) # type: ignore