001package squidpony.squidgrid.mapping; 002 003import squidpony.squidmath.Coord; 004import squidpony.squidmath.FastNoise; 005import squidpony.squidmath.GWTRNG; 006import squidpony.squidmath.GreasedRegion; 007import squidpony.squidmath.IRNG; 008import squidpony.squidmath.WobblyLine; 009 010import java.util.ArrayList; 011import java.util.Collections; 012import java.util.Comparator; 013import java.util.ListIterator; 014 015/** 016 * Map generator using Simplex noise for the formation of "rooms" and then WobblyLine to connect with corridors. 017 * Created by Tommy Ettinger on 4/18/2016. 018 */ 019public class OrganicMapGenerator implements IDungeonGenerator { 020 public char[][] map; 021 public int[][] environment; 022 public GreasedRegion floors; 023 public IRNG rng; 024 protected int width, height; 025 public double noiseMin, noiseMax; 026 protected FastNoise noise; 027 private static final Comparator<GreasedRegion> sizeComparator = new Comparator<GreasedRegion>() { 028 @Override 029 public int compare(GreasedRegion o1, GreasedRegion o2) { 030 return o2.size() - o1.size(); 031 } 032 }; 033 034 public OrganicMapGenerator() 035 { 036 this(0.55, 0.65, 80, 30, new GWTRNG()); 037 } 038 public OrganicMapGenerator(int width, int height) 039 { 040 this(0.55, 0.65, width, height, new GWTRNG()); 041 } 042 public OrganicMapGenerator(int width, int height, IRNG rng) 043 { 044 this(0.55, 0.65, width, height, rng); 045 } 046 public OrganicMapGenerator(double noiseMin, double noiseMax, int width, int height, IRNG rng) 047 { 048 this.rng = rng; 049 this.width = Math.max(3, width); 050 this.height = Math.max(3, height); 051 this.noiseMin = Math.min(0.9, Math.max(-1.0, noiseMin - 0.1)); 052 this.noiseMax = Math.min(1.0, Math.max(noiseMin + 0.05, noiseMax + 0.1)); 053 map = new char[this.width][this.height]; 054 environment = new int[this.width][this.height]; 055 floors = new GreasedRegion(width, height); 056 noise = new FastNoise(1, 0.333f, FastNoise.SIMPLEX_FRACTAL, 2); 057 } 058 059 /** 060 * Generate a map as a 2D char array using the width and height specified in the constructor. 061 * Should produce an organic, cave-like map. 062 * @return a 2D char array for the map that should be organic-looking. 063 */ 064 public char[][] generate() 065 { 066 double temp; 067 int frustration = 0; 068 while (frustration < 10) { 069 noise.setSeed(rng.nextInt()); 070 floors.clear(); 071 for (int x = 0; x < width; x++) { 072 for (int y = 0; y < height; y++) { 073 map[x][y] = '#'; 074 temp = noise.getConfiguredNoise(x, y); 075 if (temp >= noiseMin && temp <= noiseMax) { 076 floors.insert(x, y); 077 } 078 } 079 } 080 if (floors.size() < width * height * 0.1f) { 081 frustration++; 082 continue; 083 } 084 break; 085 } 086 if(frustration >= 10) { 087 noiseMin = Math.min(0.9, Math.max(-1.0, noiseMin - 0.05)); 088 noiseMax = Math.min(1.0, Math.max(noiseMin + 0.05, noiseMax + 0.05)); 089 return generate(); 090 } 091 ArrayList<GreasedRegion> regions = floors.split(); 092 GreasedRegion region, linking; 093 ArrayList<Coord> path; 094 Coord start, end; 095 Collections.sort(regions, sizeComparator); 096 ListIterator<GreasedRegion> ri = regions.listIterator(); 097 int ctr = 0, rs = regions.size() >> 1, pos = 0; 098 while (ri.hasNext()) 099 { 100 region = ri.next(); 101 if(pos++ > rs || region.size() <= 5) 102 ri.remove(); 103 else { 104 region.expand().inverseMask(map, '.'); 105 ctr += region.size(); 106 } 107 } 108 int oldSize = regions.size(); 109 if(oldSize < 4 || ctr < width * height * 0.1f) { 110 noiseMin = Math.min(0.9, Math.max(-1.0, noiseMin - 0.05)); 111 noiseMax = Math.min(1.0, Math.max(noiseMin + 0.05, noiseMax + 0.05)); 112 return generate(); 113 } 114 115 for (int x = 0; x < width; x++) { 116 for (int y = 0; y < height; y++) { 117 environment[x][y] = (map[x][y] == '.') ? DungeonUtility.CAVE_FLOOR : DungeonUtility.CAVE_WALL; 118 } 119 } 120 rng.shuffleInPlace(regions); 121 while (regions.size() > 1) 122 { 123 124 region = regions.remove(regions.size() - 1); 125 linking = regions.get(regions.size() - 1); 126 start = region.singleRandom(rng); 127 end = linking.singleRandom(rng); 128 path = WobblyLine.line(start.x, start.y, end.x, end.y, width, height, 0.75, rng); 129 Coord elem; 130 for (int i = 0; i < path.size(); i++) { 131 elem = path.get(i); 132 if(elem.x < width && elem.y < height) { 133 if (map[elem.x][elem.y] == '#') { 134 map[elem.x][elem.y] = '.'; 135 environment[elem.x][elem.y] = DungeonUtility.CORRIDOR_FLOOR; 136 ctr++; 137 } 138 } 139 } 140 } 141 int upperY = height - 1; 142 int upperX = width - 1; 143 for (int i = 0; i < width; i++) { 144 map[i][0] = '#'; 145 map[i][upperY] = '#'; 146 environment[i][0] = DungeonUtility.UNTOUCHED; 147 environment[i][upperY] = DungeonUtility.UNTOUCHED; 148 } 149 for (int i = 0; i < height; i++) { 150 map[0][i] = '#'; 151 map[upperX][i] = '#'; 152 environment[0][i] = DungeonUtility.UNTOUCHED; 153 environment[upperX][i] = DungeonUtility.UNTOUCHED; 154 } 155 156 if(ctr < width * height * 0.1f) { 157 noiseMin = Math.min(0.9, Math.max(-1.0, noiseMin - 0.05)); 158 noiseMax = Math.min(1.0, Math.max(noiseMin + 0.05, noiseMax + 0.05)); 159 return generate(); 160 } 161 return map; 162 } 163 164 165 /** 166 * Gets a 2D array of int constants, each representing a type of environment corresponding to a static field of 167 * MixedGenerator. This array will have the same size as the last char 2D array produced by generate(); the value 168 * of this method if called before generate() is undefined, but probably will be a 2D array of all 0 (UNTOUCHED). 169 * <ul> 170 * <li>MixedGenerator.UNTOUCHED, equal to 0, is used for any cells that aren't near a floor.</li> 171 * <li>MixedGenerator.ROOM_FLOOR, equal to 1, is used for floor cells inside wide room areas.</li> 172 * <li>MixedGenerator.ROOM_WALL, equal to 2, is used for wall cells around wide room areas.</li> 173 * <li>MixedGenerator.CAVE_FLOOR, equal to 3, is used for floor cells inside rough cave areas.</li> 174 * <li>MixedGenerator.CAVE_WALL, equal to 4, is used for wall cells around rough cave areas.</li> 175 * <li>MixedGenerator.CORRIDOR_FLOOR, equal to 5, is used for floor cells inside narrow corridor areas.</li> 176 * <li>MixedGenerator.CORRIDOR_WALL, equal to 6, is used for wall cells around narrow corridor areas.</li> 177 * </ul> 178 * @return a 2D int array where each element is an environment type constant in MixedGenerator 179 */ 180 public int[][] getEnvironment() 181 { 182 return environment; 183 } 184 185 public char[][] getDungeon() { 186 return map; 187 } 188 public int getWidth() { 189 return width; 190 } 191 192 public void setWidth(int width) { 193 this.width = width; 194 } 195 196 public int getHeight() { 197 return height; 198 } 199 200 public void setHeight(int height) { 201 this.height = height; 202 } 203 204 public double getNoiseMin() { 205 return noiseMin; 206 } 207 208 public void setNoiseMin(double noiseMin) { 209 this.noiseMin = noiseMin; 210 } 211 212 public double getNoiseMax() { 213 return noiseMax; 214 } 215 216 public void setNoiseMax(double noiseMax) { 217 this.noiseMax = noiseMax; 218 } 219 220}