1 /*
2  *  This file is part of Dune Legacy.
3  *
4  *  Dune Legacy is free software: you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation, either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  Dune Legacy is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with Dune Legacy.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include <MapEditor/MapGenerator.h>
19 
20 #include <globals.h>
21 
22 #include <misc/Random.h>
23 
24 #include <memory>
25 
26 
27 #define ROCKFILLER 2        //how many times random generator will try to remove sand "holes" for rock from the map
28 #define SPICEFILLER 2       //for spice
29 #define DUNESFILLER 1
30 
31 class MapGenerator {
32 
33 public:
34 
MapGenerator(int sizeX,int sizeY,int randSeed,int rockfields=ROCKFIELDS,int spicefields=SPICEFIELDS,MirrorMode mirrorMode=MirrorModeNone)35     MapGenerator(int sizeX, int sizeY, int randSeed, int rockfields = ROCKFIELDS, int spicefields = SPICEFIELDS, MirrorMode mirrorMode = MirrorModeNone)
36      : map(sizeX, sizeY), randGen(randSeed), rockfields(rockfields), spicefields(spicefields) {
37          mapMirror = std::shared_ptr<MapMirror>(MapMirror::createMapMirror(mirrorMode, sizeX, sizeY));
38     }
39 
getMap()40     MapData& getMap() {
41         return map;
42     }
43 
44     /**
45         Creates a random map
46     */
generateMap()47     void generateMap() {
48         // the whole map shall be of type Terrain_Sand
49 
50         for(int i = 0; i < rockfields; i++) {
51             int spotX = randGen.rand(0, map.getSizeX()-1);
52             int spotY = randGen.rand(0, map.getSizeY()-1);
53 
54             makeSpot(spotX, spotY, Terrain_Rock);
55         }
56 
57         for(int i = 0; i < ROCKFILLER; i++) {
58             thickSpots(Terrain_Rock); //SPOT ROCK
59         }
60 
61         // Spice fields
62         for(int i = 0; i < spicefields; i++) {
63             int spotX = randGen.rand(0, map.getSizeX()-1);
64             int spotY = randGen.rand(0, map.getSizeY()-1);
65 
66             makeSpot(spotX, spotY, Terrain_Spice);
67         }
68 
69         for(int i = 0; i < SPICEFILLER; i++) {
70             thickSpots(Terrain_Spice);
71         }
72 
73         for(int i = 0; i < SPICEFILLER; i++) {
74             thickThickSpiceSpots();
75         }
76 
77         // Spice fields
78         for(int i = 0; i < DUNEFIELDS; i++) {
79             int spotX = randGen.rand(0, map.getSizeX()-1);
80             int spotY = randGen.rand(0, map.getSizeY()-1);
81 
82             makeSpot(spotX, spotY, Terrain_Dunes);
83         }
84 
85         for(int i = 0; i < DUNESFILLER; i++) {
86             thickSpots(Terrain_Dunes);
87         }
88 
89         addRockBits(randGen.rand(0,9));
90         addSpiceBlooms(randGen.rand(0,9));
91 
92 
93     }
94 
95 private:
96 
fixTileCoordinate(int & x,int & y)97     bool fixTileCoordinate(int& x, int& y) {
98         bool error = false;
99 
100         if(x < 0) {
101             x = 0;
102             error = true;
103         } else if(x >= map.getSizeX()) {
104             x = map.getSizeX() - 1;
105             error = true;
106         }
107 
108         if(y < 0) {
109             y = 0;
110             error = true;
111         } else if(y >= map.getSizeY()) {
112             y = map.getSizeY() - 1;
113             error = true;
114         }
115 
116         return error;
117     }
118 
119     /**
120         Checks if the tile to the left of (x,y) is of type tile
121         \param x    x-coordinate in tile coordinates
122         \param y    y-coordinate in tile coordinates
123         \param type  the tile type to check
124         \return true if of tile type, false otherwise
125     */
onLeft(int x,int y,TERRAINTYPE type)126     bool onLeft(int x, int y, TERRAINTYPE type) {
127         x--;
128         fixTileCoordinate(x, y);
129 
130         return (map(x,y) == type);
131     }
132 
133     /**
134         Checks if the tile to the right of (x,y) is of type tile
135         \param x x-coordinate in tile coordinates
136         \param y y-coordinate in tile coordinates
137         \param type  the tile type to check
138         \return true if of tile type, false otherwise
139     */
onRight(int x,int y,TERRAINTYPE type)140     bool onRight(int x, int y, TERRAINTYPE type) {
141         x++;
142         fixTileCoordinate(x, y);
143 
144         return (map(x,y) == type);
145     }
146 
147     /**
148         Checks if the tile above (x,y) is of type tile
149         \param x    x-coordinate in tile coordinates
150         \param y    y-coordinate in tile coordinates
151         \param type  the tile type to check
152         \return true if of tile type, false otherwise
153     */
onUp(int x,int y,TERRAINTYPE type)154     bool onUp(int x, int y, TERRAINTYPE type) {
155         y--;
156         fixTileCoordinate(x, y);
157 
158         return (map(x,y) == type);
159     }
160 
161     /**
162         Checks if the tile below (x,y) is of type tile
163         \param x    x-coordinate in tile coordinates
164         \param y    y-coordinate in tile coordinates
165         \param type  the tile type to check
166         \return true if of tile type, false otherwise
167     */
onDown(int x,int y,TERRAINTYPE type)168     bool onDown(int x, int y, TERRAINTYPE type) {
169         y++;
170         fixTileCoordinate(x, y);
171 
172         return (map(x,y) == type);
173     }
174 
175     /**
176         Count how many tiles around (x,y) are of type tile
177         \param x    x-coordinate in tile coordinates
178         \param y    y-coordinate in tile coordinates
179         \param type  the tile type to check
180         \return number of surounding tiles of tile type (0 to 4)
181     */
side4(int x,int y,TERRAINTYPE type)182     int side4(int x, int y, TERRAINTYPE type) {
183         // Check at 4 sides for 'tile'
184         int flag = 0;
185 
186         if(onLeft(x, y, type))      flag++;
187         if(onRight(x, y, type))     flag++;
188         if(onUp(x, y, type))        flag++;
189         if(onDown(x, y, type))      flag++;
190 
191         return flag;
192     }
193 
194 
195 
196     /**
197         Removes holes in rock and spice
198         \param type  the type to remove holes from
199     */
thickSpots(TERRAINTYPE type)200     void thickSpots(TERRAINTYPE type) {
201         for(int i = 0; i < map.getSizeX(); i++) {
202             for(int j = 0; j < map.getSizeY(); j++) {
203                 if(map(i,j) != type) {
204                     // Found something else than what thickining
205 
206                     if(side4(i, j, type) >= 3) {
207                         // Seems enough of the type around it so make this also of this type
208                         for(int m=0; m < mapMirror->getSize(); m++) {
209                             Coord position = mapMirror->getCoord(Coord(i, j), m);
210                             map(position.x,position.y) = type;
211                         }
212                     }
213 
214                     if(side4(i, j, type) == 2) {
215                         // Gamble, fifty fifty... set this type or not?
216                         if(randGen.rand(0,1) == 1) {
217                             for(int m=0; m < mapMirror->getSize(); m++) {
218                                 Coord position = mapMirror->getCoord(Coord(i, j), m);
219                                 map(position.x,position.y) = type;
220                             }
221                         }
222                     }
223                 }
224             }
225         }
226     }
227 
228 
229     /**
230         Removes holes in thick spice
231     */
thickThickSpiceSpots()232     void thickThickSpiceSpots() {
233         for(int i = 0; i < map.getSizeX(); i++) {
234             for(int j = 0; j < map.getSizeY(); j++) {
235 
236                 int numSpiceTiles = side4(i,j,Terrain_Spice)+side4(i,j,Terrain_ThickSpice);
237 
238                 if(map(i,j) != Terrain_ThickSpice && (numSpiceTiles>=4)) {
239                     // Found something else than what thickining
240 
241                     if(side4(i, j, Terrain_ThickSpice) >= 3) {
242                         // Seems enough of ThickSpice around it so make this also ThickSpice
243                         for(int m=0; m < mapMirror->getSize(); m++) {
244                             Coord position = mapMirror->getCoord(Coord(i, j), m);
245                             map(position.x,position.y) = Terrain_ThickSpice;
246                         }
247                     }
248 
249                     if(side4(i, j, Terrain_ThickSpice) == 2) {
250                         // Gamble, fifty fifty... set this to ThickSpice or not?
251                         if(randGen.rand(0,1) == 1) {
252                             for(int m=0; m < mapMirror->getSize(); m++) {
253                                 Coord position = mapMirror->getCoord(Coord(i, j), m);
254                                 map(position.x,position.y) = Terrain_ThickSpice;
255                             }
256                         }
257                     }
258                 }
259             }
260         }
261     }
262 
263     /**
264         This function creates a spot of type type.
265         \param x        the x coordinate in tile coordinates to start making the spot
266         \param y        the y coordinate in tile coordinates to start making the spot
267         \param type      type of the spot
268     */
makeSpot(int x,int y,TERRAINTYPE type)269     void makeSpot(int x, int y, TERRAINTYPE type) {
270         int spotSize = (640 * map.getSizeX()*map.getSizeY())/(64*64);
271         for(int j = 0; j < spotSize; j++) {
272             int dir = randGen.rand(0,3);    // Random Dir
273 
274             switch(dir) {
275                 case 0 : x--; break;
276                 case 1 : x++; break;
277                 case 2 : y--; break;
278                 case 3 : y++; break;
279             }
280 
281             fixTileCoordinate(x, y);
282 
283             TERRAINTYPE type2Place = type;
284 
285             if(type == Terrain_Spice) {
286                 if(map(x,y) == Terrain_Rock) {
287                     // Do not place the spice spot, priority is ROCK!
288                     continue;
289                 } else if((map(x,y) == Terrain_Spice) && ((side4(x,y,Terrain_Spice)+side4(x,y,Terrain_ThickSpice)) >= 4)) {
290                     // "upgrade" spice to thick spice
291                     type2Place = Terrain_ThickSpice;
292                 } else if(map(x,y) == Terrain_ThickSpice) {
293                     // do not "downgrade" thick spice to spice
294                     type2Place = Terrain_ThickSpice;
295                 }
296             } else if(type == Terrain_Dunes) {
297                 if(map(x,y) != Terrain_Sand) {
298                     // Do not place the dunes spot, priority is ROCK and SPICE!
299                     continue;
300                 }
301             }
302 
303 
304 
305             for(int m=0; m < mapMirror->getSize(); m++) {
306                 Coord position = mapMirror->getCoord(Coord(x, y), m);
307                 map(position.x,position.y) = type2Place;
308             }
309         }
310     }
311 
312 
313     /**
314         Adds amount number of rock tiles to the map
315         \param amount the number of rock tiles to add
316     */
addRockBits(int amount)317     void addRockBits(int amount) {
318         int done = 0;
319         for(int j = 0; (done < amount) && (j < 1000) ; j++) {
320             int spotX = randGen.rand(0, map.getSizeX()-1);
321             int spotY = randGen.rand(0, map.getSizeY()-1);
322 
323             if(map(spotX, spotY) == Terrain_Sand) {
324                 for(int m=0; m < mapMirror->getSize(); m++) {
325                     Coord position = mapMirror->getCoord(Coord(spotX, spotY), m);
326                     map(position.x,position.y) = Terrain_Rock;
327                 }
328                 done++;
329             }
330         }
331     }
332 
333     /**
334         Adds amount number of spice blooms to the map
335         \param amount the number of spice blooms to add
336     */
addSpiceBlooms(int amount)337     void addSpiceBlooms(int amount) {
338         int done = 0;
339         for(int j = 0; (done < amount) && (j < 1000) ; j++) {
340             int spotX = randGen.rand(0, map.getSizeX()-1);
341             int spotY = randGen.rand(0, map.getSizeY()-1);
342 
343             if(map(spotX, spotY) == Terrain_Sand) {
344                 for(int m=0; m < mapMirror->getSize(); m++) {
345                     Coord position = mapMirror->getCoord(Coord(spotX, spotY), m);
346                     map(position.x,position.y) = Terrain_SpiceBloom;
347                 }
348                 done++;
349             }
350         }
351     }
352 
353 private:
354     MapData map;
355 
356     Random randGen;
357 
358     int rockfields;
359     int spicefields;
360 
361     std::shared_ptr<MapMirror>      mapMirror;
362 };
363 
364 /**
365     Creates a random map
366     \param sizeX        width of the new map (in tiles)
367     \param sizeY        height of the new map (in tiles)
368     \param randSeed     the seed value for the random generator
369     \param rockfields   num rock fields to add
370     \param spicefields  num spice fields to add
371     \return the generated map
372 */
generateRandomMap(int sizeX,int sizeY,int randSeed,int rockfields,int spicefields,MirrorMode mirrorMode)373 MapData generateRandomMap(int sizeX, int sizeY, int randSeed, int rockfields, int spicefields, MirrorMode mirrorMode) {
374     MapGenerator mapGenerator(sizeX, sizeY, randSeed, rockfields, spicefields, mirrorMode);
375 
376     mapGenerator.generateMap();
377 
378     return mapGenerator.getMap();
379 }
380