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