GameEngine/include/extern/roguelike.h
2026-02-20 16:12:16 +01:00

2753 lines
96 KiB
C

/**
* MIT License
*
* Copyright (c) 2024 Michael H. Mackus
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*
* roguelike.h
*
*
* A single header library for tile-based games. Most features have a
* "rl_*_create" and a "rl_*_destroy" function. The create function allocates
* memory and returns a pointer that is assumed to be freed with rl_*_destroy.
* You can avoid using malloc & free by defining RL_MALLOC (and optionally
* RL_CALLOC and RL_REALLOC) and RL_FREE, or simply manage the memory yourself.
*
* Make sure to define RL_IMPLEMENTATION once and only once before including
* "roguelike.h" to compile the library.
*
* The primary feature of this library deals with the tile-based maps and map
* generation. The functions are prefixed with rl_map and rl_mapgen.
*
* To generate a map, create the map via rl_map_create then call the function
* with the algorithm you wish to use for mapgen. For example:
*
* RL_Map *map = rl_map_create(80, 25);
* if (rl_mapgen_bsp(map, RL_MAPGEN_BSP_DEFAULTS) != RL_OK) {
* printf("Error occurred during mapgen!\n");
* }
* ....
* rl_map_destroy(map); // frees the map pointer & internal data
*
* The rl_bsp methods correspond to a BSP graph containing data for rectangles.
* Note that the rl_bsp_split function does allocate memory for the split and
* assigns the new left & right nodes to the BSP tree (this data is freed with
* rl_bsp_destroy).
*
* There is also functionality for a simple min heap (or priority queue).
* These functions are prefixed with rl_heap. To use these you need to create
* a heap with rl_heap_create then insert items with rl_heap_insert. The heap
* does not free or allocate memory for items you insert into the heap.
*
* RL_Heap *q = rl_heap_create(1, NULL); // NULL comparison function acts as dynamic array
* int val = 5;
* rl_heap_insert(q, &val);
* ....
* int r;
* while ((r = rl_heap_pop(eq))) { ... }
* rl_heap_destroy(q);
*
* There is also a set of FOV functions - these functions use a simple
* shadowcasting algorithm to implement FOV. Create the RL_FOV struct with
* rl_fov_create (making sure to free it with rl_fov_destroy), and each time
* you want to "update" the FOV you should call "rl_fov_calculate" or
* "rl_fov_calculate_ex".
*
* RL_FOV *fov = rl_fov_create(80, 25);
* for (;;) { // gameloop
* rl_fov_calculate(fov, map, player_x, player_y, 8); // last arg is FOV radius
* ... // draw map, handle input, etc.
* }
* rl_fov_destroy(fov);
*
* There is also a set of pathfinding functions - these functions primarily
* create and manage Dijkstra graphs for pathfinding. These functions are
* prefixed with rl_path, rl_dijkstra, and rl_graph. Paths should be "walked"
* with "rl_path_walk" which frees each part of the path passed, returning the
* next part of the path; or you can alternatively call "rl_path_destroy".
*
* RL_Path *path = rl_path_create(map, RL_XY(0,0), RL_XY(20,20), rl_distance_euclidian, rl_map_is_passable);
* while ((path = rl_path_walk(path))) { ... } // frees the path
*
* Dijkstra graphs can be created & scored with rl_dijkstra_create or manually
* scored via rl_dijkstra_score* functions. After the graph is scored the
* graph is can be walked by finding a "start" node in the graph, and
* recursively walking the graph by choosing the lowest scored neighbor. If a
* RL_GraphNode has a score of FLT_MAX it has not been scored.
*
* // Typically you provide destination for the initial Dijkstra graph
* RL_Graph *graph = rl_dijkstra_create(map, dest, rl_distance_manhattan, rl_map_is_passable);
* // Then find start point in graph
* RL_GraphNode *node = rl_graph_node(graph, start);
* // Then, you "roll downhill" from the start point
* if (node != NULL) {
* RL_GraphNode *lowest_neighbor = rl_graph_node_lowest_neighbor(node);
* // The next point in the path is lowest_neighbor->point
* if (lowest_neighbor) move_player(lowest_neighbor->point);
* ...
* }
* rl_graph_destroy(graph);
*
*
* Preprocessor definitions (define these before including roguelike.h to customize internals):
*
*
* RL_IMPLEMENTATION Define this to compile the library - should only be defined once in one file
* RL_MAX_NEIGHBOR_COUNT Maximum neighbor count for Dijkstra graphs (defaults to 8). Note this is needed in the function definitions - if you override this, you'll have to define it everywhere you include roguelike.h (just make a wrapper).
* RL_FOV_SYMMETRIC Set this to 0 to disable symmetric FOV (defaults to 1)
* RL_MAX_RECURSION Maximum recursion (defaults to 100). This is used in FOV to limit recursion when fov_radius is large or -1 (unlimited).
* RL_MAPGEN_BSP_RANDOMISE_ROOM_LOC Set this to 0 to disable randomizing room locations within bsp (used in rl_mapgen_bsp - defaults to 1)
* RL_ENABLE_PATHFINDING Set this to 0 to disable pathfinding functionality (defaults to 1)
* RL_ENABLE_FOV Set this to 0 to disable field of view functionality (defaults to 1)
* RL_ENABLE_FILE Set this to 0 to disable save & load helper functions.
* RL_IS_PASSABLE Passable tile logic. Macro function - first argument to the macro is the tile, second is x, third is y.
* RL_IS_OPAQUE Opaque tile logic. Macro function - first argument to the macro is the tile, second is x, third is y.
* RL_IS_WALL_TILE Wall tile logic, for checking if this tile can connect to other walls. Macro function - first argument to the macro is the tile, second is x, third is y.
* RL_PASSABLE_F Set this to your default passable function (defaults to rl_map_is_passable).
* RL_OPAQUE_F Set this to your default opaque function (defaults to rl_map_is_opaque).
* RL_WALL_F Set this to your default is_wall function (defaults to rl_map_is_wall).
* RL_FOV_DISTANCE_F Set this to your default FOV distance function (defaults to rl_distance_euclidian).
* RL_RNG_F Set this to your default RNG generation function (defaults to rl_rng_generate).
* RL_ASSERT Define this to override the assert function used by the library (defaults to "assert")
* RL_MALLOC Define this to override the malloc function used by the library (defaults to "malloc")
* RL_CALLOC Define this to override the calloc function used by the library (defaults to "calloc")
* RL_REALLOC Define this to override the realloc function used by the library, used in rl_heap_* (defaults to "realloc")
* RL_FREE Define this to override the free function used by the library (defaults to "free")
*/
#ifdef __cplusplus
extern "C" {
#endif
#ifndef RL_ROGUELIKE_H
#define RL_ROGUELIKE_H
#include <stddef.h>
/* This is a helper since MSVC & c89 don't support compound literals */
#ifndef RL_CLITERAL
#if _MSVC_LANG || __cplusplus
#define RL_CLITERAL(type) type
#elif __STDC_VERSION__ < 199409L
#define RL_CLITERAL(type)
#else
#define RL_CLITERAL(type) (type)
#endif
#endif
/* Bool type in c89 */
#if __STDC_VERSION__ < 199409L && !__cplusplus
typedef int bool;
#define true 1
#define false 0
#else
#include <stdbool.h>
#endif
/**
* Generic structs for library.
*/
/* each tile is the size of 1 byte, so it can be casted back & forth from char <-> RL_Tile */
typedef unsigned char RL_Byte;
/* Generic dungeon map structure, supporting hex & square 2d maps, along with the associated tile enum. */
typedef enum {
RL_TileRock = ' ',
RL_TileRoom = '.',
RL_TileCorridor = '#',
RL_TileDoor = '+',
RL_TileDoorOpen = '='
} RL_Tile;
typedef struct RL_Map {
unsigned int width;
unsigned int height;
RL_Byte *tiles; /* a sequential array of RL_Tiles, stride for each row equals the map width. */
} RL_Map;
/* BSP tree */
typedef struct RL_BSP {
unsigned int width;
unsigned int height;
unsigned int x;
unsigned int y;
struct RL_BSP *parent;
struct RL_BSP *left; /* left child */
struct RL_BSP *right; /* right child */
} RL_BSP;
/* BSP split direction */
typedef enum {
RL_SplitHorizontally, /* split the BSP node on the x axis (splits width) */
RL_SplitVertically /* split the BSP node on the y axis (splits height) */
} RL_SplitDirection;
/**
* Random map generation
*/
/* Creates an empty map. Make sure to call rl_map_destroy to clear memory. */
RL_Map *rl_map_create(unsigned int width, unsigned int height);
/* Frees the map & internal memory. */
void rl_map_destroy(RL_Map *map);
/* Enum representing the type of corridor connection algorithm. RL_ConnectRandomly is the default and results in the
* most interesting & aesthetic maps. */
typedef enum {
RL_ConnectNone = 0, /* don't connect corridors */
RL_ConnectRandomly, /* connect corridors to random leaf nodes (requires RL_ENABLE_PATHFINDING, by default this is on) */
RL_ConnectBSP, /* connect corridors by traversing the BSP graph (faster than above but less circular/interesting maps, requires RL_ENABLE_PATHFINDING) */
RL_ConnectSimple /* connect corridors by traversing the BSP graph without Dijkstra pathfinding (fastest) */
} RL_MapgenCorridorConnection;
/* The config for BSP map generation - note that the dimensions *include* the walls on both sides, so the min room width
* & height the library accepts is 3. */
typedef struct {
unsigned int room_min_width;
unsigned int room_max_width;
unsigned int room_min_height;
unsigned int room_max_height;
unsigned int room_padding;
RL_MapgenCorridorConnection draw_corridors; /* type of corridor connection algorithm to use */
bool draw_doors; /* whether to draw doors while connecting corridors */
int max_splits; /* max times to split BSP - set lower for less rooms */
} RL_MapgenConfigBSP;
/* Provide some defaults for mapgen. */
#define RL_MAPGEN_BSP_DEFAULTS RL_CLITERAL(RL_MapgenConfigBSP) { \
/*.room_min_width =*/ 4, \
/*.room_max_width =*/ 6, \
/*.room_min_height =*/ 4, \
/*.room_max_height =*/ 6, \
/*.room_padding =*/ 1, \
/*.draw_corridors =*/ RL_ConnectRandomly, \
/*.draw_doors =*/ true, \
/*.max_splits =*/ 100 \
}
typedef enum {
RL_OK = 0,
RL_ErrorMemory,
RL_ErrorNullParameter,
RL_ErrorMapgenInvalidConfig
} RL_Status;
/* Generate map with recursive BSP split algorithm. This fills the map tiles with RL_TileRock before generation. */
RL_Status rl_mapgen_bsp(RL_Map *map, RL_MapgenConfigBSP config);
/* Generates map with recursive BSP split algorithm. This splits the BSP pointer passed, and uses the BSP to constrain
* the dimensions of the map generation.
*
* This allocates memory for the BSP children - make sure to use rl_bsp_destroy or free them yourself. Note that this
* does not set the tiles to RL_TileRock before generation. This way you can have separate regions of the map with
* different mapgen algorithms. */
RL_Status rl_mapgen_bsp_ex(RL_Map *map, RL_BSP *bsp, const RL_MapgenConfigBSP *config);
/* The config for BSP map generation - note that the dimensions *include* the walls on both sides, so the min room width
* & height the library accepts is 3. */
typedef struct {
unsigned int chance_cell_initialized; /* chance (from 1-100) a cell is initialized with rock */
unsigned int birth_threshold; /* threshold of neighbors for a cell to be born */
unsigned int survival_threshold; /* threshold of neighbors for a cell to die from overpopulation */
unsigned int max_iterations; /* recursion limit */
bool draw_corridors; /* after generation, whether to randomly draw corridors to unconnected space
* note - you still need cull_unconnected if you want a fully connected map
*
* requires RL_ENABLE_PATHFINDING */
bool cull_unconnected; /* after generation, whether to remove unconnected space from the larger map - requires RL_ENABLE_PATHFINDING */
bool fill_border; /* after generation, whether to fill the border with rock to ensure enclosed map*/
} RL_MapgenConfigAutomata;
/* Provide some defaults for automata mapgen. */
#define RL_MAPGEN_AUTOMATA_DEFAULTS RL_CLITERAL(RL_MapgenConfigAutomata) { \
/*.chance_cell_initialized =*/ 45, \
/*.birth_threshold =*/ 5, \
/*.survival_threshold =*/ 4, \
/*.max_iterations =*/ 3, \
/*.draw_corridors = */ true, \
/*.cull_unconnected =*/ true, \
/*.fill_border =*/ true \
}
/* Generate map with cellular automata. This clears out the previous tiles before generation. */
RL_Status rl_mapgen_automata(RL_Map *map, RL_MapgenConfigAutomata config);
/* Same as above function, but constrains generation according to passed dimensions. */
RL_Status rl_mapgen_automata_ex(RL_Map *map, unsigned int x, unsigned int y, unsigned int width, unsigned int height, const RL_MapgenConfigAutomata *config);
/* Generate map with a random maze (via simplistic BFS). Tiles are carved with RL_TileCorridor. Fully connected. */
RL_Status rl_mapgen_maze(RL_Map *map);
/* Generate map with a random maze (via simplistic BFS). Tiles are carved with RL_TileCorridor. Fully connected. */
RL_Status rl_mapgen_maze_ex(RL_Map *map, unsigned int x, unsigned int y, unsigned int width, unsigned int height);
/* Connect map via corridors using the supplied BSP graph. */
RL_Status rl_mapgen_connect_corridors(RL_Map *map, RL_BSP *root, bool draw_doors, RL_MapgenCorridorConnection connection_algorithm);
/**
* Generic map helper functions.
*/
/* Verifies a coordinates is within bounds of map. */
bool rl_map_in_bounds(const RL_Map *map, unsigned int x, unsigned int y);
/* Checks if a tile is passable. */
bool rl_map_is_passable(const RL_Map *map, unsigned int x, unsigned int y);
/* Checks if a tile is opaque (for FOV calculations). */
bool rl_map_is_opaque(const RL_Map *map, unsigned int x, unsigned int y);
/* Get tile at point */
RL_Byte *rl_map_tile(const RL_Map *map, unsigned int x, unsigned int y);
/* Returns 1 if tile at point matches given parameter. */
bool rl_map_tile_is(const RL_Map *map, unsigned int x, unsigned int y, RL_Byte tile);
/* Type of wall on the map - idea is they can be bitmasked together (e.g. for corners). See rl_map_wall and other
* related functions. */
typedef enum {
RL_WallToWest = 1,
RL_WallToEast = 1 << 1,
RL_WallToNorth = 1 << 2,
RL_WallToSouth = 1 << 3,
RL_WallOther = 1 << 7 /* e.g. a wall that has no connecting walls */
} RL_Wall;
/* A tile is considered a wall if it is touching a passable tile.
*
* Returns a bitmask of the RL_Wall enum. For example, a wall with a wall tile to the south, west, and east would have a
* bitmask of 0b1011. */
RL_Byte rl_map_wall(const RL_Map *map, unsigned int x, unsigned int y);
/* Is the tile a wall tile? */
bool rl_map_is_wall(const RL_Map *map, unsigned int x, unsigned int y);
/* Is the wall a corner? */
bool rl_map_is_corner_wall(const RL_Map *map, unsigned int x, unsigned int y);
/* Is this a wall that is touching a room tile? */
bool rl_map_is_room_wall(const RL_Map *map, unsigned int x, unsigned int y);
/* A wall that is touching a room tile (e.g. to display it lit). */
RL_Byte rl_map_room_wall(const RL_Map *map, unsigned int x, unsigned int y);
/**
* Simple priority queue implementation
*/
typedef struct {
void **heap;
int cap;
int len;
int (*comparison_f)(const void *heap_item_a, const void *heap_item_b);
} RL_Heap;
/* Allocates memory for the heap. Make sure to call rl_heap_destroy after you are done.
*
* capacity - initial capacity for the heap
* comparison_f - A comparison function that returns 1 if heap_item_a should be
* popped from the queue before heap_item_b. If NULL the heap will still work
* but order will be undefined. */
RL_Heap *rl_heap_create(int capacity, int (*comparison_f)(const void *heap_item_a, const void *heap_item_b));
/* Frees the heap & internal memory. */
void rl_heap_destroy(RL_Heap *h);
/* Return the length of the heap items */
int rl_heap_length(const RL_Heap *h);
/* Insert item into the heap. This will resize the heap if necessary. */
bool rl_heap_insert(RL_Heap *h, void *item);
/* Returns & removes an item from the queue. */
void *rl_heap_pop(RL_Heap *h);
/* Peek at the first item in the queue. This does not remove the item from the queue. */
void *rl_heap_peek(RL_Heap *h);
/**
* BSP Manipulation
*/
/* Params width & height must be positive. Make sure to free with rl_bsp_destroy. */
RL_BSP *rl_bsp_create(unsigned int width, unsigned int height);
/* Frees the BSP root & all children */
void rl_bsp_destroy(RL_BSP *root);
/* Split the BSP by direction - this creates the left & right leaf and */
/* populates them in the BSP node. Position must be positive and within */
/* the BSP root node. Also node->left & node->right must be NULL */
void rl_bsp_split(RL_BSP *node, unsigned int position, RL_SplitDirection direction);
/* Recursively split the BSP. Used for map generation. */
/* */
/* Returns true if the BSP was able to split at least once */
RL_Status rl_bsp_recursive_split(RL_BSP *root, unsigned int min_width, unsigned int min_height, unsigned int max_recursion);
/* Returns 1 if the node is a leaf node. */
bool rl_bsp_is_leaf(const RL_BSP *node);
/* Return sibling node. Returns NULL if there is no parent (i.e. for the root */
/* node). */
RL_BSP *rl_bsp_sibling(const RL_BSP *node);
/* Returns amount of leaves in tree. */
size_t rl_bsp_leaf_count(const RL_BSP *root);
/* Return the next leaf node to the right if it exists. */
RL_BSP *rl_bsp_next_leaf(const RL_BSP *node);
/* Returns a random leaf node beneath root */
RL_BSP* rl_bsp_random_leaf(const RL_BSP *root);
/**
* Pathfinding - disable with #define RL_ENABLE_PATHFINDING 0
*/
/* A point on the map used for pathfinding. The points are a float type for flexibility since pathfinding works for maps */
/* of all data types. */
typedef struct RL_Point {
float x, y;
} RL_Point;
/* Macro to easily create a RL_Point (compound literals only available in C99, which MSVC doesn't support). */
#define RL_XY(x, y) RL_CLITERAL(RL_Point) { (float)(x), (float)(y) }
/* Max neighbors for a pathfinding node. */
#ifndef RL_MAX_NEIGHBOR_COUNT
#define RL_MAX_NEIGHBOR_COUNT 8
#endif
/* Represents a graph of pathfinding nodes that has been scored for pathfinding (e.g. with the Dijkstra algorithm). */
/* TODO store weights on graph nodes ? */
typedef struct RL_GraphNode {
float score; /* will be FLT_MAX for an unreachable/unscored node in the Dijkstra algorithm */
RL_Point point;
size_t neighbors_length;
struct RL_GraphNode *neighbors[RL_MAX_NEIGHBOR_COUNT];
} RL_GraphNode;
typedef struct RL_Graph {
size_t length; /* length of nodes */
RL_GraphNode *nodes; /* array of nodes - length will be the size of the map.width * map.height */
} RL_Graph;
/* A path is a linked list of paths. You can "walk" a path using rl_path_walk which will simultaneously free the
* previous path. */
typedef struct RL_Path {
RL_Point point;
struct RL_Path *next;
} RL_Path;
/* Useful distance functions for pathfinding. */
float rl_distance_manhattan(RL_Point node, RL_Point end);
float rl_distance_euclidian(RL_Point node, RL_Point end);
float rl_distance_chebyshev(RL_Point node, RL_Point end);
/* Custom distance function for pathfinding - calculates distance between map nodes */
typedef float (*RL_DistanceFun)(RL_Point from, RL_Point to);
/* Custom passable function for pathfinding. Return 0 to prevent neighbor from being included in graph. */
typedef bool (*RL_PassableFun)(void *context, unsigned int x, unsigned int y);
/* Custom score function for pathfinding - most users won't need this, but it gives flexibility in weighting the
* Dijkstra graph. Note that Dijkstra expects you to add the current node's score to the newly calculated score. */
typedef float (*RL_ScoreFun)(const RL_GraphNode *current, const RL_GraphNode *neighbor, void *context);
/* Generates a line starting at from ending at to. Each path in the line will be incremented by step. */
RL_Path *rl_line_create(RL_Point from, RL_Point to, float step);
/* Find a path between start and end via Dijkstra algorithm. Make sure to call rl_path_destroy when done with path.
* Pass NULL to distance_f to use rough approximation for euclidian. */
RL_Path *rl_path_create(const RL_Map *map, RL_Point start, RL_Point end, RL_DistanceFun distance_f);
/* Find a path between start and end via the scored Dijkstra graph. Make sure to call rl_path_destroy when done with path (or
* use rl_path_walk). */
RL_Path *rl_path_create_from_graph(const RL_Graph *graph, RL_Point start);
/* Convenience function to "walk" the path. This will return the next path, freeing the current path. You do not need to
* call rl_path_destroy if you walk the full path. */
RL_Path *rl_path_walk(RL_Path *path);
/* Frees the path & all linked nodes. */
void rl_path_destroy(RL_Path *path);
/* Dijkstra pathfinding algorithm. Pass NULL to distance_f to use rough approximation for euclidian.
*
* You can use Dijkstra maps for pathfinding, simple AI, and much more. For example, by setting the player point to
* "start" then you can pick the highest scored tile in the map and set that as the new "start" point. As with all
* Dijkstra maps, you just walk the map by picking the lowest scored neighbor. This is a simplistic AI resembling a
* wounded NPC fleeing from the player.
*
* Make sure to destroy the resulting RL_Graph with rl_graph_destroy. */
RL_Graph *rl_dijkstra_create(const RL_Map *map,
RL_Point start,
RL_DistanceFun distance_f);
/* Dijkstra pathfinding algorithm. Uses RL_Graph so that your code doesn't need to rely on RL_Map. Each node's
* distance should equal FLT_MAX in the resulting graph if it is impassable. */
void rl_dijkstra_score(RL_Graph *graph, RL_Point start, RL_DistanceFun distance_f);
/* Dijkstra pathfinding algorithm for advanced use cases such as weighting certain tiles higher than others. Uses
* RL_Graph so that your code doesn't need to rely on RL_Map. Each node's distance should equal FLT_MAX in the resulting
* graph if it is impassable. Most users should just use rl_dijkstra_score - only use this if you have a specific need. */
void rl_dijkstra_score_ex(RL_Graph *graph, RL_Point start, RL_ScoreFun score_f, void *score_context);
/* Returns a the largest connected area (of passable tiles) on the map. Make sure to destroy the graph with
* rl_graph_destroy after you are done. */
RL_Graph *rl_graph_floodfill_largest_area(const RL_Map *map);
/* Create an unscored graph based on the 2d map. Make sure to call rl_graph_destroy when finished. */
RL_Graph *rl_graph_create(const RL_Map *map);
/* Create an unscored graph based on the 2d map. Make sure to call rl_graph_destroy when finished. */
RL_Graph *rl_graph_create_ex(const RL_Map *map, void *context, RL_PassableFun passable_f, bool allow_diagonal_neighbors);
/* Reset scores of Dijkstra map */
void rl_graph_reset(RL_Graph *graph);
/* Add two graphs together, adding their scores */
/* Note that this assumes the graph lengths are identical */
void rl_graph_add(RL_Graph *graph, const RL_Graph *graph_b);
/* Multiply the scores of a graph by a coefficient (e.g. to weight a graph) */
void rl_graph_weight(RL_Graph *graph, float coefficient);
/* Frees the graph & internal memory. */
void rl_graph_destroy(RL_Graph *graph);
/* Checks if coordinate is scored in graph (e.g. its score is less than FLT_MAX). */
bool rl_graph_is_scored(const RL_Graph *graph, RL_Point point);
/* Returns the node of a point within a graph if it exists. */
RL_GraphNode *rl_graph_node(const RL_Graph *graph, RL_Point point);
/* Returns the lowest scored neighbor within a graph if it exists - returns NULL if the lowest scored neighbor is scored
* with FLT_MAX (meaning it is unscored). */
RL_GraphNode *rl_graph_node_lowest_neighbor(const RL_GraphNode *node);
/* Sort node neighbors based on score - lowest score will be at node->neighbors[0] */
void rl_graph_node_sort_neighbors(RL_GraphNode *node);
/**
* FOV - disable with #define RL_ENABLE_FOV 0
*/
/* Structure containing information for the FOV algorithm, along with the associated visibility enum. */
typedef enum {
RL_TileCannotSee = 0,
RL_TileVisible,
RL_TileSeen
} RL_TileVisibility;
typedef struct {
unsigned int width;
unsigned int height;
RL_Byte *visibility; /* a sequential array of RL_Visibility, stride for each row = the map width */
} RL_FOV;
/* Creates empty FOV and fills it with opaque tiles. Make sure to call rl_fov_destroy to clear memory. */
RL_FOV *rl_fov_create(unsigned int width, unsigned int height);
/* Frees the FOV & internal memory. */
void rl_fov_destroy(RL_FOV *fov);
/* Function to determine if a tile is within the range of the FOV. Returns true if point is in range. */
typedef bool (*RL_IsInRangeFun)(unsigned int x, unsigned int y, void *context);
/* Function to determine if a tile is considered Opaque for FOV calculation. Make sure you do bounds checking that the
* point is within your map. Returns true if point is considered "opaque" (i.e. unable to see through). */
typedef bool (*RL_IsOpaqueFun)(unsigned int x, unsigned int y, void *context);
/* Function to mark a tile as visible within the FOV. Make sure you do bounds checking that the point is within your map. */
typedef void (*RL_MarkAsVisibleFun)(unsigned int x, unsigned int y, void *context);
/* Calculate FOV using simple shadowcasting algorithm. Set fov_radius to a negative value to have unlimited FOV (note
* this is limited by RL_MAX_RECURSION).
*
* Note that this sets previously visible tiles to RL_TileSeen. */
void rl_fov_calculate(RL_FOV *fov, const RL_Map *map, unsigned int x, unsigned int y, int fov_radius);
/* Calculate FOV using simple shadowcasting algorithm. Set fov_radius to a negative value to have unlimited FOV (note
* this is limited by RL_MAX_RECURSION).
*
* Generic version of above function. */
void rl_fov_calculate_ex(void *context, unsigned int x, unsigned int y, RL_IsInRangeFun in_range_f, RL_IsOpaqueFun opaque_f, RL_MarkAsVisibleFun mark_visible_f);
/* Checks if a point is visible within FOV. Make sure to call rl_fov_calculate first. */
bool rl_fov_is_visible(const RL_FOV *map, unsigned int x, unsigned int y);
/* Checks if a point has been seen within FOV. Make sure to call rl_fov_calculate first. */
bool rl_fov_is_seen(const RL_FOV *map, unsigned int x, unsigned int y);
/**
* Random number generation
*/
/* Default implementation of RNG using standard library. */
unsigned int rl_rng_generate(unsigned int min, unsigned int max);
/**
* Saving & Loading helper functions - to use these make sure to open the file beforehand in binary mode.
*
* The file is a FILE pointer (void* so we don't have to depend on stdio).
*/
bool rl_file_save_map(const RL_Map *data, void *file);
bool rl_file_load_map(RL_Map **data, void *file);
bool rl_file_save_fov(const RL_FOV *data, void *file);
bool rl_file_load_fov(RL_FOV **data, void *file);
#endif /* RL_ROGUELIKE_H */
#ifdef RL_IMPLEMENTATION
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <limits.h>
#ifndef RL_FOV_SYMMETRIC
#define RL_FOV_SYMMETRIC 1
#endif
#ifndef RL_MAX_RECURSION
#define RL_MAX_RECURSION 100
#endif
/* define this to 0 to put the rooms in the middle of the BSP leaf during dungeon generation */
#ifndef RL_MAPGEN_BSP_RANDOMISE_ROOM_LOC
#define RL_MAPGEN_BSP_RANDOMISE_ROOM_LOC 1
#endif
/* define to 0 to disable pathfinding */
#ifndef RL_ENABLE_PATHFINDING
#define RL_ENABLE_PATHFINDING 1
#endif
/* define to 0 to disable FOV */
#ifndef RL_ENABLE_FOV
#define RL_ENABLE_FOV 1
#endif
/* define to 0 to disable save & load */
#ifndef RL_ENABLE_FILE
#define RL_ENABLE_FILE 1
#endif
/* convenience macro for custom passable tile logic (for mapgen & pathfinding) */
#ifndef RL_IS_PASSABLE
#define RL_IS_PASSABLE(t, x, y) (t == RL_TileRoom || t == RL_TileCorridor || t == RL_TileDoor || t == RL_TileDoorOpen)
#endif
/* convenience macro for custom opaque tile logic (for FOV) */
#ifndef RL_IS_OPAQUE
#define RL_IS_OPAQUE(t, x, y) (t == RL_TileDoor || !RL_IS_PASSABLE(t, x, y))
#endif
/* convenience macro for custom wall tile logic (for connections) */
#ifndef RL_IS_WALL_TILE
#define RL_IS_WALL_TILE(t, x, y) (!RL_IS_PASSABLE(t,x,y) || t == RL_TileDoor || t == RL_TileDoorOpen)
#endif
#ifndef RL_PASSABLE_F
#define RL_PASSABLE_F rl_map_is_passable
#endif
#ifndef RL_OPAQUE_F
#define RL_OPAQUE_F rl_map_is_opaque
#endif
#ifndef RL_WALL_F
#define RL_WALL_F rl_map_is_wall
#endif
#ifndef RL_FOV_DISTANCE_F
#define RL_FOV_DISTANCE_F rl_distance_euclidian
#endif
#ifndef RL_RNG_F
#define RL_RNG_F rl_rng_generate
#endif
#ifndef RL_ASSERT
#include <assert.h>
#define RL_ASSERT(expr) (assert(expr));
#endif
#ifndef RL_MALLOC
#define RL_MALLOC malloc
#endif
#ifndef RL_CALLOC
#define RL_CALLOC calloc
#endif
#ifndef RL_REALLOC
#define RL_REALLOC realloc
#endif
#ifndef RL_FREE
#define RL_FREE free
#endif
#define RL_UNUSED(x) (void)x
#if RL_ENABLE_PATHFINDING
#include <float.h>
#include <math.h>
#endif
RL_Map *rl_map_create(unsigned int width, unsigned int height)
{
RL_Map *map;
unsigned char *memory;
RL_ASSERT(width*height < UINT_MAX);
RL_ASSERT(width > 0 && height > 0);
map = NULL;
/* allocate all the memory we need at once */
memory = (unsigned char*) RL_MALLOC(sizeof(*map) + sizeof(*map->tiles)*width*height);
RL_ASSERT(memory);
if (memory == NULL) return NULL;
map = (RL_Map*) memory;
RL_ASSERT(map);
map->width = width;
map->height = height;
map->tiles = (RL_Byte*) (memory + sizeof(*map));
RL_ASSERT(map->tiles);
memset(map->tiles, RL_TileRock, sizeof(*map->tiles)*map->width*map->height);
return map;
}
void rl_map_destroy(RL_Map *map)
{
if (map) {
RL_FREE(map);
}
}
bool rl_map_in_bounds(const RL_Map *map, unsigned int x, unsigned int y)
{
return x < map->width && y < map->height;
}
bool rl_map_is_passable(const RL_Map *map, unsigned int x, unsigned int y)
{
if (rl_map_in_bounds(map, x, y)) {
return RL_IS_PASSABLE(map->tiles[y * map->width + x], x, y);
}
return 0;
}
bool rl_map_is_opaque(const RL_Map *map, unsigned int x, unsigned int y)
{
if (!rl_map_in_bounds(map, x, y)) {
return true;
}
return RL_IS_OPAQUE(map->tiles[y * map->width + x], x, y);
}
RL_Byte *rl_map_tile(const RL_Map *map, unsigned int x, unsigned int y)
{
if (rl_map_in_bounds(map, x, y)) {
return &map->tiles[x + y*map->width];
}
return NULL;
}
bool rl_map_is_wall(const RL_Map *map, unsigned int x, unsigned int y)
{
if (!rl_map_in_bounds(map, x, y))
return 0;
if (RL_IS_WALL_TILE(map->tiles[x + y*map->width], x, y)) {
return RL_PASSABLE_F(map, x, y + 1) ||
RL_PASSABLE_F(map, x, y - 1) ||
RL_PASSABLE_F(map, x + 1, y) ||
RL_PASSABLE_F(map, x - 1, y) ||
RL_PASSABLE_F(map, x + 1, y - 1) ||
RL_PASSABLE_F(map, x - 1, y - 1) ||
RL_PASSABLE_F(map, x + 1, y + 1) ||
RL_PASSABLE_F(map, x - 1, y + 1);
}
return 0;
}
static bool rl_map_wall_connects_ew(const RL_Map *map, unsigned int x, unsigned int y)
{
return (rl_map_in_bounds(map, x, y - 1) && !RL_IS_WALL_TILE(map->tiles[x+(y-1)*map->width], x, y - 1)) ||
(rl_map_in_bounds(map, x, y + 1) && !RL_IS_WALL_TILE(map->tiles[x+(y+1)*map->width], x, y + 1));
}
static bool rl_map_wall_connects_ns(const RL_Map *map, unsigned int x, unsigned int y)
{
return (rl_map_in_bounds(map, x - 1, y) && !RL_IS_WALL_TILE(map->tiles[(x-1)+y*map->width], x - 1, y)) ||
(rl_map_in_bounds(map, x + 1, y) && !RL_IS_WALL_TILE(map->tiles[(x+1)+y*map->width], x + 1, y));
}
/* checks if target tile is connecting from source (e.g. they can reach it) */
RL_Byte rl_map_wall(const RL_Map *map, unsigned int x, unsigned int y)
{
RL_Byte mask = 0;
if (!RL_WALL_F(map, x, y))
return mask;
if ((rl_map_in_bounds(map, x + 1, y ) && RL_IS_WALL_TILE(map->tiles[(x+1)+y*map->width], x + 1, y )) && (rl_map_wall_connects_ew(map, x, y ) || rl_map_wall_connects_ew(map, x + 1, y)))
mask |= RL_WallToEast;
if ((rl_map_in_bounds(map, x - 1, y ) && RL_IS_WALL_TILE(map->tiles[(x-1)+y*map->width], x - 1, y )) && (rl_map_wall_connects_ew(map, x, y ) || rl_map_wall_connects_ew(map, x - 1, y)))
mask |= RL_WallToWest;
if ((rl_map_in_bounds(map, x , y - 1) && RL_IS_WALL_TILE(map->tiles[x+(y-1)*map->width], x , y - 1)) && (rl_map_wall_connects_ns(map, x, y ) || rl_map_wall_connects_ns(map, x , y - 1)))
mask |= RL_WallToNorth;
if ((rl_map_in_bounds(map, x , y + 1) && RL_IS_WALL_TILE(map->tiles[x+(y+1)*map->width], x , y + 1)) && (rl_map_wall_connects_ns(map, x, y ) || rl_map_wall_connects_ns(map, x , y + 1)))
mask |= RL_WallToSouth;
return mask ? mask : RL_WallOther;
}
bool rl_map_is_corner_wall(const RL_Map *map, unsigned int x, unsigned int y)
{
int wall = rl_map_wall(map, x, y);
if (!wall) return 0;
return (wall & RL_WallToWest && wall & RL_WallToNorth) ||
(wall & RL_WallToWest && wall & RL_WallToSouth) ||
(wall & RL_WallToEast && wall & RL_WallToNorth) ||
(wall & RL_WallToEast && wall & RL_WallToSouth);
}
bool rl_map_tile_is(const RL_Map *map, unsigned int x, unsigned int y, RL_Byte tile)
{
if (!rl_map_in_bounds(map, x, y)) return 0;
return map->tiles[x + y*map->width] == tile;
}
bool rl_map_is_room_wall(const RL_Map *map, unsigned int x, unsigned int y)
{
if (!RL_WALL_F(map, x, y))
return 0;
return rl_map_tile_is(map, x, y + 1, RL_TileRoom) ||
rl_map_tile_is(map, x, y - 1, RL_TileRoom) ||
rl_map_tile_is(map, x + 1, y, RL_TileRoom) ||
rl_map_tile_is(map, x - 1, y, RL_TileRoom) ||
rl_map_tile_is(map, x + 1, y - 1, RL_TileRoom) ||
rl_map_tile_is(map, x - 1, y - 1, RL_TileRoom) ||
rl_map_tile_is(map, x + 1, y + 1, RL_TileRoom) ||
rl_map_tile_is(map, x - 1, y + 1, RL_TileRoom);
}
RL_Byte rl_map_room_wall(const RL_Map *map, unsigned int x, unsigned int y)
{
RL_Byte mask = 0;
if (!rl_map_is_room_wall(map, x, y))
return mask;
if (rl_map_is_room_wall(map, x + 1, y))
mask |= RL_WallToEast;
if (rl_map_is_room_wall(map, x - 1, y))
mask |= RL_WallToWest;
if (rl_map_is_room_wall(map, x, y - 1))
mask |= RL_WallToNorth;
if (rl_map_is_room_wall(map, x, y + 1))
mask |= RL_WallToSouth;
return mask ? mask : RL_WallOther;
}
unsigned int rl_rng_generate(unsigned int min, unsigned int max)
{
int rnd;
RL_ASSERT(max >= min);
RL_ASSERT(max < RAND_MAX);
RL_ASSERT(max < UINT_MAX);
if (max < min || max >= RAND_MAX || max >= UINT_MAX)
return min;
if (min == max)
return min;
rnd = rand();
if (rnd < 0) rnd *= -1; /* fixes issue on LLVM MOS */
/* produces more uniformity than using mod */
return min + rnd / (RAND_MAX / (max - min + 1) + 1);
}
RL_BSP *rl_bsp_create(unsigned int width, unsigned int height)
{
RL_BSP *bsp;
RL_ASSERT(width > 0 && height > 0);
bsp = (RL_BSP*) RL_CALLOC(1, sizeof(*bsp));
if (bsp == NULL) return NULL;
bsp->width = width;
bsp->height = height;
return bsp;
}
void rl_bsp_destroy(RL_BSP* root)
{
if (root) {
if (root->left) {
rl_bsp_destroy(root->left);
root->left = NULL;
}
if (root->right) {
rl_bsp_destroy(root->right);
root->right = NULL;
}
RL_FREE(root);
}
}
void rl_bsp_split(RL_BSP *node, unsigned int position, RL_SplitDirection direction)
{
RL_BSP *left, *right;
/* can't split something already split */
RL_ASSERT(node->left == NULL && node->right == NULL);
if (node->left || node->right)
return;
if (direction == RL_SplitVertically && position >= node->height)
return;
if (direction == RL_SplitHorizontally && position >= node->width)
return;
left = (RL_BSP*) RL_CALLOC(1, sizeof(RL_BSP));
if (left == NULL)
return;
right = (RL_BSP*) RL_CALLOC(1, sizeof(RL_BSP));
if (right == NULL) {
RL_FREE(left);
return;
}
if (direction == RL_SplitVertically) {
left->width = node->width;
left->height = position;
left->x = node->x;
left->y = node->y;
right->width = node->width;
right->height = node->height - position;
right->x = node->x;
right->y = node->y + position;
} else {
left->width = position;
left->height = node->height;
left->x = node->x;
left->y = node->y;
right->width = node->width - position;
right->height = node->height;
right->x = node->x + position;
right->y = node->y;
}
left->parent = right->parent = node;
node->left = left;
node->right = right;
}
RL_Status rl_bsp_recursive_split(RL_BSP *root, unsigned int min_width, unsigned int min_height, unsigned int max_recursion)
{
unsigned int width, height, split_position;
RL_SplitDirection dir;
RL_BSP *left, *right;
RL_Status ret;
RL_ASSERT(root);
RL_ASSERT(min_width > 0 && min_height > 0 && root != NULL);
RL_ASSERT(min_width <= root->width && min_height <= root->height);
if (root == NULL)
return RL_ErrorNullParameter;
if (max_recursion <= 0)
return RL_OK;
width = root->width;
height = root->height;
/* determine split dir & split */
if (RL_RNG_F(0, 1)) {
if (width < min_width*2)
dir = RL_SplitVertically;
else
dir = RL_SplitHorizontally;
} else {
if (height < min_height*2)
dir = RL_SplitHorizontally;
else
dir = RL_SplitVertically;
}
if (dir == RL_SplitHorizontally) {
/* cannot split if current node size is too small - end splitting */
if (width < min_width*2)
return RL_OK;
split_position = width / 2;
} else {
/* cannot split if current node size is too small - end splitting */
if (height < min_height*2)
return RL_OK;
split_position = height / 2;
}
rl_bsp_split(root, split_position, dir);
/* continue recursion */
left = root->left;
right = root->right;
if (left == NULL || right == NULL)
return RL_ErrorMemory;
ret = rl_bsp_recursive_split(left, min_width, min_height, max_recursion - 1);
if (ret != RL_OK) {
RL_FREE(left);
RL_FREE(right);
root->left = root->right = NULL;
return ret;
}
ret = rl_bsp_recursive_split(right, min_width, min_height, max_recursion - 1);
if (ret != RL_OK) {
RL_FREE(left);
RL_FREE(right);
root->left = root->right = NULL;
return ret;
}
return RL_OK;
}
bool rl_bsp_is_leaf(const RL_BSP *node)
{
if (node == NULL) return 0;
return (node->left == NULL && node->right == NULL);
}
RL_BSP *rl_bsp_sibling(const RL_BSP *node)
{
if (node && node->parent) {
if (node->parent->left == node)
return node->parent->right;
if (node->parent->right == node)
return node->parent->left;
RL_ASSERT("BSP structure is invalid" && 0); /* BSP structure is invalid */
}
return NULL;
}
RL_BSP *rl_bsp_next_node_recursive_down(RL_BSP *node, int depth)
{
if (node == NULL)
return NULL;
if (depth == 0) /* found the node */
return node;
if (node->left == NULL)
return NULL;
return rl_bsp_next_node_recursive_down(node->left, depth + 1);
}
RL_BSP *rl_bsp_next_node_recursive(RL_BSP *node, int depth)
{
if (node == NULL || node->parent == NULL)
return NULL;
if (node->parent->left == node) /* traverse back down */
return rl_bsp_next_node_recursive_down(node->parent->right, depth);
return rl_bsp_next_node_recursive(node->parent, depth - 1);
}
RL_BSP *rl_bsp_next_node(RL_BSP *node)
{
if (node == NULL || node->parent == NULL)
return NULL;
/* LOOP up until we are on the left, then go back down */
return rl_bsp_next_node_recursive(node, 0);
}
RL_BSP *rl_bsp_next_leaf_recursive_down(RL_BSP *node)
{
if (node == NULL)
return NULL;
if (rl_bsp_is_leaf(node)) /* found the node */
return node;
if (node->left == NULL)
return NULL;
return rl_bsp_next_leaf_recursive_down(node->left);
}
RL_BSP *rl_bsp_next_leaf_recursive(const RL_BSP *node)
{
if (node == NULL || node->parent == NULL)
return NULL;
if (node->parent->left == node) /* traverse back down */
return rl_bsp_next_leaf_recursive_down(node->parent->right);
return rl_bsp_next_leaf_recursive(node->parent);
}
RL_BSP *rl_bsp_next_leaf(const RL_BSP *node)
{
if (node == NULL || node->parent == NULL)
return NULL;
RL_ASSERT(rl_bsp_is_leaf(node));
/* LOOP up until we are on the left, then go back down */
return rl_bsp_next_leaf_recursive(node);
}
RL_BSP* rl_bsp_random_leaf(const RL_BSP *root)
{
const RL_BSP *node;
if (root == NULL)
return NULL;
node = root;
while (!rl_bsp_is_leaf(node)) {
if (RL_RNG_F(0, 1)) {
node = node->left;
} else {
node = node->right;
}
}
return (RL_BSP*) node;
}
size_t rl_bsp_leaf_count(const RL_BSP *root)
{
int count;
const RL_BSP *node;
if (root == NULL) return 0;
RL_ASSERT(root->parent == NULL);
/* find first leaf */
node = root;
while (node->left != NULL) {
node = node->left;
}
/* count leaves */
count = 1;
while ((node = rl_bsp_next_leaf(node)) != NULL) {
count++;
}
return count;
}
static void rl_map_bsp_generate_room(RL_Map *map, unsigned int room_width, unsigned int room_height, unsigned int room_x, unsigned int room_y)
{
unsigned int x, y;
RL_ASSERT(map && room_width + room_x <= map->width);
RL_ASSERT(map && room_height + room_y <= map->height);
if (map == NULL) return;
for (x = room_x; x < room_x + room_width; ++x) {
for (y = room_y; y < room_y + room_height; ++y) {
if (x == room_x || x == room_x + room_width - 1 ||
y == room_y || y == room_y + room_height - 1
) {
/* set sides of room to walls */
map->tiles[y*map->width + x] = RL_TileRock;
} else {
map->tiles[y*map->width + x] = RL_TileRoom;
}
}
}
}
static void rl_map_bsp_generate_rooms(RL_BSP *node, RL_Map *map, unsigned int room_min_width, unsigned int room_max_width, unsigned int room_min_height, unsigned int room_max_height, unsigned int room_padding)
{
RL_ASSERT(map);
RL_ASSERT(room_min_width < room_max_width);
RL_ASSERT(room_min_height < room_max_height);
RL_ASSERT(room_max_width + room_padding*2 < UINT_MAX);
RL_ASSERT(room_max_height + room_padding*2 < UINT_MAX);
RL_ASSERT(room_min_width > 2 && room_min_height > 2); /* width of 2 can end up having rooms made of nothing but walls */
RL_ASSERT(node && room_min_width < node->width);
RL_ASSERT(node && room_min_height < node->height);
RL_ASSERT(node && room_max_width <= node->width);
RL_ASSERT(node && room_max_height <= node->height);
if (map == NULL) return;
if (node && node->left) {
if (rl_bsp_is_leaf(node->left)) {
unsigned int room_width, room_height, room_x, room_y;
RL_BSP *leaf = node->left;
room_width = RL_RNG_F(room_min_width, room_max_width);
if (room_width + room_padding*2 > leaf->width)
room_width = leaf->width - room_padding*2;
room_height = RL_RNG_F(room_min_height, room_max_height);
if (room_height + room_padding*2 > leaf->height)
room_height = leaf->height - room_padding*2;
#if(RL_MAPGEN_BSP_RANDOMISE_ROOM_LOC)
room_x = RL_RNG_F(leaf->x + room_padding, leaf->x + leaf->width - room_width - room_padding);
room_y = RL_RNG_F(leaf->y + room_padding, leaf->y + leaf->height - room_height - room_padding);
#else
room_x = leaf->x + leaf->width/2 - room_width/2 - room_padding/2;
room_y = leaf->y + leaf->height/2 - room_height/2 - room_padding/2;
#endif
rl_map_bsp_generate_room(map, room_width, room_height, room_x, room_y);
} else {
rl_map_bsp_generate_rooms(node->left, map, room_min_width, room_max_width, room_min_height, room_max_height, room_padding);
}
}
if (node && node->right) {
if (rl_bsp_is_leaf(node->left)) {
unsigned int room_width, room_height, room_x, room_y;
RL_BSP *leaf = node->right;
room_width = RL_RNG_F(room_min_width, room_max_width);
if (room_width + room_padding*2 > leaf->width)
room_width = leaf->width - room_padding*2;
room_height = RL_RNG_F(room_min_height, room_max_height);
if (room_height + room_padding*2 > leaf->height)
room_height = leaf->height - room_padding*2;
#if(RL_MAPGEN_BSP_RANDOMISE_ROOM_LOC)
room_x = RL_RNG_F(leaf->x + room_padding, leaf->x + leaf->width - room_width - room_padding);
room_y = RL_RNG_F(leaf->y + room_padding, leaf->y + leaf->height - room_height - room_padding);
#else
room_x = leaf->x + leaf->width/2 - room_width/2 - room_padding/2;
room_y = leaf->y + leaf->height/2 - room_height/2 - room_padding/2;
#endif
rl_map_bsp_generate_room(map, room_width, room_height, room_x, room_y);
} else {
rl_map_bsp_generate_rooms(node->right, map, room_min_width, room_max_width, room_min_height, room_max_height, room_padding);
}
}
}
RL_Status rl_mapgen_bsp(RL_Map *map, RL_MapgenConfigBSP config)
{
RL_Status ret;
RL_BSP *bsp;
RL_ASSERT(map);
if (map == NULL) return RL_ErrorMemory;
bsp = rl_bsp_create(map->width, map->height);
RL_ASSERT(bsp);
if (bsp == NULL) return RL_ErrorMemory;
memset(map->tiles, RL_TileRock, sizeof(*map->tiles)*map->width*map->height);
ret = rl_mapgen_bsp_ex(map, bsp, &config);
rl_bsp_destroy(bsp);
return ret;
}
RL_Status rl_mapgen_bsp_ex(RL_Map *map, RL_BSP *root, const RL_MapgenConfigBSP *config)
{
RL_Status ret;
RL_ASSERT(map);
RL_ASSERT(root);
RL_ASSERT(root->width > 0 && root->height > 0);
RL_ASSERT(root->x < root->width && root->y < root->height);
RL_ASSERT(config);
RL_ASSERT(config->room_min_width > 0 && config->room_max_width >= config->room_min_width && config->room_min_height > 0 && config->room_max_height >= config->room_min_height);
RL_ASSERT(config->room_max_width <= map->width && config->room_max_height <= map->height);
RL_ASSERT(config->max_splits > 0);
if (map == NULL || root == NULL || config == NULL) {
return RL_ErrorNullParameter;
}
ret = rl_bsp_recursive_split(root, config->room_max_width + config->room_padding, config->room_max_height + config->room_padding, config->max_splits);
if (ret != RL_OK) return ret;
rl_map_bsp_generate_rooms(root, map, config->room_min_width, config->room_max_width, config->room_min_height, config->room_max_height, config->room_padding);
ret = rl_mapgen_connect_corridors(map, root, config->draw_doors, config->draw_corridors);
if (ret != RL_OK) return ret;
/* if (config->use_secret_passages) { */
/* TODO connect secret passages */
/* } */
return RL_OK;
}
/* find the room tile within BSP */
void rl_bsp_find_room(RL_Map *map, RL_BSP *leaf, unsigned int *dx, unsigned int *dy)
{
unsigned int x, y;
unsigned int start_x, start_y, end_x, end_y;
bool found_start = false;
RL_ASSERT(dx && dy);
RL_ASSERT(map);
RL_ASSERT(leaf);
for (x = leaf->x; x < leaf->width + leaf->x; ++x) {
for (y = leaf->y; y < leaf->height + leaf->y; ++y) {
if (!found_start) {
if (rl_map_tile_is(map, x, y, RL_TileRoom)) {
start_x = x;
start_y = y;
end_x = x;
end_y = y;
found_start = true;
}
} else {
if (rl_map_tile_is(map, x, y, RL_TileRoom)) {
end_x = x;
end_y = y;
} else {
/* found end - return middle of room */
int diff_x = end_x - start_x;
int diff_y = end_y - start_y;
RL_ASSERT(diff_x >= 0 && diff_y >= 0);
*dx = start_x + diff_x/2;
*dy = start_y + diff_y/2;
}
}
}
}
}
bool rl_mapgen_automata_is_alive(const RL_Map *map, int x, int y)
{
if (!rl_map_in_bounds(map, x, y)) return true;
return rl_map_tile_is(map, x, y, RL_TileRock);
}
unsigned int rl_mapgen_automata_alive_neighbors(const RL_Map *map, int x, int y)
{
return rl_mapgen_automata_is_alive(map, x + 1, y) +
rl_mapgen_automata_is_alive(map, x - 1, y) +
rl_mapgen_automata_is_alive(map, x, y + 1) +
rl_mapgen_automata_is_alive(map, x, y - 1) +
rl_mapgen_automata_is_alive(map, x + 1, y + 1) +
rl_mapgen_automata_is_alive(map, x - 1, y + 1) +
rl_mapgen_automata_is_alive(map, x + 1, y - 1) +
rl_mapgen_automata_is_alive(map, x - 1, y - 1);
}
RL_Status rl_mapgen_automata(RL_Map *map, RL_MapgenConfigAutomata config)
{
return rl_mapgen_automata_ex(map, 0, 0, map->width, map->height, &config);
}
#if RL_ENABLE_PATHFINDING
static inline float rl_mapgen_corridor_scorer(const RL_GraphNode *current, const RL_GraphNode *neighbor, void *context);
#endif
RL_Status rl_mapgen_automata_ex(RL_Map *map, unsigned int offset_x, unsigned int offset_y, unsigned int width, unsigned int height, const RL_MapgenConfigAutomata *config)
{
unsigned int i, x, y;
RL_ASSERT(map && config);
RL_ASSERT(width > 0 && height > 0);
RL_ASSERT(offset_x < width && offset_y < height);
RL_ASSERT(offset_x < map->width && offset_y < map->height);
RL_ASSERT(offset_x + width <= map->width && offset_y + height <= map->height);
RL_ASSERT(config->chance_cell_initialized > 0 && config->chance_cell_initialized <= 100);
if (map == NULL || config == NULL) {
return RL_ErrorNullParameter;
}
/* initialize map */
for (x=offset_x; x<offset_x + width; ++x) {
for (y=offset_y; y<offset_y + height; ++y) {
unsigned int r = RL_RNG_F(1, 100);
if (r <= config->chance_cell_initialized) {
map->tiles[x + y*map->width] = RL_TileRock;
} else {
map->tiles[x + y*map->width] = RL_TileRoom;
}
}
}
/* cellular automata algorithm */
for (i=config->max_iterations; i>0; i--) {
for (x=offset_x; x<offset_x + width; ++x) {
for (y=offset_y; y<offset_y + height; ++y) {
unsigned int alive_neighbors = rl_mapgen_automata_alive_neighbors(map, x, y);
if (!rl_mapgen_automata_is_alive(map, x, y) && alive_neighbors >= config->birth_threshold) {
/* cell isn't alive but has enough alive neighbors to be born */
map->tiles[x + y*map->width] = RL_TileRock;
} else if (rl_mapgen_automata_is_alive(map, x, y) && alive_neighbors >= config->survival_threshold) {
/* cell is alive and has enough alive neighbors to survive */
} else {
/* cell dies */
map->tiles[x + y*map->width] = RL_TileRoom;
}
}
}
}
if (config->draw_corridors) {
#if RL_ENABLE_PATHFINDING
/* A very crude algorithm for connecting corridors within the cellular automata. This creates a heap of Dijkstra
* graphs, containing each floodfilled region of the map. Then, it goes through each of these regions and
* connects it to another random region. This is pretty slow and can be optimized in the future, but works for
* now. */
RL_Heap *heap = rl_heap_create(1, NULL);
/* fill floodfills array with a floodfill of each connected space */
for (x=offset_x; x<offset_x + width; ++x) {
for (y=offset_y; y<offset_y + height; ++y) {
int i;
bool is_scored = false;
if (RL_PASSABLE_F(map, x, y)) {
for (i=0; i<heap->len; ++i) {
RL_Graph *floodfill = (RL_Graph*) heap->heap[i];
if (rl_graph_is_scored(floodfill, RL_XY(x, y))) {
is_scored = true;
break;
}
}
if (!is_scored) {
RL_Graph *floodfill = rl_dijkstra_create(map, RL_XY(x, y), NULL);
rl_heap_insert(heap, floodfill);
}
}
}
}
/* connect each floodfill with another random one */
if (heap->len > 1) {
int i;
RL_Graph *graph = rl_graph_create_ex(map, map, NULL, 0);
RL_ASSERT(graph);
for (i=0; i<heap->len; ++i) {
RL_Graph *floodfill_target;
RL_Graph *floodfill;
RL_Point dig_start, dig_end;
size_t node_idx;
int j = i;
floodfill = (RL_Graph*) heap->heap[i];
/* find a random target node to connect to */
while (j == i) {
j = RL_RNG_F(0, heap->len - 1);
}
floodfill_target = (RL_Graph*) heap->heap[j];
RL_ASSERT(floodfill && floodfill_target);
/* find start & end point for corridor pathfinding */
for (node_idx=0; node_idx<floodfill->length; ++node_idx) {
RL_GraphNode *n = &floodfill->nodes[node_idx];
RL_ASSERT(n);
if (n->score < FLT_MAX && RL_PASSABLE_F(map, n->point.x, n->point.y)) {
dig_start = n->point;
break;
}
}
RL_ASSERT(RL_PASSABLE_F(map, dig_start.x, dig_start.y));
for (node_idx=0; node_idx<floodfill_target->length; ++node_idx) {
RL_GraphNode *n = &floodfill_target->nodes[node_idx];
RL_ASSERT(n);
if (n->score < FLT_MAX && RL_PASSABLE_F(map, n->point.x, n->point.y)) {
dig_end = n->point;
break;
}
}
RL_ASSERT(RL_PASSABLE_F(map, dig_end.x, dig_end.y));
RL_ASSERT(!(dig_start.x == dig_end.x && dig_start.y == dig_end.y));
/* carve out corridors */
rl_dijkstra_score_ex(graph, dig_end, rl_mapgen_corridor_scorer, map);
RL_Path *path = rl_path_create_from_graph(graph, dig_start);
RL_ASSERT(path);
while ((path = rl_path_walk(path))) {
if (rl_map_tile_is(map, path->point.x, path->point.y, RL_TileRock)) {
map->tiles[(size_t)floor(path->point.x) + (size_t)floor(path->point.y) * map->width] = RL_TileCorridor;
}
}
}
rl_graph_destroy(graph);
}
/* cleanup */
RL_Graph *floodfill = NULL;
while ((floodfill = (RL_Graph*) rl_heap_pop(heap))) {
rl_graph_destroy(floodfill);
}
rl_heap_destroy(heap);
#else
return RL_ErrorMapgenInvalidConfig;
#endif
}
if (config->fill_border) {
x = 0;
for (y=offset_y; y<height; ++y) map->tiles[x + y*map->width] = RL_TileRock;
x = width - 1;
for (y=offset_y; y<height; ++y) map->tiles[x + y*map->width] = RL_TileRock;
y = 0;
for (x=offset_x; x<width; ++x) map->tiles[x + y*map->width] = RL_TileRock;
y = height - 1;
for (x=offset_x; x<width; ++x) map->tiles[x + y*map->width] = RL_TileRock;
}
if (config->cull_unconnected) {
#if RL_ENABLE_PATHFINDING
RL_Graph *floodfill = rl_graph_floodfill_largest_area(map);
if (floodfill) {
for (x=offset_x; x<offset_x + width; ++x) {
for (y=offset_y; y<offset_y + height; ++y) {
if (!rl_graph_is_scored(floodfill, RL_XY(x, y))) {
map->tiles[x + y*map->width] = RL_TileRock;
}
}
}
rl_graph_destroy(floodfill);
}
#else
return RL_ErrorMapgenInvalidConfig;
#endif
}
return RL_OK;
}
typedef struct {
int x, y;
} RL_MapPoint;
int rl_mapgen_maze_unvisited_neighbors(RL_MapPoint ps[4], const RL_Map *map, int x, int y, int sx, int mx, int sy, int my)
{
RL_MapPoint neighbors[4];
int i, count = 0;
neighbors[0].x = x - 2;
neighbors[0].y = y;
neighbors[1].x = x + 2;
neighbors[1].y = y;
neighbors[2].x = x;
neighbors[2].y = y - 2;
neighbors[3].x = x;
neighbors[3].y = y + 2;
for (i = 0; i<4; ++i) {
int x = neighbors[i].x;
int y = neighbors[i].y;
if (x < sx || x >= mx || y < sy || y >= my) continue;
if (map->tiles[x + y*map->width] == RL_TileRock) {
/* matching neighbor */
ps[count].x = x;
ps[count].y = y;
count ++;
}
}
return count;
}
RL_Status rl_mapgen_maze(RL_Map *map)
{
RL_ASSERT(map);
if (map == NULL) return RL_ErrorNullParameter;
RL_ASSERT(map->width > 2 && map->height > 2);
memset(map->tiles, RL_TileRock, sizeof(*map->tiles) * map->width * map->height);
return rl_mapgen_maze_ex(map, 1, 1, map->width - 2, map->height - 2);
}
RL_Status rl_mapgen_maze_ex(RL_Map *map, unsigned int offset_x, unsigned int offset_y, unsigned int width, unsigned int height)
{
int x, y;
RL_MapPoint *ps;
RL_MapPoint *p;
RL_Heap *heap;
RL_ASSERT(map);
RL_ASSERT(width > 0 && height > 0);
RL_ASSERT(offset_x < width && offset_y < height);
RL_ASSERT(offset_x < map->width && offset_y < map->height);
RL_ASSERT(offset_x + width <= map->width && offset_y + height <= map->height);
RL_ASSERT(offset_x + width < INT_MAX);
RL_ASSERT(offset_y + height < INT_MAX);
if (map == NULL) {
return RL_ErrorNullParameter;
}
/* reset all tiles within range to rock */
for (x = (int)offset_x; x < (int)offset_x + (int)width; ++x) {
for (y = (int)offset_y; y < (int)offset_y + (int)height; ++y) {
map->tiles[x + y*map->width] = RL_TileRock;
}
}
/* allocate memory for BFS */
heap = rl_heap_create(width * height, NULL);
ps = (RL_MapPoint*) RL_MALLOC(sizeof(*ps) * map->width * map->height);
RL_ASSERT(ps && heap);
if (ps == NULL || heap == NULL) {
return RL_ErrorMemory;
}
/* choose random starting tile */
x = RL_RNG_F(offset_x, offset_x + width - 1);
y = RL_RNG_F(offset_y, offset_y + height - 1);
map->tiles[x + y*map->width] = RL_TileCorridor;
p = &ps[x + y*map->width];
p->x = x;
p->y = y;
rl_heap_insert(heap, p);
while ((p = (RL_MapPoint*) rl_heap_pop(heap)) != NULL) {
/* check unvisited neighbors (+2 so we have enough space for walls) */
RL_MapPoint neighbors[4];
RL_MapPoint *p2;
int wall_x, wall_y, i;
int neighbors_count = rl_mapgen_maze_unvisited_neighbors(neighbors, map, p->x, p->y, offset_x, offset_x + width, offset_y, offset_y + height);
if (neighbors_count == 0) continue;
/* choose one unvisitied neighbor */
i = RL_RNG_F(0, neighbors_count - 1);
x = neighbors[i].x;
y = neighbors[i].y;
RL_ASSERT(rl_map_in_bounds(map, x, y));
RL_ASSERT(map->tiles[x + y*map->width] == RL_TileRock);
/* unvisited neighbor - remove wall and push to heap */
wall_x = x;
wall_y = y;
if (x < p->x) wall_x = x + 1;
if (x > p->x) wall_x = x - 1;
if (y < p->y) wall_y = y + 1;
if (y > p->y) wall_y = y - 1;
map->tiles[wall_x + wall_y*map->width] = RL_TileCorridor;
map->tiles[x + y*map->width] = RL_TileCorridor;
p2 = &ps[x + y*map->width];
p2->x = x;
p2->y = y;
rl_heap_insert(heap, p);
rl_heap_insert(heap, p2);
}
/* free memory for BFS */
rl_heap_destroy(heap);
RL_FREE(ps);
return RL_OK;
}
/* custom corridor connection to most efficiently connect leaves of the BSP tree */
void rl_mapgen_connect_corridors_simple(RL_Map *map, RL_BSP *root, bool draw_doors)
{
/* unsigned int dig_start_x, dig_start_y, dig_end_x, dig_end_y, cur_x, cur_y; */
unsigned int dig_start_x, dig_start_y, dig_end_x, dig_end_y, cur_x, cur_y;
int direction, diff_y, diff_x;
RL_BSP *node, *sibling, *left, *right;
RL_ASSERT(map && root);
if (!map || !root) return;
/* connect siblings */
node = root->left;
sibling = root->right;
if (node == NULL || sibling == NULL) return;
/* find rooms in BSP */
left = rl_bsp_random_leaf(node);
right = rl_bsp_random_leaf(sibling);
#if RL_MAPGEN_BSP_RANDOMISE_ROOM_LOC
rl_bsp_find_room(map, left, &dig_start_x, &dig_start_y);
#else
dig_start_x = left->x + left->width / 2;
dig_start_y = left->y + left->height / 2;
#endif
#if RL_MAPGEN_BSP_RANDOMISE_ROOM_LOC
rl_bsp_find_room(map, right, &dig_end_x, &dig_end_y);
#else
dig_end_x = right->x + right->width / 2;
dig_end_y = right->y + right->height / 2;
#endif
RL_ASSERT(RL_PASSABLE_F(map, dig_start_x, dig_start_y));
RL_ASSERT(RL_PASSABLE_F(map, dig_end_x, dig_end_y));
RL_ASSERT(!(dig_start_x == dig_end_x && dig_start_y == dig_end_y));
/* carve out corridors */
cur_x = dig_start_x;
cur_y = dig_start_y;
direction = 0;
diff_y = cur_y - dig_end_y;
if (diff_y < 0) diff_y *= -1;
diff_x = cur_x - dig_end_x;
if (diff_x < 0) diff_x *= -1;
if (diff_y > diff_x) {
direction = 1;
}
while (cur_x != dig_end_x || cur_y != dig_end_y) {
/* prevent digging float wide corridors */
unsigned int next_x, next_y; next_x = cur_x;
next_y = cur_y;
if (direction == 0) { /* digging left<->right */
if (cur_x == dig_end_x) {
direction = !direction;
} else {
next_x += dig_end_x < cur_x ? -1 : 1;
}
}
if (direction == 1) { /* digging up<->down */
if (cur_y == dig_end_y) {
direction = !direction;
} else {
next_y += dig_end_y < cur_y ? -1 : 1;
}
}
/* dig */
if (map->tiles[cur_x + cur_y*map->width] == RL_TileRock) {
if (draw_doors && rl_map_is_room_wall(map, cur_x, cur_y))
map->tiles[cur_x + cur_y*map->width] = RL_TileDoor;
else
map->tiles[cur_x + cur_y*map->width] = RL_TileCorridor;
}
cur_x = next_x;
cur_y = next_y;
}
/* connect siblings' children */
rl_mapgen_connect_corridors_simple(map, node, draw_doors);
rl_mapgen_connect_corridors_simple(map, sibling, draw_doors);
}
void rl_mapgen_connect_corridors_bsp(RL_Map *map, RL_BSP *root, bool draw_doors);
void rl_mapgen_connect_corridors_randomly(RL_Map *map, RL_BSP *root, bool draw_doors);
RL_Status rl_mapgen_connect_corridors(RL_Map *map, RL_BSP *root, bool draw_doors, RL_MapgenCorridorConnection connection_algorithm)
{
switch (connection_algorithm) {
case RL_ConnectNone:
RL_UNUSED(map);
RL_UNUSED(root);
RL_UNUSED(draw_doors);
break;
case RL_ConnectSimple:
rl_mapgen_connect_corridors_simple(map, root, draw_doors);
break;
case RL_ConnectRandomly:
#if RL_ENABLE_PATHFINDING
rl_mapgen_connect_corridors_randomly(map, root, draw_doors);
{
/* cull non-connected tiles */
RL_Graph *floodfill = rl_graph_floodfill_largest_area(map);
RL_ASSERT(floodfill);
if (floodfill) {
for (size_t x=0; x < map->width; ++x) {
for (size_t y=0; y < map->height; ++y) {
if (floodfill->nodes[x + y*map->width].score == FLT_MAX) {
/* set unreachable tiles to rock */
map->tiles[x + y*map->width] = RL_TileRock;
}
}
}
rl_graph_destroy(floodfill);
}
}
break;
#endif
case RL_ConnectBSP:
#if RL_ENABLE_PATHFINDING
rl_mapgen_connect_corridors_bsp(map, root, draw_doors);
break;
#endif
default:
return RL_ErrorMapgenInvalidConfig;
}
return RL_OK;
}
/**
* Heap functions for pathfinding
*
* Ref: https://gist.github.com/skeeto/f012a207aff1753662b679917f706de6
*/
static int rl_heap_noop_comparison_f(const void *_a, const void *_b)
{
RL_UNUSED(_a);
RL_UNUSED(_b);
return 1;
}
RL_Heap *rl_heap_create(int capacity, int (*comparison_f)(const void *heap_item_a, const void *heap_item_b))
{
RL_Heap *heap;
heap = (RL_Heap*) RL_MALLOC(sizeof(*heap));
RL_ASSERT(heap);
RL_ASSERT(capacity > 0);
if (heap == NULL) {
return NULL;
}
heap->heap = (void**) RL_MALLOC(sizeof(*heap->heap) * capacity);
RL_ASSERT(heap->heap);
if (heap->heap == NULL) {
RL_FREE(heap);
return NULL;
}
if (comparison_f == NULL) {
comparison_f = rl_heap_noop_comparison_f;
}
heap->cap = capacity;
heap->comparison_f = comparison_f;
heap->len = 0;
return heap;
}
void rl_heap_destroy(RL_Heap *h)
{
if (h) {
if (h->heap) {
RL_FREE(h->heap);
}
RL_FREE(h);
}
}
int rl_heap_length(const RL_Heap *h)
{
if (h == NULL) return 0;
return h->len;
}
bool rl_heap_insert(RL_Heap *h, void *item)
{
int i;
RL_ASSERT(h != NULL);
if (h == NULL) return false;
if (h->len == h->cap) {
/* resize the heap */
void **heap_items = (void**) RL_REALLOC(h->heap, sizeof(void*) * h->cap * 2);
RL_ASSERT(heap_items);
if (heap_items == NULL) {
rl_heap_destroy(h);
return false;
}
h->heap = heap_items;
h->cap *= 2;
}
h->heap[h->len] = item;
for (i = h->len++; i;) {
void *tmp;
int p = (i - 1) / 2;
if (h->comparison_f(h->heap[p], h->heap[i])) {
break;
}
tmp = h->heap[p];
h->heap[p] = h->heap[i];
h->heap[i] = tmp;
i = p;
}
return true;
}
static void rl_heap_remove(RL_Heap *h, int index)
{
int i;
RL_ASSERT(h);
if (h == NULL) {
return;
}
h->heap[index] = h->heap[--h->len];
for (i = index;;) {
int a = 2*i + 1;
int b = 2*i + 2;
int j = i;
void *tmp;
if (a < h->len && h->comparison_f(h->heap[a], h->heap[j])) j = a;
if (b < h->len && h->comparison_f(h->heap[b], h->heap[j])) j = b;
if (i == j) break;
tmp = h->heap[j];
h->heap[j] = h->heap[i];
h->heap[i] = tmp;
i = j;
}
}
void *rl_heap_pop(RL_Heap *h)
{
void *r;
if (h == NULL) {
return NULL;
}
r = NULL;
if (h->len) {
RL_ASSERT(h->heap);
r = h->heap[0];
rl_heap_remove(h, 0);
}
return r;
}
void *rl_heap_peek(RL_Heap *h)
{
if (h == NULL) {
return NULL;
}
RL_ASSERT(h->heap);
if (h->len) {
return h->heap[0];
} else {
return NULL;
}
}
#if RL_ENABLE_PATHFINDING
/* simplified distance for side by side nodes */
static float rl_distance_simple(RL_Point node, RL_Point end)
{
if (node.x == end.x && node.y == end.y) return 0;
if (node.x == end.x || node.y == end.y) return 1;
return 1.4;
}
static int rl_scored_graph_heap_comparison(const void *heap_item_a, const void *heap_item_b)
{
RL_GraphNode *node_a = (RL_GraphNode*) heap_item_a;
RL_GraphNode *node_b = (RL_GraphNode*) heap_item_b;
return node_a->score < node_b->score;
}
RL_Path *rl_path(RL_Point p)
{
RL_Path *path = (RL_Path*) RL_MALLOC(sizeof(*path));
RL_ASSERT(path);
if (path == NULL) return NULL;
path->next = NULL;
path->point = p;
return path;
}
float rl_distance_manhattan(RL_Point node, RL_Point end)
{
return fabs(node.x - end.x) + fabs(node.y - end.y);
}
float rl_distance_euclidian(RL_Point node, RL_Point end)
{
float distance_x = node.x - end.x;
float distance_y = node.y - end.y;
return sqrt(distance_x * distance_x + distance_y * distance_y);
}
float rl_distance_chebyshev(RL_Point node, RL_Point end)
{
float distance_x = fabs(node.x - end.x);
float distance_y = fabs(node.y - end.y);
return distance_x > distance_y ? distance_x : distance_y;
}
/* custom Dijkstra scorer function to prevent carving double wide doors when carving corridors */
static inline float rl_mapgen_corridor_scorer(const RL_GraphNode *current, const RL_GraphNode *neighbor, void *context)
{
RL_Map *map = (RL_Map*) context;
RL_Point start = current->point;
RL_Point end = neighbor->point;
float r = current->score + rl_distance_manhattan(start, end);
if (rl_map_tile_is(map, end.x, end.y, RL_TileDoor)) {
return r; /* doors are passable but count as "walls" - encourage passing through them */
}
if (rl_map_is_corner_wall(map, end.x, end.y)) {
return r + 99; /* discourage double wide corridors & double carving into walls */
}
if (RL_WALL_F(map, end.x, end.y)) {
return r + 9; /* discourage double wide corridors & double carving into walls */
}
return r;
}
void rl_mapgen_connect_corridors_bsp_recursive(RL_Map *map, RL_BSP *root, bool draw_doors, RL_Graph *graph)
{
RL_ASSERT(map && root && graph);
if (map == NULL || root == NULL || graph == NULL) return;
/* connect siblings */
RL_BSP *node = root->left;
RL_BSP *sibling = root->right;
if (node == NULL || sibling == NULL) return;
/* find rooms in BSP */
unsigned int x, y;
RL_BSP *leaf = rl_bsp_random_leaf(node);
rl_bsp_find_room(map, leaf, &x, &y);
RL_Point dig_start = {x, y};
RL_ASSERT(RL_PASSABLE_F(map, dig_start.x, dig_start.y));
leaf = rl_bsp_random_leaf(sibling);
rl_bsp_find_room(map, leaf, &x, &y);
RL_Point dig_end = {x, y};
RL_ASSERT(RL_PASSABLE_F(map, dig_end.x, dig_end.y));
RL_ASSERT(!(dig_start.x == dig_end.x && dig_start.y == dig_end.y));
/* carve out corridors */
rl_dijkstra_score_ex(graph, dig_end, rl_mapgen_corridor_scorer, map);
RL_Path *path = rl_path_create_from_graph(graph, dig_start);
RL_ASSERT(path);
while ((path = rl_path_walk(path))) {
if (rl_map_tile_is(map, path->point.x, path->point.y, RL_TileRock)) {
if (rl_map_is_room_wall(map, path->point.x, path->point.y) && draw_doors) {
map->tiles[(size_t)floor(path->point.x) + (size_t)floor(path->point.y) * map->width] = RL_TileDoor;
} else {
map->tiles[(size_t)floor(path->point.x) + (size_t)floor(path->point.y) * map->width] = RL_TileCorridor;
}
}
}
/* connect siblings' children */
rl_mapgen_connect_corridors_bsp_recursive(map, node, draw_doors, graph);
rl_mapgen_connect_corridors_bsp_recursive(map, sibling, draw_doors, graph);
}
void rl_mapgen_connect_corridors_bsp(RL_Map *map, RL_BSP *root, bool draw_doors)
{
RL_Graph *graph = rl_graph_create_ex(map, map, NULL, 0);
RL_ASSERT(graph);
if (graph) {
rl_mapgen_connect_corridors_bsp_recursive(map, root, draw_doors, graph);
rl_graph_destroy(graph);
}
}
void rl_mapgen_connect_corridors_randomly(RL_Map *map, RL_BSP *root, bool draw_doors)
{
RL_ASSERT(map && root);
if (!map || !root) return;
/* find deepest left-most node */
RL_BSP *leftmost_node = root;
while (leftmost_node->left != NULL) {
leftmost_node = leftmost_node->left;
}
RL_ASSERT(leftmost_node && rl_bsp_is_leaf(leftmost_node));
RL_BSP *node = leftmost_node;
RL_Graph *graph = rl_graph_create_ex(map, map, NULL, 0);
RL_ASSERT(graph);
if (graph == NULL) return;
while (node) {
RL_BSP *sibling;
/* find random sibling */
while ((sibling = rl_bsp_random_leaf(root)) == node) {}
RL_ASSERT(sibling);
/* TODO need to change this to find the *actual* room (e.g. what if the user provides a map filled with "."?) */
unsigned int x, y;
rl_bsp_find_room(map, node, &x, &y);
RL_ASSERT(RL_PASSABLE_F(map, x, y));
RL_Point dig_start = {x, y};
RL_ASSERT(RL_PASSABLE_F(map, dig_start.x, dig_start.y));
rl_bsp_find_room(map, sibling, &x, &y);
RL_ASSERT(RL_PASSABLE_F(map, x, y));
RL_Point dig_end = {x, y};
RL_ASSERT(RL_PASSABLE_F(map, dig_end.x, dig_end.y));
RL_ASSERT(!(dig_start.x == dig_end.x && dig_start.y == dig_end.y));
/* carve out corridors */
rl_dijkstra_score_ex(graph, dig_end, rl_mapgen_corridor_scorer, map);
RL_Path *path = rl_path_create_from_graph(graph, dig_start);
RL_ASSERT(path);
while ((path = rl_path_walk(path))) {
if (rl_map_tile_is(map, path->point.x, path->point.y, RL_TileRock)) {
if (rl_map_is_room_wall(map, path->point.x, path->point.y) && draw_doors) {
map->tiles[(size_t)floor(path->point.x) + (size_t)floor(path->point.y) * map->width] = RL_TileDoor;
} else {
map->tiles[(size_t)floor(path->point.x) + (size_t)floor(path->point.y) * map->width] = RL_TileCorridor;
}
}
}
/* find start node for next loop iteration */
node = rl_bsp_next_leaf(node);
}
rl_graph_destroy(graph);
}
RL_Graph *rl_graph_floodfill_largest_area(const RL_Map *map)
{
RL_ASSERT(map);
if (map == NULL) return NULL;
int *visited = (int*) RL_CALLOC(map->width * map->height, sizeof(*visited));
RL_ASSERT(visited);
if (visited == NULL) return NULL;
RL_Graph *floodfill = NULL; /* largest floodfill */
int floodfill_scored = 0;
for (unsigned int x = 0; x < map->width; ++x) {
for (unsigned int y = 0; y < map->height; ++y) {
if (RL_PASSABLE_F(map, x, y) && !visited[x + y*map->width]) {
RL_Graph *test = rl_dijkstra_create(map, RL_XY(x, y), NULL);
RL_ASSERT(test);
if (test == NULL) {
RL_FREE(visited);
if (floodfill) {
rl_graph_destroy(floodfill);
}
return NULL;
}
int test_scored = 0;
for (size_t i = 0; i < test->length; i++) {
if (test->nodes[i].score != FLT_MAX) {
visited[i] = 1;
test_scored ++;
}
}
if (test_scored > floodfill_scored) {
floodfill_scored = test_scored;
if (floodfill) {
rl_graph_destroy(floodfill);
}
floodfill = test;
} else {
rl_graph_destroy(test);
}
}
}
}
RL_FREE(visited);
return floodfill;
}
RL_Path *rl_line_create(RL_Point a, RL_Point b, float step)
{
float delta_x = fabs(a.x - b.x);
float x_increment = b.x > a.x ? step : -step;
float delta_y = fabs(a.y - b.y);
float y_increment = b.y > a.y ? step : -step;
float error = 0.0;
float slope = delta_x ? delta_y / delta_x : 0.0;
RL_Path *head = rl_path(a);
if (head == NULL) return NULL;
RL_Path *path = head;
while (path->point.x != b.x || path->point.y != b.y) {
RL_Point point = path->point;
if (delta_x > delta_y) {
error += slope;
if (error > 0.5 && point.y != b.y) {
error -= 1.0;
point.y += y_increment;
}
point.x += x_increment;
} else {
error += 1/slope;
if (error > 0.5 && point.x != b.x) {
error -= 1.0;
point.x += x_increment;
}
point.y += y_increment;
}
/* add new member to linked list & advance */
path->next = rl_path(point);
path = path->next;
}
return head;
}
RL_Path *rl_path_create(const RL_Map *map, RL_Point start, RL_Point end, RL_DistanceFun distance_f)
{
RL_Graph *graph = rl_dijkstra_create(map, end, distance_f);
RL_ASSERT(graph);
if (graph == NULL) return NULL;
RL_Path *path = rl_path_create_from_graph(graph, start);
RL_ASSERT(path);
rl_graph_destroy(graph);
return path;
}
RL_Path *rl_path_create_from_graph(const RL_Graph *graph, RL_Point start)
{
RL_Path *path = rl_path(start);
RL_Path *path_start = path;
RL_GraphNode *node = NULL;
RL_ASSERT(path);
RL_ASSERT(graph && graph->nodes);
if (path == NULL || graph == NULL || graph->nodes == NULL) return NULL;
for (size_t i=0; i<graph->length; i++) {
if (graph->nodes[i].point.x == start.x && graph->nodes[i].point.y == start.y) {
node = &graph->nodes[i];
}
}
if (node == NULL) {
return path;
}
while (node != NULL && node->score > 0) {
node = rl_graph_node_lowest_neighbor(node);
if (node == NULL) break;
path->next = rl_path(node->point);
RL_ASSERT(path->next);
if (path->next == NULL) {
rl_path_destroy(path);
return NULL;
}
path = path->next;
}
return path_start;
}
RL_Path *rl_path_walk(RL_Path *path)
{
if (!path) return NULL;
RL_Path *next = path->next;
path->next = NULL;
RL_FREE(path);
return next;
}
void rl_path_destroy(RL_Path *path)
{
if (path) {
while ((path = rl_path_walk(path))) {}
}
}
bool rl_graph_default_passable_fun(void *context, unsigned int x, unsigned int y)
{
RL_Map *map = (RL_Map*) context;
return RL_PASSABLE_F(map, x, y);
}
RL_Graph *rl_graph_create(const RL_Map *map)
{
return rl_graph_create_ex(map, (void*) map, rl_graph_default_passable_fun, true);
}
RL_Graph *rl_graph_create_ex(const RL_Map *map, void *context, RL_PassableFun passable_f, bool allow_diagonal_neighbors)
{
RL_Graph *graph = (RL_Graph*) RL_MALLOC(sizeof(*graph));
RL_ASSERT(graph);
if (graph == NULL) return NULL;
size_t length = map->width * map->height;
RL_GraphNode *nodes = (RL_GraphNode*) RL_CALLOC(length, sizeof(*nodes));
RL_ASSERT(nodes != NULL);
if (nodes == NULL) {
RL_FREE(graph);
return NULL;
}
for (unsigned int x=0; x<map->width; x++) {
for (unsigned int y=0; y<map->height; y++) {
size_t idx = x + y*map->width;
RL_GraphNode *node = &nodes[idx];
node->point.x = (float) x;
node->point.y = (float) y;
node->neighbors_length = 0;
node->score = FLT_MAX;
/* calculate neighbors */
RL_Point neighbor_coords[8];
neighbor_coords[0].x = (int)x + 1;
neighbor_coords[0].y = (int)y;
neighbor_coords[1].x = (int)x - 1;
neighbor_coords[1].y = (int)y;
neighbor_coords[2].x = (int)x;
neighbor_coords[2].y = (int)y + 1;
neighbor_coords[3].x = (int)x;
neighbor_coords[3].y = (int)y - 1;
neighbor_coords[4].x = (int)x + 1;
neighbor_coords[4].y = (int)y + 1;
neighbor_coords[5].x = (int)x + 1;
neighbor_coords[5].y = (int)y - 1;
neighbor_coords[6].x = (int)x - 1;
neighbor_coords[6].y = (int)y + 1;
neighbor_coords[7].x = (int)x - 1;
neighbor_coords[7].y = (int)y - 1;
for (int i=0; i<8; i++) {
if (passable_f && !passable_f(context, neighbor_coords[i].x, neighbor_coords[i].y))
continue;
if (!rl_map_in_bounds(map, neighbor_coords[i].x, neighbor_coords[i].y))
continue;
if (!allow_diagonal_neighbors && i >= 4)
continue;
size_t idx = neighbor_coords[i].x + neighbor_coords[i].y*map->width;
node->neighbors[node->neighbors_length] = &nodes[idx];
node->neighbors_length++;
}
}
}
graph->length = length;
graph->nodes = nodes;
return graph;
}
void rl_graph_add(RL_Graph *graph, const RL_Graph *graph_b)
{
RL_ASSERT(graph != NULL);
RL_ASSERT(graph_b != NULL);
RL_ASSERT(graph->length == graph_b->length);
for (size_t i=0; i < graph->length; i++) {
RL_GraphNode *node = &graph->nodes[i];
if (node->score <= FLT_MAX - graph_b->nodes[i].score) {
node->score += graph_b->nodes[i].score;
} else {
node->score = FLT_MAX;
}
}
}
void rl_graph_weight(RL_Graph *graph, float coefficient)
{
RL_ASSERT(graph != NULL);
RL_ASSERT(coefficient <= 1 && coefficient >= 0);
for (size_t i=0; i < graph->length; i++) {
RL_GraphNode *node = &graph->nodes[i];
node->score *= coefficient;
}
}
RL_Graph *rl_dijkstra_create(const RL_Map *map,
RL_Point start,
RL_DistanceFun distance_f)
{
RL_Graph *graph = rl_graph_create(map);
rl_dijkstra_score(graph, start, distance_f);
return graph;
}
/* default scorer function for Dijkstra - this simply accepts a RL_DistanceFun as context and adds the current nodes */
/* score to the result of the distance function */
struct rl_score_context { RL_DistanceFun fun; };
float rl_dijkstra_default_score_f(const RL_GraphNode *current, const RL_GraphNode *neighbor, void *context)
{
struct rl_score_context *distance_f = (struct rl_score_context*) context;
return current->score + distance_f->fun(current->point, neighbor->point);
}
void rl_dijkstra_score(RL_Graph *graph, RL_Point start, RL_DistanceFun distance_f)
{
struct { RL_DistanceFun fun; } scorer_context;
scorer_context.fun = distance_f ? distance_f : rl_distance_simple; /* default to rl_distance_simple */
rl_dijkstra_score_ex(graph, start, rl_dijkstra_default_score_f, &scorer_context);
}
void rl_graph_reset(RL_Graph *graph)
{
RL_ASSERT(graph != NULL);
if (graph == NULL) return;
/* reset scores of dijkstra map */
for (size_t i=0; i < graph->length; i++) {
RL_GraphNode *node = &graph->nodes[i];
node->score = FLT_MAX;
}
}
void rl_dijkstra_score_ex(RL_Graph *graph, RL_Point start, RL_ScoreFun score_f, void *score_context)
{
RL_ASSERT(graph);
RL_ASSERT(score_f);
if (graph == NULL) return;
RL_GraphNode *current;
RL_Heap *heap = rl_heap_create(graph->length, &rl_scored_graph_heap_comparison);
/* reset scores of dijkstra map, setting the start point to 0 */
for (size_t i=0; i < graph->length; i++) {
RL_GraphNode *node = &graph->nodes[i];
if (node->point.x == start.x && node->point.y == start.y) {
node->score = 0;
current = node;
} else {
node->score = FLT_MAX;
}
}
rl_heap_insert(heap, (void*) current);
current = (RL_GraphNode*) rl_heap_pop(heap);
while (current) {
for (size_t i=0; i<current->neighbors_length; i++) {
RL_GraphNode *neighbor = current->neighbors[i];
float distance = score_f(current, neighbor, score_context);
if (distance < neighbor->score) {
if (neighbor->score == FLT_MAX) {
rl_heap_insert(heap, neighbor);
}
neighbor->score = distance;
}
}
current = (RL_GraphNode *) rl_heap_pop(heap);
}
rl_heap_destroy(heap);
}
void rl_graph_destroy(RL_Graph *graph)
{
if (graph) {
if (graph->nodes) {
RL_FREE(graph->nodes);
}
RL_FREE(graph);
}
}
bool rl_graph_is_scored(const RL_Graph *graph, RL_Point point)
{
RL_GraphNode *n = rl_graph_node(graph, point);
if (n) {
return n->score < FLT_MAX;
} else {
return false;
}
}
RL_GraphNode *rl_graph_node(const RL_Graph *graph, RL_Point point)
{
RL_ASSERT(graph);
if (graph == NULL) return NULL;
for (unsigned int i=0; i<graph->length; ++i) {
RL_GraphNode *n = &graph->nodes[i];
RL_ASSERT(n);
if (n && n->point.x == point.x && n->point.y == point.y) {
return n;
}
}
return NULL;
}
RL_GraphNode *rl_graph_node_lowest_neighbor(const RL_GraphNode *node)
{
RL_ASSERT(node);
if (node == NULL) return NULL;
RL_GraphNode *lowest_neighbor = NULL;
for (size_t i=0; i<node->neighbors_length; i++) {
RL_GraphNode *neighbor = node->neighbors[i];
if (!lowest_neighbor || neighbor->score < lowest_neighbor->score) {
lowest_neighbor = neighbor;
}
}
if (lowest_neighbor->score == FLT_MAX) return NULL;
return lowest_neighbor;
}
int rl_graph_compare_neighbors(const void *a, const void *b)
{
RL_GraphNode **node_a = (RL_GraphNode**) a;
RL_GraphNode **node_b = (RL_GraphNode**) b;
if ((*node_a)->score < (*node_b)->score) return -1;
if ((*node_a)->score > (*node_b)->score) return 1;
return 0;
}
void rl_graph_node_sort_neighbors(RL_GraphNode *node)
{
qsort(node->neighbors, node->neighbors_length, sizeof(*node->neighbors), rl_graph_compare_neighbors);
}
#endif /* RL_ENABLE_PATHFINDING */
#if RL_ENABLE_FOV
RL_FOV *rl_fov_create(unsigned int width, unsigned int height)
{
RL_FOV *fov;
unsigned char *memory;
RL_ASSERT(width > 0 && height > 0);
RL_ASSERT(width != UINT_MAX && !(width > UINT_MAX / height)); /* check for overflow */
fov = NULL;
/* allocate all the memory we need at once */
memory = (unsigned char*) RL_CALLOC(sizeof(*fov) + sizeof(*fov->visibility)*width*height, 1);
RL_ASSERT(memory);
if (memory == NULL) return NULL;
fov = (RL_FOV*) memory;
fov->width = width;
fov->height = height;
fov->visibility = (RL_Byte*) (memory + sizeof(*fov));
RL_ASSERT(fov);
RL_ASSERT(fov->visibility);
return fov;
}
void rl_fov_destroy(RL_FOV *fov)
{
if (fov) {
RL_FREE(fov);
}
}
typedef struct {
int Y;
int X;
} RL_Slope;
/* adapted from: https://www.adammil.net/blog/v125_Roguelike_Vision_Algorithms.html#shadowcode (public domain) */
/* also see: https://www.roguebasin.com/index.php/FOV_using_recursive_shadowcasting */
void rl_fov_calculate_recursive(void *map, unsigned int origin_x, unsigned int origin_y, RL_IsInRangeFun in_range_f, RL_IsOpaqueFun opaque_f, RL_MarkAsVisibleFun mark_visible_f, unsigned int octant, float original_x, RL_Slope top, RL_Slope bottom)
{
int x;
RL_ASSERT(in_range_f);
RL_ASSERT(opaque_f);
RL_ASSERT(mark_visible_f);
for(x = original_x; x < RL_MAX_RECURSION; x++)
{
/* compute the Y coordinates where the top vector leaves the column (on the right) and where the bottom vector */
/* enters the column (on the left). this equals (x+0.5)*top+0.5 and (x-0.5)*bottom+0.5 respectively, which can */
/* be computed like (x+0.5)*top+0.5 = (2(x+0.5)*top+1)/2 = ((2x+1)*top+1)/2 to avoid floating point math */
/* the rounding is a bit tricky, though */
int topY = top.X == 1 ? x : ((x*2+1) * top.Y + top.X - 1) / (top.X*2); /* the rounding is a bit tricky, though */
int bottomY = bottom.Y == 0 ? 0 : ((x*2-1) * bottom.Y + bottom.X) / (bottom.X*2);
int wasOpaque = -1; /* 0:false, 1:true, -1:not applicable */
int y;
for(y=topY; y >= bottomY; y--)
{
float tx = origin_x, ty = origin_y;
bool inRange, isOpaque;
switch(octant) /* translate local coordinates to map coordinates */
{
case 0: tx += x; ty -= y; break;
case 1: tx += y; ty -= x; break;
case 2: tx -= y; ty -= x; break;
case 3: tx -= x; ty -= y; break;
case 4: tx -= x; ty += y; break;
case 5: tx -= y; ty += x; break;
case 6: tx += y; ty += x; break;
case 7: tx += x; ty += y; break;
}
inRange = in_range_f(tx, ty, map);
if(inRange) {
if (RL_FOV_SYMMETRIC && (y != topY || top.Y*(int)x >= top.X*y) && (y != bottomY || bottom.Y*(int)x <= bottom.X*y)) {
mark_visible_f(tx, ty, map);
} else if (!RL_FOV_SYMMETRIC) {
mark_visible_f(tx, ty, map);
}
}
if (x == original_x && !inRange) {
return;
}
isOpaque = !inRange || opaque_f(tx, ty, map);
if(isOpaque)
{
if(wasOpaque == 0) /* if we found a transition from clear to opaque, this sector is done in this column, so */
{ /* adjust the bottom vector upwards and continue processing it in the next column. */
RL_Slope newBottom;
newBottom.Y = y*2 + 1; /* (x*2-1, y*2+1) is a vector to the top-left of the opaque tile */
newBottom.X = x*2 - 1;
if(!inRange || y == bottomY) { bottom = newBottom; break; } /* don't recurse unless we have to */
else if (inRange) rl_fov_calculate_recursive(map, origin_x, origin_y, in_range_f, opaque_f, mark_visible_f, octant, x+1, top, newBottom);
}
wasOpaque = 1;
}
else /* adjust top vector downwards and continue if we found a transition from opaque to clear */
{ /* (x*2+1, y*2+1) is the top-right corner of the clear tile (i.e. the bottom-right of the opaque tile) */
if(wasOpaque > 0) {
top.Y = y*2 + 1;
top.X = x*2 + 1;
}
wasOpaque = 0;
}
}
if(wasOpaque != 0) break; /* if the column ended in a clear tile, continue processing the current sector */
}
}
struct RL_FOVMap {
RL_FOV *fov;
const RL_Map *map;
unsigned int origin_x;
unsigned int origin_y;
int fov_radius;
};
void rl_fovmap_mark_visible_f(unsigned int x, unsigned int y, void *context)
{
struct RL_FOVMap *map = (struct RL_FOVMap*) context;
if (rl_map_in_bounds(map->map, x, y)) {
map->fov->visibility[x + y*map->map->width] = RL_TileVisible;
}
}
bool rl_fovmap_opaque_f(unsigned int x, unsigned int y, void *context)
{
struct RL_FOVMap *map = (struct RL_FOVMap*) context;
return RL_OPAQUE_F(map->map, x, y);
}
bool rl_fovmap_in_range_f(unsigned int x, unsigned int y, void *context)
{
struct RL_FOVMap *map = (struct RL_FOVMap*) context;
#if RL_ENABLE_PATHFINDING
RL_Point p1, p2;
p1.x = map->origin_x;
p1.y = map->origin_y;
p2.x = x;
p2.y = y;
return map->fov_radius < 0 || RL_FOV_DISTANCE_F(p1, p2) <= (float)map->fov_radius;
#else
/* simplistic manhattan distance distance */
int diff_x = (int)map->origin_x - (int)x;
int diff_y = (int)map->origin_y - (int)y;
if (diff_x < 0) diff_x *= -1;
if (diff_y < 0) diff_y *= -1;
return map->fov_radius < 0 || diff_x + diff_y < map->fov_radius;
#endif
}
void rl_fov_calculate(RL_FOV *fov, const RL_Map *map, unsigned int x, unsigned int y, int fov_radius)
{
struct RL_FOVMap fovmap;
unsigned int cur_x, cur_y;
if (!rl_map_in_bounds(map, x, y)) {
return;
}
/* set previously visible tiles to seen */
for (cur_x=0; cur_x<map->width; ++cur_x) {
for (cur_y=0; cur_y<map->height; ++cur_y) {
if (fov->visibility[cur_x + cur_y*map->width] == RL_TileVisible) {
fov->visibility[cur_x + cur_y*map->width] = RL_TileSeen;
}
}
}
fovmap.map = map;
fovmap.fov = fov;
fovmap.origin_x = x;
fovmap.origin_y = y;
fovmap.fov_radius = fov_radius;
rl_fov_calculate_ex(&fovmap, x, y, rl_fovmap_in_range_f, rl_fovmap_opaque_f, rl_fovmap_mark_visible_f);
}
void rl_fov_calculate_ex(void *context, unsigned int x, unsigned int y, RL_IsInRangeFun in_range_f, RL_IsOpaqueFun opaque_f, RL_MarkAsVisibleFun mark_visible_f)
{
int octant;
RL_Slope from = { 1, 1 };
RL_Slope to = { 0, 1 };
mark_visible_f(x, y, context);
for (octant=0; octant<8; ++octant) {
rl_fov_calculate_recursive(context, x, y, in_range_f, opaque_f, mark_visible_f, octant, 1, from, to);
}
}
bool rl_fov_is_visible(const RL_FOV *map, unsigned int x, unsigned int y)
{
if (map == NULL) return false;
if (!rl_map_in_bounds((const RL_Map*) map, x, y)) {
return false;
}
return map->visibility[x + y*map->width] == RL_TileVisible;
}
bool rl_fov_is_seen(const RL_FOV *map, unsigned int x, unsigned int y)
{
if (map == NULL) return false;
if (!rl_map_in_bounds((const RL_Map*) map, x, y)) {
return false;
}
return map->visibility[x + y*map->width] == RL_TileSeen;
}
#endif /* if RL_ENABLE_FOV */
#if RL_ENABLE_FILE
#include <stdio.h>
bool rl_file_save_map(const RL_Map *data, void *file)
{
int version = 0;
RL_ASSERT(data != NULL && file != NULL);
if (fwrite(&version, sizeof(version), 1, (FILE*) file) < 1) {
return false;
}
if (fwrite(data, sizeof(*data), 1, (FILE*)file) < 1) {
return false;
}
if (fwrite(data->tiles, sizeof(*data->tiles), data->width * data->height, (FILE*) file) < data->width * data->height) {
return false;
}
return true;
}
bool rl_file_load_map(RL_Map **data, void *file)
{
int version;
RL_Map dest;
RL_ASSERT(data != NULL && file != NULL);
if (fread(&version, sizeof(version), 1, (FILE*) file) < 1) {
return false;
}
if (version != 0) {
return false;
}
if (fread(&dest, sizeof(dest), 1, (FILE*) file) < 1) {
return false;
}
RL_ASSERT(dest.width > 0 && dest.height > 0);
dest.tiles = (RL_Byte*) malloc(sizeof(*dest.tiles) * dest.width * dest.height);
RL_ASSERT(dest.tiles != NULL);
if (fread(dest.tiles, sizeof(*dest.tiles), dest.width * dest.height, (FILE*) file) < dest.width * dest.height) {
return false;
}
*data = (RL_Map*) malloc(sizeof(dest));
RL_ASSERT(*data != NULL);
*data = (RL_Map*) memcpy(*data, &dest, sizeof(dest));
if (data == NULL) {
return false;
}
return true;
}
bool rl_file_save_fov(const RL_FOV *data, void *file)
{
int version = 0;
RL_ASSERT(data != NULL && file != NULL);
if (fwrite(&version, sizeof(version), 1, (FILE*) file) < 1) {
return false;
}
if (fwrite(data, sizeof(*data), 1, (FILE*) file) < 1) {
return false;
}
if (fwrite(data->visibility, sizeof(*data->visibility), data->width * data->height, (FILE*) file) < data->width * data->height) {
return false;
}
return true;
}
bool rl_file_load_fov(RL_FOV **data, void *file)
{
int version;
RL_FOV dest;
RL_ASSERT(data != NULL && file != NULL);
if (fread(&version, sizeof(version), 1, (FILE*) file) < 1) {
return false;
}
if (version != 0) {
return false;
}
if (fread(&dest, sizeof(dest), 1, (FILE*) file) < 1) {
return false;
}
RL_ASSERT(dest.width > 0 && dest.height > 0);
dest.visibility = (RL_Byte*) malloc(sizeof(*dest.visibility) * dest.width * dest.height);
RL_ASSERT(dest.visibility != NULL);
if (fread(dest.visibility, sizeof(*dest.visibility), dest.width * dest.height, (FILE*)file) < dest.width * dest.height) {
return false;
}
*data = (RL_FOV*) malloc(sizeof(dest));
RL_ASSERT(*data != NULL);
*data = (RL_FOV*) memcpy(*data, &dest, sizeof(dest));
if (data == NULL) {
return false;
}
return true;
}
#endif /* if RL_ENABLE_FILE */
#endif /* RL_IMPLEMENTATION */
#ifdef __cplusplus
}
#endif