Crossfire Server, Trunk  1.75.0
room_gen_crawl.cpp
Go to the documentation of this file.
1 
26 #include <queue>
27 
28 #include <stdlib.h>
29 #include <global.h>
30 #include <random_map.h>
31 
32 
33 #define MAP_CRAWL_NONE 0
34 #define MAP_CRAWL_WEST 1
35 #define MAP_CRAWL_EAST 2
36 #define MAP_CRAWL_NORTH 4
37 #define MAP_CRAWL_SOUTH 8
38 // Mask of all the directions
39 #define MAP_CRAWL_ALL 15
40 
55 static void print_debug_paths(uint8_t *paths, int x_eff, int y_eff) {
56  for (int y = 0; y < y_eff; ++y) {
57  for (int x = 0; x < x_eff; ++x) {
58  switch (paths[x * y_eff + y]) {
59  case MAP_CRAWL_WEST:
60  printf("╸");
61  break;
63  printf("━");
64  break;
66  printf("┛");
67  break;
69  printf("┓");
70  break;
72  printf("┻");
73  break;
75  printf("┳");
76  break;
78  printf("┫");
79  break;
81  printf("╋");
82  break;
83  case MAP_CRAWL_EAST:
84  printf("╺");
85  break;
87  printf("┗");
88  break;
90  printf("┏");
91  break;
93  printf("┣");
94  break;
95  case MAP_CRAWL_NORTH:
96  printf("╹");
97  break;
99  printf("┃");
100  break;
101  case MAP_CRAWL_SOUTH:
102  printf("╻");
103  break;
104  case MAP_CRAWL_NONE:
105  printf(".");
106  break;
107  default:
108  printf("?");
109  }
110  }
111  printf("\n");
112  }
113 }
114 
139 char **map_gen_crawl(int xsize, int ysize, int iter, int hallway) {
140  // Create a two-dimensional array of zero-filled characters.
141  char **crawl = (char **)malloc(xsize * sizeof(char *));
142  for (int i = 0; i < xsize; ++i) {
143  crawl[i] = (char *)calloc(ysize, sizeof(char));
144  }
145 
146  // If no hallway size is provided, select at random in range [1,3].
147  if (hallway == 0) {
148  hallway = (RANDOM() % 3) + 1;
149  }
150 
151  // Determine the functional x and y sizes for maze design.
152  // This is affected by the hallway size.
153  // We remove one from the size for the unaccounted part of the outer wall.
154  // Also handle the upper-left of walls in each block.
155  int x_eff = (xsize - 1) / (hallway + 1),
156  y_eff = (ysize - 1) / (hallway + 1);
157 
158  // If x_eff or y_eff are zero, bail entirely.
159  if (!x_eff || !y_eff) {
160  // Crawl is a blank dungeon.
161  // I don't think this code path will happen without a defined hallway width that is far too large.
162  return crawl;
163  }
164 
165  // Allocate an array of int8s to hold the flags of where the map goes.
166  uint8_t *paths = (uint8_t *)calloc(xsize * ysize, sizeof(uint8_t));
167 
168  // Determine where we start spidering from.
169  // Make sure to avoid starting at the edge if we have enough room
170  int startx, starty;
171  if (x_eff <= 2) {
172  startx = RANDOM() % x_eff;
173  }
174  else {
175  startx = (RANDOM() % (x_eff - 2)) + 1;
176  }
177  if (y_eff <= 2) {
178  starty = RANDOM() % y_eff;
179  }
180  else {
181  starty = (RANDOM() % (y_eff - 2)) + 1;
182  }
183 
184  // Count the sections we create.
185  int sections = 0;
186 
187  // Add the start position to the queue
188  std::queue<int> queue;
189  queue.push(startx * y_eff + starty);
190 
191  // Set a flag for marking the first entry.
192  // We need it to guarantee generating something.
193  int8_t first = 1;
194 
195  while (!queue.empty()) {
196  // Dequeue the point info from the queue
197  // Why is this two separate calls? Really STL?
198  int loc = queue.front();
199  queue.pop();
200  // Massage the point info out of the int
201  int at_x = loc / y_eff;
202  int at_y = loc % y_eff;
203 
204  ++sections;
205 
206  // Sanity check for the tile. If it is already filled, skip.
207  if (paths[at_x * y_eff + at_y] != MAP_CRAWL_NONE) {
208  continue;
209  }
210 
211  // Paths we can take. Start by assuming all directions are clear.
212  uint8_t avail_paths = MAP_CRAWL_ALL;
213 
214  // Paths we must take to complete open paths. Start by assuming none.
215  uint8_t req_paths = MAP_CRAWL_NONE;
216 
217  // Determine what directions are available.
218  // We cannot leave the bounds of the map,
219  // Nor can we trample existing path definitions.
220  if (at_x == 0) {
221  avail_paths &= ~MAP_CRAWL_WEST;
222  }
223  else if (paths[(at_x - 1) * y_eff + at_y] != MAP_CRAWL_NONE) {
224  // If non-blank tile has no path to here, exclude it.
225  if ((paths[(at_x - 1) * y_eff + at_y] & MAP_CRAWL_EAST) == 0) {
226  avail_paths &= ~MAP_CRAWL_WEST;
227  }
228  // Otherwise, require it.
229  else {
230  req_paths |= MAP_CRAWL_WEST;
231  }
232  }
233  if (at_x == x_eff - 1) {
234  avail_paths &= ~MAP_CRAWL_EAST;
235  }
236  else if (paths[(at_x + 1) * y_eff + at_y] != MAP_CRAWL_NONE) {
237  // If non-blank tile has no path to here, exclude it.
238  if ((paths[(at_x + 1) * y_eff + at_y] & MAP_CRAWL_WEST) == 0) {
239  avail_paths &= ~MAP_CRAWL_EAST;
240  }
241  // Otherwise, require it.
242  else {
243  req_paths |= MAP_CRAWL_EAST;
244  }
245  }
246  if (at_y == 0) {
247  avail_paths &= ~MAP_CRAWL_NORTH;
248  }
249  else if (paths[at_x * y_eff + at_y - 1] != MAP_CRAWL_NONE) {
250  // If non-blank tile has no path to here, exclude it.
251  if ((paths[at_x * y_eff + at_y - 1] & MAP_CRAWL_SOUTH) == 0) {
252  avail_paths &= ~MAP_CRAWL_NORTH;
253  }
254  // Otherwise, require it.
255  else {
256  req_paths |= MAP_CRAWL_NORTH;
257  }
258  }
259  if (at_y == y_eff - 1) {
260  avail_paths &= ~MAP_CRAWL_SOUTH;
261  }
262  else if (paths[at_x * y_eff + at_y + 1] != MAP_CRAWL_NONE) {
263  // If non-blank tile has no path to here, exclude it.
264  if ((paths[at_x * y_eff + at_y + 1] & MAP_CRAWL_NORTH) == 0) {
265  avail_paths &= ~MAP_CRAWL_SOUTH;
266  }
267  // Otherwise, require it.
268  else {
269  req_paths |= MAP_CRAWL_SOUTH;
270  }
271  }
272 
273  uint8_t path;
274  // Make sure the first try of the attmept always generates at least one path.
275  // Otherwise it is possible (albeit rare, 1/16 in each generation) to have an all-wall result.
276  do {
277  path = (RANDOM() & avail_paths) | req_paths;
278  } while (first && !path);
279 
280  // Mask out the unvailable paths and required paths, and randomly generate remaining connections.
281  paths[at_x * y_eff + at_y] = path;
282  first = 0;
283 
284  // Now we enqueue any new paths into the queue
285  // req_paths happens to tell us where some adjacent stuff is,
286  // and checking them is cheap and easy.
287  // If not from a required connection, we add to the queue.
288  // We can assert there is a filled space when it was required, so we
289  // don't need to process that tile again.
290  if (paths[at_x * y_eff + at_y] & MAP_CRAWL_WEST & ~req_paths) {
291  queue.push((at_x - 1) * y_eff + at_y);
292  }
293  if (paths[at_x * y_eff + at_y] & MAP_CRAWL_EAST & ~req_paths) {
294  queue.push((at_x + 1) * y_eff + at_y);
295  }
296  if (paths[at_x * y_eff + at_y] & MAP_CRAWL_NORTH & ~req_paths) {
297  queue.push(at_x * y_eff + at_y - 1);
298  }
299  if (paths[at_x * y_eff + at_y] & MAP_CRAWL_SOUTH & ~req_paths) {
300  queue.push(at_x * y_eff + at_y + 1);
301  }
302 
303  }
304 
305  // Sanity check on our sections.
306  if (iter == 0 && sections < x_eff * y_eff / 5) {
307  // Clean up this run and try again.
308  for (int i = 0; i < xsize; ++i) {
309  free(crawl[i]);
310  }
311  free(crawl);
312  free(paths);
313  // Single try at recursion.
314  return map_gen_crawl(xsize, ysize, 1, hallway);
315  }
316 
317  // Temp debugging of paths
318  // print_debug_paths(paths, x_eff, y_eff);
319 
320  // Translate our generation onto the actual random map.
321 
322  // First, figure out the offset to center our generatable space
323  // in the center of the map size
324  int offset_x = (xsize - (hallway + 1) * x_eff - 1) / 2,
325  offset_y = (ysize - (hallway + 1) * y_eff - 1) / 2;
326 
327  // Fill in unused areas with wall
328  // Left side
329  for (int i = 0; i < offset_x; ++i) {
330  for (int j = 0; j < ysize; ++j) {
331  crawl[i][j] = '#';
332  }
333  }
334  // Top side
335  for (int j = 0; j < offset_y; ++j) {
336  for (int i = 0; i < xsize; ++i) {
337  crawl[i][j] = '#';
338  }
339  }
340  // Right side
341  for (int i = offset_x + (hallway + 1) * x_eff + 1; i < xsize; ++i) {
342  for (int j = 0; j < ysize; ++j) {
343  crawl[i][j] = '#';
344  }
345  }
346  // Bottom side
347  for (int j = offset_y + (hallway + 1) * y_eff + 1; j < ysize; ++j) {
348  for (int i = 0; i < xsize; ++i) {
349  crawl[i][j] = '#';
350  }
351  }
352 
353  // Handle the used area.
354  for (int x = 0; x < x_eff; ++x) {
355  for (int y = 0; y < y_eff; ++y) {
356  // We affect hallway + 2 tiles in each direction.
357  // But we shift hallway + 1 tiles for the next tile,
358  // which will cause some repeated wall constructions,
359  // but will prevent a mess of checks in this section.
360 
361  uint8_t cur_path = paths[x * y_eff + y];
362 
363  // If no paths, fill with walls
364  if ((cur_path & MAP_CRAWL_ALL) == 0) {
365  for (int tmpx = 0; tmpx < hallway + 2; ++tmpx) {
366  for (int tmpy = 0; tmpy < hallway + 2; ++tmpy) {
367  crawl[x * (hallway + 1) + offset_x + tmpx][y * (hallway + 1) + offset_y + tmpy] = '#';
368  }
369  }
370  }
371  // Otherwise, draw the necessary walls
372  else {
373  // If north is blocked, write walls
374  if ((cur_path & MAP_CRAWL_NORTH) == 0) {
375  for (int tmp = 0; tmp < hallway + 2; ++tmp) {
376  crawl[x * (hallway + 1) + offset_x + tmp][y * (hallway + 1) + offset_y] = '#';
377  }
378  }
379  // Do the same for each other direction.
380  if ((cur_path & MAP_CRAWL_SOUTH) == 0) {
381  for (int tmp = 0; tmp < hallway + 2; ++tmp) {
382  crawl[x * (hallway + 1) + offset_x + tmp][(y + 1) * (hallway + 1) + offset_y] = '#';
383  }
384  }
385  if ((cur_path & MAP_CRAWL_WEST) == 0) {
386  for (int tmp = 0; tmp < hallway + 2; ++tmp) {
387  crawl[x * (hallway + 1) + offset_x][y * (hallway + 1) + offset_y + tmp] = '#';
388  }
389  }
390  if ((cur_path & MAP_CRAWL_EAST) == 0) {
391  for (int tmp = 0; tmp < hallway + 2; ++tmp) {
392  crawl[(x + 1) * (hallway + 1) + offset_x][y * (hallway + 1) + offset_y + tmp] = '#';
393  }
394  }
395  }
396  }
397  }
398 
399  // Cleanup
400  free(paths);
401 
402  return crawl;
403 }
global.h
random_map.h
MAP_CRAWL_NORTH
#define MAP_CRAWL_NORTH
Definition: room_gen_crawl.cpp:36
paths
*envar *is the environment if one that can also be used as an override If both the flag and the envar are the envar takes precedence name flag envar notes confdir conf absolute datadir data CROSSFIRE_LIBDIR absolute localdir CROSSFIRE_LOCALDIR absolute mapdir maps CROSSFIRE_MAPDIR relative to datadir or localdir playerdir playerdir CROSSFIRE_PLAYERDIR relative to localdir templatedir tmpdir CROSSFIRE_TEMPLATEDIR relative to localdir uniquedir uniquedir CROSSFIRE_UNIQUEDIR relative to localdir tmpdir templatedir CROSSFIRE_TMPDIR absolute regions regions unused Paths marked absolute if you contain relative paths
Definition: server-directories.txt:26
MAP_CRAWL_EAST
#define MAP_CRAWL_EAST
Definition: room_gen_crawl.cpp:35
MAP_CRAWL_ALL
#define MAP_CRAWL_ALL
Definition: room_gen_crawl.cpp:39
MAP_CRAWL_SOUTH
#define MAP_CRAWL_SOUTH
Definition: room_gen_crawl.cpp:37
print_debug_paths
static void print_debug_paths(uint8_t *paths, int x_eff, int y_eff)
Function for helping to ensure parity between the compartmentalized and final versions of the map.
Definition: room_gen_crawl.cpp:55
RANDOM
#define RANDOM()
Definition: define.h:628
map_gen_crawl
char ** map_gen_crawl(int xsize, int ysize, int iter, int hallway)
Generator of the crawl maze.
Definition: room_gen_crawl.cpp:139
MAP_CRAWL_NONE
#define MAP_CRAWL_NONE
Definition: room_gen_crawl.cpp:33
MAP_CRAWL_WEST
#define MAP_CRAWL_WEST
Definition: room_gen_crawl.cpp:34