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