001package squidpony.squidgrid.mapping; 002 003import squidpony.squidmath.Coord; 004import squidpony.squidmath.PoissonDisk; 005import squidpony.squidmath.RNG; 006 007import java.util.*; 008 009/** 010 * A variant on {@link MixedGenerator} that creates bi-radially symmetric maps (basically a yin-yang shape). Useful for 011 * strategy games and possibly competitive multi-player games. The Coords passed to constructors as room positions do 012 * not necessarily need to be 013 * 014 * Created by Tommy Ettinger on 11/20/2015. 015 */ 016public class SymmetryDungeonGenerator extends MixedGenerator { 017 018 public static LinkedHashMap<Coord, List<Coord>> removeSomeOverlap(int width, int height, List<Coord> sequence) 019 { 020 List<Coord> s2 = new ArrayList<>(sequence.size()); 021 for(Coord c : sequence) 022 { 023 if(c.x * 1.0 / width + c.y * 1.0 / height <= 1.0) 024 s2.add(c); 025 } 026 return listToMap(s2); 027 } 028 public static LinkedHashMap<Coord, List<Coord>> removeSomeOverlap(int width, int height, LinkedHashMap<Coord, List<Coord>> connections) { 029 LinkedHashMap<Coord, List<Coord>> lhm2 = new LinkedHashMap<>(connections.size()); 030 Set<Coord> keyset = connections.keySet(), newkeys = new LinkedHashSet<>(connections.size()); 031 for (Coord c : keyset) { 032 if (c.x * 1.0 / width + c.y * 1.0 / height <= 1.0) { 033 newkeys.add(c); 034 } 035 } 036 Coord[] keys = newkeys.toArray(new Coord[newkeys.size()]); 037 for (int i = 0; i < keys.length; i++) { 038 Coord c = keys[i]; 039 if (c.x * 1.0 / width + c.y * 1.0 / height <= 1.0) { 040 List<Coord> cs = new ArrayList<>(4); 041 for (Coord c2 : connections.get(c)) { 042 if (c2.x * 1.0 / width + c2.y * 1.0 / height <= 1.0) { 043 cs.add(c2); 044 } else if (keys[(i + 1) % keys.length].x * 1.0 / width + 045 keys[(i + 1) % keys.length].y * 1.0 / height <= 1.0) { 046 cs.add(keys[(i + 1) % keys.length]); 047 } 048 049 } 050 lhm2.put(c, cs); 051 } 052 } 053 return lhm2; 054 } 055 /** 056 * This prepares a map generator that will generate a map with the given width and height, using the given RNG. 057 * This version of the constructor uses Poisson Disk sampling to generate the points it will draw caves and 058 * corridors between, ensuring a minimum distance between points, but it does not ensure that paths between points 059 * will avoid overlapping with rooms or other paths. You call the different carver-adding methods to affect what the 060 * dungeon will look like, putCaveCarvers(), putBoxRoomCarvers(), and putRoundRoomCarvers(), defaulting to only 061 * caves if none are called. You call generate() after adding carvers, which returns a char[][] for a map. 062 * 063 * @param width the width of the final map in cells 064 * @param height the height of the final map in cells 065 * @param rng an RNG object to use for random choices; this make a lot of random choices. 066 * @see PoissonDisk used to ensure spacing for the points. 067 */ 068 public SymmetryDungeonGenerator(int width, int height, RNG rng) { 069 this(width, height, rng, basicPoints(width, height, rng)); 070 } 071 072 /** 073 * This prepares a map generator that will generate a map with the given width and height, using the given RNG. 074 * This version of the constructor uses a List of Coord points from some other source to determine the path to add 075 * rooms or caves to and then connect. You call the different carver-adding methods to affect what the 076 * dungeon will look like, putCaveCarvers(), putBoxRoomCarvers(), and putRoundRoomCarvers(), defaulting to only 077 * caves if none are called. You call generate() after adding carvers, which returns a char[][] for a map. 078 * 079 * @param width the width of the final map in cells 080 * @param height the height of the final map in cells 081 * @param rng an RNG object to use for random choices; this make a lot of random choices. 082 * @param sequence a List of Coord to connect in order; index 0 is the start, index size() - 1 is the end. 083 * @see SerpentMapGenerator a class that uses this technique 084 */ 085 public SymmetryDungeonGenerator(int width, int height, RNG rng, List<Coord> sequence) { 086 this(width, height, rng, listToMap((sequence)), 1f); 087 } 088 089 /** 090 * This prepares a map generator that will generate a map with the given width and height, using the given RNG. 091 * This version of the constructor uses a LinkedHashMap with Coord keys and Coord array values to determine a 092 * branching path for the dungeon to take; each key will connect once to each of the Coords in its value, and you 093 * usually don't want to connect in both directions. You call the different carver-adding methods to affect what the 094 * dungeon will look like, putCaveCarvers(), putBoxRoomCarvers(), and putRoundRoomCarvers(), defaulting to only 095 * caves if none are called. You call generate() after adding carvers, which returns a char[][] for a map. 096 * 097 * @param width the width of the final map in cells 098 * @param height the height of the final map in cells 099 * @param rng an RNG object to use for random choices; this make a lot of random choices. 100 * @param connections a Map of Coord keys to arrays of Coord to connect to next; shouldn't connect both ways 101 * @see SerpentMapGenerator a class that uses this technique 102 */ 103 public SymmetryDungeonGenerator(int width, int height, RNG rng, LinkedHashMap<Coord, List<Coord>> connections) { 104 this(width, height, rng, connections, 0.8f); 105 } 106 107 /** 108 * This prepares a map generator that will generate a map with the given width and height, using the given RNG. 109 * This version of the constructor uses a LinkedHashMap with Coord keys and Coord array values to determine a 110 * branching path for the dungeon to take; each key will connect once to each of the Coords in its value, and you 111 * usually don't want to connect in both directions. You call the different carver-adding methods to affect what the 112 * dungeon will look like, putCaveCarvers(), putBoxRoomCarvers(), and putRoundRoomCarvers(), defaulting to only 113 * caves if none are called. You call generate() after adding carvers, which returns a char[][] for a map. 114 * 115 * @param width the width of the final map in cells 116 * @param height the height of the final map in cells 117 * @param rng an RNG object to use for random choices; this make a lot of random choices. 118 * @param connections a Map of Coord keys to arrays of Coord to connect to next; shouldn't connect both ways 119 * @param roomSizeMultiplier a float multiplier that will be applied to each room's width and height 120 * @see SerpentMapGenerator a class that uses this technique 121 */ 122 public SymmetryDungeonGenerator(int width, int height, RNG rng, LinkedHashMap<Coord, List<Coord>> connections, float roomSizeMultiplier) { 123 super(width, height, rng, crossConnect(width, height, connections), roomSizeMultiplier); 124 } 125 126 protected static LinkedHashMap<Coord, List<Coord>> listToMap(List<Coord> sequence) 127 { 128 LinkedHashMap<Coord, List<Coord>> conns = new LinkedHashMap<>(sequence.size() - 1); 129 for (int i = 0; i < sequence.size() - 1; i++) { 130 Coord c1 = sequence.get(i), c2 = sequence.get(i+1); 131 List<Coord> cs = new ArrayList<>(1); 132 cs.add(c2); 133 conns.put(c1, cs); 134 } 135 return conns; 136 } 137 138 protected static LinkedHashMap<Coord, List<Coord>> crossConnect(int width, int height, LinkedHashMap<Coord, List<Coord>> connections) 139 { 140 LinkedHashMap<Coord, List<Coord>> conns = new LinkedHashMap<>(connections.size()); 141 for(Map.Entry<Coord, List<Coord>> entry : connections.entrySet()) 142 { 143 conns.put(entry.getKey(), new ArrayList<>(entry.getValue())); 144 } 145 double lowest1 = 9999, lowest2 = 9999, lowest3 = 9999, lowest4 = 9999; 146 Coord l1 = null, l2 = null, l3 = null, l4 = null, r1 = null, r2 = null, r3 = null, r4 = null; 147 for(List<Coord> left : connections.values()) 148 { 149 for(List<Coord> right : connections.values()) 150 { 151 for(Coord lc : left) 152 { 153 for(Coord rc : right) 154 { 155 Coord rc2 = Coord.get(width - 1 - rc.x, height - 1 - rc.y); 156 double dist = lc.distance(rc2); 157 if(dist < 0.001) 158 continue; 159 if(dist < lowest1) 160 { 161 lowest1 = dist; 162 l1 = lc; 163 r1 = rc2; 164 } 165 else if(dist < lowest2 && !lc.equals(l1) && !rc2.equals(r1)) 166 { 167 lowest2 = dist; 168 l2 = lc; 169 r2 = rc2; 170 } 171 else if(dist < lowest3 172 && !lc.equals(l1) && !rc2.equals(r1) && !lc.equals(l2) && !rc2.equals(r2)) 173 { 174 lowest3 = dist; 175 l3 = lc; 176 r3 = rc2; 177 } 178 else if(dist < lowest4 179 && !lc.equals(l1) && !rc2.equals(r1) 180 && !lc.equals(l2) && !rc2.equals(r2) 181 && !lc.equals(l3) && !rc2.equals(r3)) 182 { 183 lowest4 = dist; 184 l4 = lc; 185 r4 = rc2; 186 } 187 } 188 } 189 } 190 } 191 if(l1 != null && r1 != null) 192 { 193 if(conns.containsKey(l1)) 194 { 195 conns.get(l1).add(r1); 196 } 197 else if(conns.containsKey(r1)) 198 { 199 conns.get(r1).add(l1); 200 } 201 } 202 if(l2 != null && r2 != null) 203 { 204 if(conns.containsKey(l2)) 205 { 206 conns.get(l2).add(r2); 207 } 208 else if(conns.containsKey(r2)) 209 { 210 conns.get(r2).add(l2); 211 } 212 } 213 if(l3 != null && r3 != null) 214 { 215 if(conns.containsKey(l3)) 216 { 217 conns.get(l3).add(r3); 218 } 219 else if(conns.containsKey(r3)) 220 { 221 conns.get(r3).add(l3); 222 } 223 } 224 if(l4 != null && r4 != null) 225 { 226 if(conns.containsKey(l4)) 227 { 228 conns.get(l4).add(r4); 229 } 230 else if(conns.containsKey(r4)) 231 { 232 conns.get(r4).add(l4); 233 } 234 } 235 return conns; 236 } 237 /** 238 * Internal use. Marks a point to be made into floor. 239 * 240 * @param x x position to mark 241 * @param y y position to mark 242 * @return false if everything is normal, true if and only if this failed to mark because the position is walled 243 */ 244 @Override 245 protected boolean mark(int x, int y) { 246 return super.mark(x, y) || super.mark(width - 1 - x, height - 1 - y); 247 } 248 249 /** 250 * Internal use. Marks a point to be made into floor. 251 * 252 * @param x x position to mark 253 * @param y y position to mark 254 */ 255 @Override 256 protected void markPiercing(int x, int y) { 257 super.markPiercing(x, y); 258 super.markPiercing(width - 1 - x, height - 1 - y); 259 } 260 261 /** 262 * Internal use. Marks a point to be made into floor. 263 * 264 * @param x x position to mark 265 * @param y y position to mark 266 */ 267 @Override 268 protected void wallOff(int x, int y) { 269 super.wallOff(x, y); 270 super.wallOff(width - 1 - x, height - 1 - y); 271 } 272 /** 273 * Internal use. Marks a point's environment type as the appropriate kind of environment. 274 * @param x x position to mark 275 * @param y y position to mark 276 * @param kind an int that should be one of the constants in MixedGenerator for environment types. 277 */ 278 @Override 279 protected void markEnvironment(int x, int y, int kind) { 280 super.markEnvironment(x, y, kind); 281 super.markEnvironment(width - 1 - x, height - 1 - y, kind); 282 } 283}