1 /*
2 * Grid
3 * Brogue
4 *
5 * Created by Brian Walker on 12/7/12.
6 * Copyright 2012. All rights reserved.
7 *
8 * This file is part of Brogue.
9 *
10 * This program is free software: you can redistribute it and/or modify
11 * it under the terms of the GNU Affero General Public License as
12 * published by the Free Software Foundation, either version 3 of the
13 * License, or (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Affero General Public License for more details.
19 *
20 * You should have received a copy of the GNU Affero General Public License
21 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 */
23
24 #include "Rogue.h"
25 #include "IncludeGlobals.h"
26
27
28 // mallocing two-dimensional arrays! dun dun DUN!
allocGrid()29 short **allocGrid() {
30 short i;
31 short **array = malloc(DCOLS * sizeof(short *));
32
33 array[0] = malloc(DROWS * DCOLS * sizeof(short));
34 for(i = 1; i < DCOLS; i++) {
35 array[i] = array[0] + i * DROWS;
36 }
37 return array;
38 }
39
freeGrid(short ** array)40 void freeGrid(short **array) {
41 free(array[0]);
42 free(array);
43 }
44
copyGrid(short ** to,short ** from)45 void copyGrid(short **to, short **from) {
46 short i, j;
47
48 for(i = 0; i < DCOLS; i++) {
49 for(j = 0; j < DROWS; j++) {
50 to[i][j] = from[i][j];
51 }
52 }
53 }
54
fillGrid(short ** grid,short fillValue)55 void fillGrid(short **grid, short fillValue) {
56 short i, j;
57
58 for(i = 0; i < DCOLS; i++) {
59 for(j = 0; j < DROWS; j++) {
60 grid[i][j] = fillValue;
61 }
62 }
63 }
64
65 // Highlight the portion indicated by hiliteCharGrid with the hiliteColor at the hiliteStrength -- both latter arguments are optional.
hiliteGrid(short ** grid,color * hiliteColor,short hiliteStrength)66 void hiliteGrid(short **grid, color *hiliteColor, short hiliteStrength) {
67 short i, j, x, y;
68 color hCol;
69
70 assureCosmeticRNG;
71
72 if (hiliteColor) {
73 hCol = *hiliteColor;
74 } else {
75 hCol = yellow;
76 }
77
78 bakeColor(&hCol);
79
80 if (!hiliteStrength) {
81 hiliteStrength = 75;
82 }
83
84 for (i=0; i<DCOLS; i++) {
85 for (j=0; j<DROWS; j++) {
86 if (grid[i][j]) {
87 x = mapToWindowX(i);
88 y = mapToWindowY(j);
89
90 displayBuffer[x][y].backColorComponents[0] = clamp(displayBuffer[x][y].backColorComponents[0] + hCol.red * hiliteStrength / 100, 0, 100);
91 displayBuffer[x][y].backColorComponents[1] = clamp(displayBuffer[x][y].backColorComponents[1] + hCol.green * hiliteStrength / 100, 0, 100);
92 displayBuffer[x][y].backColorComponents[2] = clamp(displayBuffer[x][y].backColorComponents[2] + hCol.blue * hiliteStrength / 100, 0, 100);
93 displayBuffer[x][y].foreColorComponents[0] = clamp(displayBuffer[x][y].foreColorComponents[0] + hCol.red * hiliteStrength / 100, 0, 100);
94 displayBuffer[x][y].foreColorComponents[1] = clamp(displayBuffer[x][y].foreColorComponents[1] + hCol.green * hiliteStrength / 100, 0, 100);
95 displayBuffer[x][y].foreColorComponents[2] = clamp(displayBuffer[x][y].foreColorComponents[2] + hCol.blue * hiliteStrength / 100, 0, 100);
96 }
97 }
98 }
99 restoreRNG;
100 }
101
findReplaceGrid(short ** grid,short findValueMin,short findValueMax,short fillValue)102 void findReplaceGrid(short **grid, short findValueMin, short findValueMax, short fillValue) {
103 short i, j;
104
105 for(i = 0; i < DCOLS; i++) {
106 for(j = 0; j < DROWS; j++) {
107 if (grid[i][j] >= findValueMin && grid[i][j] <= findValueMax) {
108 grid[i][j] = fillValue;
109 }
110 }
111 }
112 }
113
114 // Flood-fills the grid from (x, y) along cells that are within the eligible range.
115 // Returns the total count of filled cells.
floodFillGrid(short ** grid,short x,short y,short eligibleValueMin,short eligibleValueMax,short fillValue)116 short floodFillGrid(short **grid, short x, short y, short eligibleValueMin, short eligibleValueMax, short fillValue) {
117 enum directions dir;
118 short newX, newY, fillCount = 1;
119
120 brogueAssert(fillValue < eligibleValueMin || fillValue > eligibleValueMax);
121
122 grid[x][y] = fillValue;
123 for (dir = 0; dir < 4; dir++) {
124 newX = x + nbDirs[dir][0];
125 newY = y + nbDirs[dir][1];
126 if (coordinatesAreInMap(newX, newY)
127 && grid[newX][newY] >= eligibleValueMin
128 && grid[newX][newY] <= eligibleValueMax) {
129 fillCount += floodFillGrid(grid, newX, newY, eligibleValueMin, eligibleValueMax, fillValue);
130 }
131 }
132 return fillCount;
133 }
134
drawRectangleOnGrid(short ** grid,short x,short y,short width,short height,short value)135 void drawRectangleOnGrid(short **grid, short x, short y, short width, short height, short value) {
136 short i, j;
137
138 for (i=x; i < x+width; i++) {
139 for (j=y; j<y+height; j++) {
140 grid[i][j] = value;
141 }
142 }
143 }
144
drawCircleOnGrid(short ** grid,short x,short y,short radius,short value)145 void drawCircleOnGrid(short **grid, short x, short y, short radius, short value) {
146 short i, j;
147
148 for (i=max(0, x - radius - 1); i < max(DCOLS, x + radius); i++) {
149 for (j=max(0, y - radius - 1); j < max(DROWS, y + radius); j++) {
150 if ((i-x)*(i-x) + (j-y)*(j-y) < radius * radius + radius) {
151 grid[i][j] = value;
152 }
153 }
154 }
155 }
156
intersectGrids(short ** onto,short ** from)157 void intersectGrids(short **onto, short **from) {
158 short i, j;
159 for(i = 0; i < DCOLS; i++) {
160 for(j = 0; j < DROWS; j++) {
161 if (onto[i][j] && from[i][j]) {
162 onto[i][j] = true;
163 } else {
164 onto[i][j] = false;
165 }
166 }
167 }
168 }
169
uniteGrids(short ** onto,short ** from)170 void uniteGrids(short **onto, short **from) {
171 short i, j;
172 for(i = 0; i < DCOLS; i++) {
173 for(j = 0; j < DROWS; j++) {
174 if (!onto[i][j] && from[i][j]) {
175 onto[i][j] = from[i][j];
176 }
177 }
178 }
179 }
180
invertGrid(short ** grid)181 void invertGrid(short **grid) {
182 short i, j;
183 for(i = 0; i < DCOLS; i++) {
184 for(j = 0; j < DROWS; j++) {
185 grid[i][j] = !grid[i][j];
186 }
187 }
188 }
189
190 // Fills grid locations with the given value if they match any terrain flags or map flags.
191 // Otherwise does not change the grid location.
getTerrainGrid(short ** grid,short value,unsigned long terrainFlags,unsigned long mapFlags)192 void getTerrainGrid(short **grid, short value, unsigned long terrainFlags, unsigned long mapFlags) {
193 short i, j;
194 for(i = 0; i < DCOLS; i++) {
195 for(j = 0; j < DROWS; j++) {
196 if (grid[i][j] != value && cellHasTerrainFlag(i, j, terrainFlags) || (pmap[i][j].flags & mapFlags)) {
197 grid[i][j] = value;
198 }
199 }
200 }
201 }
202
getTMGrid(short ** grid,short value,unsigned long TMflags)203 void getTMGrid(short **grid, short value, unsigned long TMflags) {
204 short i, j;
205 for(i = 0; i < DCOLS; i++) {
206 for(j = 0; j < DROWS; j++) {
207 if (grid[i][j] != value && cellHasTMFlag(i, j, TMflags)) {
208 grid[i][j] = value;
209 }
210 }
211 }
212 }
213
getPassableArcGrid(short ** grid,short minPassableArc,short maxPassableArc,short value)214 void getPassableArcGrid(short **grid, short minPassableArc, short maxPassableArc, short value) {
215 short i, j, count;
216 for(i = 0; i < DCOLS; i++) {
217 for(j = 0; j < DROWS; j++) {
218 if (grid[i][j] != value) {
219 count = passableArcCount(i, j);
220 if (count >= minPassableArc && count <= maxPassableArc) {
221 grid[i][j] = value;
222 }
223 }
224 }
225 }
226 }
227
validLocationCount(short ** grid,short validValue)228 short validLocationCount(short **grid, short validValue) {
229 short i, j, count;
230 count = 0;
231 for(i = 0; i < DCOLS; i++) {
232 for(j = 0; j < DROWS; j++) {
233 if (grid[i][j] == validValue) {
234 count++;
235 }
236 }
237 }
238 return count;
239 }
240
leastPositiveValueInGrid(short ** grid)241 short leastPositiveValueInGrid(short **grid) {
242 short i, j, leastPositiveValue = 0;
243 for(i = 0; i < DCOLS; i++) {
244 for(j = 0; j < DROWS; j++) {
245 if (grid[i][j] > 0 && (leastPositiveValue == 0 || grid[i][j] < leastPositiveValue)) {
246 leastPositiveValue = grid[i][j];
247 }
248 }
249 }
250 return leastPositiveValue;
251 }
252
253 // Takes a grid as a mask of valid locations, chooses one randomly and returns it as (x, y).
254 // If there are no valid locations, returns (-1, -1).
randomLocationInGrid(short ** grid,short * x,short * y,short validValue)255 void randomLocationInGrid(short **grid, short *x, short *y, short validValue) {
256 const short locationCount = validLocationCount(grid, validValue);
257 short i, j;
258
259 if (locationCount <= 0) {
260 *x = *y = -1;
261 return;
262 }
263 short index = rand_range(0, locationCount - 1);
264 for(i = 0; i < DCOLS && index >= 0; i++) {
265 for(j = 0; j < DROWS && index >= 0; j++) {
266 if (grid[i][j] == validValue) {
267 if (index == 0) {
268 *x = i;
269 *y = j;
270 }
271 index--;
272 }
273 }
274 }
275 return;
276 }
277
278 // Finds the lowest positive number in a grid, chooses one location with that number randomly and returns it as (x, y).
279 // If there are no valid locations, returns (-1, -1).
randomLeastPositiveLocationInGrid(short ** grid,short * x,short * y,boolean deterministic)280 void randomLeastPositiveLocationInGrid(short **grid, short *x, short *y, boolean deterministic) {
281 const short targetValue = leastPositiveValueInGrid(grid);
282 short locationCount;
283 short i, j, index;
284
285 if (targetValue == 0) {
286 *x = *y = -1;
287 return;
288 }
289
290 locationCount = 0;
291 for(i = 0; i < DCOLS; i++) {
292 for(j = 0; j < DROWS; j++) {
293 if (grid[i][j] == targetValue) {
294 locationCount++;
295 }
296 }
297 }
298
299 if (deterministic) {
300 index = locationCount / 2;
301 } else {
302 index = rand_range(0, locationCount - 1);
303 }
304
305 for(i = 0; i < DCOLS && index >= 0; i++) {
306 for(j = 0; j < DROWS && index >= 0; j++) {
307 if (grid[i][j] == targetValue) {
308 if (index == 0) {
309 *x = i;
310 *y = j;
311 }
312 index--;
313 }
314 }
315 }
316 return;
317 }
318
getQualifyingPathLocNear(short * retValX,short * retValY,short x,short y,boolean hallwaysAllowed,unsigned long blockingTerrainFlags,unsigned long blockingMapFlags,unsigned long forbiddenTerrainFlags,unsigned long forbiddenMapFlags,boolean deterministic)319 boolean getQualifyingPathLocNear(short *retValX, short *retValY,
320 short x, short y,
321 boolean hallwaysAllowed,
322 unsigned long blockingTerrainFlags,
323 unsigned long blockingMapFlags,
324 unsigned long forbiddenTerrainFlags,
325 unsigned long forbiddenMapFlags,
326 boolean deterministic) {
327 short **grid, **costMap;
328 short loc[2];
329
330 // First check the given location to see if it works, as an optimization.
331 if (!cellHasTerrainFlag(x, y, blockingTerrainFlags | forbiddenTerrainFlags)
332 && !(pmap[x][y].flags & (blockingMapFlags | forbiddenMapFlags))
333 && (hallwaysAllowed || passableArcCount(x, y) <= 1)) {
334
335 *retValX = x;
336 *retValY = y;
337 return true;
338 }
339
340 // Allocate the grids.
341 grid = allocGrid();
342 costMap = allocGrid();
343
344 // Start with a base of a high number everywhere.
345 fillGrid(grid, 30000);
346 fillGrid(costMap, 1);
347
348 // Block off the pathing blockers.
349 getTerrainGrid(costMap, PDS_FORBIDDEN, blockingTerrainFlags, blockingMapFlags);
350 if (blockingTerrainFlags & (T_OBSTRUCTS_DIAGONAL_MOVEMENT | T_OBSTRUCTS_PASSABILITY)) {
351 getTerrainGrid(costMap, PDS_OBSTRUCTION, T_OBSTRUCTS_DIAGONAL_MOVEMENT, 0);
352 }
353
354 // Run the distance scan.
355 grid[x][y] = 1;
356 costMap[x][y] = 1;
357 dijkstraScan(grid, costMap, true);
358 findReplaceGrid(grid, 30000, 30000, 0);
359
360 // Block off invalid targets that aren't pathing blockers.
361 getTerrainGrid(grid, 0, forbiddenTerrainFlags, forbiddenMapFlags);
362 if (!hallwaysAllowed) {
363 getPassableArcGrid(grid, 2, 10, 0);
364 }
365
366 // Get the solution.
367 randomLeastPositiveLocationInGrid(grid, retValX, retValY, deterministic);
368
369 // dumpLevelToScreen();
370 // displayGrid(grid);
371 // if (coordinatesAreInMap(*retValX, *retValY)) {
372 // hiliteCell(*retValX, *retValY, &yellow, 100, true);
373 // }
374 // temporaryMessage("Qualifying path selected:", REQUIRE_ACKNOWLEDGMENT);
375
376 freeGrid(grid);
377 freeGrid(costMap);
378
379 // Fall back to a pathing-agnostic alternative if there are no solutions.
380 if (*retValX == -1 && *retValY == -1) {
381 if (getQualifyingLocNear(loc, x, y, hallwaysAllowed, NULL,
382 (blockingTerrainFlags | forbiddenTerrainFlags),
383 (blockingMapFlags | forbiddenMapFlags),
384 false, deterministic)) {
385 *retValX = loc[0];
386 *retValY = loc[1];
387 return true; // Found a fallback solution.
388 } else {
389 return false; // No solutions.
390 }
391 } else {
392 return true; // Found a primary solution.
393 }
394 }
395
cellularAutomataRound(short ** grid,char birthParameters[9],char survivalParameters[9])396 void cellularAutomataRound(short **grid, char birthParameters[9], char survivalParameters[9]) {
397 short i, j, nbCount, newX, newY;
398 enum directions dir;
399 short **buffer2;
400
401 buffer2 = allocGrid();
402 copyGrid(buffer2, grid); // Make a backup of grid in buffer2, so that each generation is isolated.
403
404 for(i=0; i<DCOLS; i++) {
405 for(j=0; j<DROWS; j++) {
406 nbCount = 0;
407 for (dir=0; dir< DIRECTION_COUNT; dir++) {
408 newX = i + nbDirs[dir][0];
409 newY = j + nbDirs[dir][1];
410 if (coordinatesAreInMap(newX, newY)
411 && buffer2[newX][newY]) {
412
413 nbCount++;
414 }
415 }
416 if (!buffer2[i][j] && birthParameters[nbCount] == 't') {
417 grid[i][j] = 1; // birth
418 } else if (buffer2[i][j] && survivalParameters[nbCount] == 't') {
419 // survival
420 } else {
421 grid[i][j] = 0; // death
422 }
423 }
424 }
425
426 freeGrid(buffer2);
427 }
428
429 // Marks a cell as being a member of blobNumber, then recursively iterates through the rest of the blob
fillContiguousRegion(short ** grid,short x,short y,short fillValue)430 short fillContiguousRegion(short **grid, short x, short y, short fillValue) {
431 enum directions dir;
432 short newX, newY, numberOfCells = 1;
433
434 grid[x][y] = fillValue;
435
436 // Iterate through the four cardinal neighbors.
437 for (dir=0; dir<4; dir++) {
438 newX = x + nbDirs[dir][0];
439 newY = y + nbDirs[dir][1];
440 if (!coordinatesAreInMap(newX, newY)) {
441 break;
442 }
443 if (grid[newX][newY] == 1) { // If the neighbor is an unmarked region cell,
444 numberOfCells += fillContiguousRegion(grid, newX, newY, fillValue); // then recurse.
445 }
446 }
447 return numberOfCells;
448 }
449
450 // Loads up **grid with the results of a cellular automata simulation.
createBlobOnGrid(short ** grid,short * retMinX,short * retMinY,short * retWidth,short * retHeight,short roundCount,short minBlobWidth,short minBlobHeight,short maxBlobWidth,short maxBlobHeight,short percentSeeded,char birthParameters[9],char survivalParameters[9])451 void createBlobOnGrid(short **grid,
452 short *retMinX, short *retMinY, short *retWidth, short *retHeight,
453 short roundCount,
454 short minBlobWidth, short minBlobHeight,
455 short maxBlobWidth, short maxBlobHeight, short percentSeeded,
456 char birthParameters[9], char survivalParameters[9]) {
457
458 short i, j, k;
459 short blobNumber, blobSize, topBlobNumber, topBlobSize;
460
461 short topBlobMinX, topBlobMinY, topBlobMaxX, topBlobMaxY, blobWidth, blobHeight;
462 //short buffer2[maxBlobWidth][maxBlobHeight]; // buffer[][] is already a global short array
463 boolean foundACellThisLine;
464
465 // Generate blobs until they satisfy the minBlobWidth and minBlobHeight restraints
466 do {
467 // Clear buffer.
468 fillGrid(grid, 0);
469
470 // Fill relevant portion with noise based on the percentSeeded argument.
471 for(i=0; i<maxBlobWidth; i++) {
472 for(j=0; j<maxBlobHeight; j++) {
473 grid[i][j] = (rand_percent(percentSeeded) ? 1 : 0);
474 }
475 }
476
477 // colorOverDungeon(&darkGray);
478 // hiliteGrid(grid, &white, 100);
479 // temporaryMessage("Random starting noise:", REQUIRE_ACKNOWLEDGMENT);
480
481 // Some iterations of cellular automata
482 for (k=0; k<roundCount; k++) {
483 cellularAutomataRound(grid, birthParameters, survivalParameters);
484
485 // colorOverDungeon(&darkGray);
486 // hiliteGrid(grid, &white, 100);
487 // temporaryMessage("Cellular automata progress:", REQUIRE_ACKNOWLEDGMENT);
488 }
489
490 // colorOverDungeon(&darkGray);
491 // hiliteGrid(grid, &white, 100);
492 // temporaryMessage("Cellular automata result:", REQUIRE_ACKNOWLEDGMENT);
493
494 // Now to measure the result. These are best-of variables; start them out at worst-case values.
495 topBlobSize = 0;
496 topBlobNumber = 0;
497 topBlobMinX = maxBlobWidth;
498 topBlobMaxX = 0;
499 topBlobMinY = maxBlobHeight;
500 topBlobMaxY = 0;
501
502 // Fill each blob with its own number, starting with 2 (since 1 means floor), and keeping track of the biggest:
503 blobNumber = 2;
504
505 for(i=0; i<DCOLS; i++) {
506 for(j=0; j<DROWS; j++) {
507 if (grid[i][j] == 1) { // an unmarked blob
508 // Mark all the cells and returns the total size:
509 blobSize = fillContiguousRegion(grid, i, j, blobNumber);
510 if (blobSize > topBlobSize) { // if this blob is a new record
511 topBlobSize = blobSize;
512 topBlobNumber = blobNumber;
513 }
514 blobNumber++;
515 }
516 }
517 }
518
519 // Figure out the top blob's height and width:
520 // First find the max & min x:
521 for(i=0; i<DCOLS; i++) {
522 foundACellThisLine = false;
523 for(j=0; j<DROWS; j++) {
524 if (grid[i][j] == topBlobNumber) {
525 foundACellThisLine = true;
526 break;
527 }
528 }
529 if (foundACellThisLine) {
530 if (i < topBlobMinX) {
531 topBlobMinX = i;
532 }
533 if (i > topBlobMaxX) {
534 topBlobMaxX = i;
535 }
536 }
537 }
538
539 // Then the max & min y:
540 for(j=0; j<DROWS; j++) {
541 foundACellThisLine = false;
542 for(i=0; i<DCOLS; i++) {
543 if (grid[i][j] == topBlobNumber) {
544 foundACellThisLine = true;
545 break;
546 }
547 }
548 if (foundACellThisLine) {
549 if (j < topBlobMinY) {
550 topBlobMinY = j;
551 }
552 if (j > topBlobMaxY) {
553 topBlobMaxY = j;
554 }
555 }
556 }
557
558 blobWidth = (topBlobMaxX - topBlobMinX) + 1;
559 blobHeight = (topBlobMaxY - topBlobMinY) + 1;
560
561 } while (blobWidth < minBlobWidth
562 || blobHeight < minBlobHeight
563 || topBlobNumber == 0);
564
565 // Replace the winning blob with 1's, and everything else with 0's:
566 for(i=0; i<DCOLS; i++) {
567 for(j=0; j<DROWS; j++) {
568 if (grid[i][j] == topBlobNumber) {
569 grid[i][j] = 1;
570 } else {
571 grid[i][j] = 0;
572 }
573 }
574 }
575
576 // Populate the returned variables.
577 *retMinX = topBlobMinX;
578 *retMinY = topBlobMinY;
579 *retWidth = blobWidth;
580 *retHeight = blobHeight;
581 }
582