/** * 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 /* 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 #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 #include #include #include #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 #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 #include #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; xchance_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= 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; xlen; ++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; ilen; ++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_idxlength; ++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_idxlength; ++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; ytiles[x + y*map->width] = RL_TileRock; x = width - 1; for (y=offset_y; ytiles[x + y*map->width] = RL_TileRock; y = 0; for (x=offset_x; xtiles[x + y*map->width] = RL_TileRock; y = height - 1; for (x=offset_x; xtiles[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; xtiles[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; ilength; 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; xwidth; x++) { for (unsigned int y=0; yheight; 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; ineighbors_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; ilength; ++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; ineighbors_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_xwidth; ++cur_x) { for (cur_y=0; cur_yheight; ++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 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