001package squidpony.squidmath; 002 003import squidpony.squidgrid.mapping.DungeonUtility; 004 005import java.io.Serializable; 006import java.util.AbstractList; 007import java.util.Collection; 008 009import static squidpony.squidmath.CoordPacker.*; 010/** 011 * <b>NOTE</b>: You should consider {@link GreasedRegion} before using this class, unless you know Region does exactly 012 * what you want. 013 * <br> 014 * Represents an area or series of areas as one logical unit, and allows iterating over or altering that unit. 015 * This can be useful for getting an iterable data structure from a FOV map, Dijkstra map, or just a map from a dungeon 016 * generator. It can also work with some shapes (circles and rectangles). 017 * Regions must be no larger than 256x256 (actually, the Coords in them should fit in that), even if Coord has had its 018 * expandPool() method called. This is because CoordPacker relies on that maximum size to significantly improve 019 * efficiency, and this is really just a convenience class wrapping CoordPacker to avoid some of its complexity. 020 * GreasedRegion allows larger maps, particularly when the Coord pool has been expanded, and should be much faster at 021 * tasks that involve changing the size or shape of an area. It also is mutable by default, without needing to track 022 * the "dirty" and "clean" states of this class. GreasedRegion does not implement the List interface, though; it does 023 * implement Iterable of Coord. 024 * <br> 025 * Created by Tommy Ettinger on 5/12/2016. 026 * @see GreasedRegion You should prefer GreasedRegion for most usage. 027 */ 028public class Region extends AbstractList<Coord> implements Serializable{ 029 030 private static final long serialVersionUID = 4015272367863327093L; 031 032 protected short[] raw; 033 protected Coord[] coords; 034 private boolean dirty; 035 036 /** 037 * A constructor for a Region that takes a 2D double array, usually the kind produced by FOV, and stores only Coord 038 * positions that correspond to values greater than 0.0 (actually, greater than epsilon, which here is 0.0001). 039 * This won't behave as-expected if you give it a double[][] that DijkstraMap produces; there's a different 040 * constructor for that purpose. 041 * @param fovMap a 2D double array as produced by FOV 042 */ 043 public Region(double[][] fovMap) 044 { 045 CoordPacker.init(); 046 raw = pack(fovMap); 047 coords = allPacked(raw); 048 dirty = false; 049 } 050 051 /** 052 * A constructor for a Region that takes a 2D double array, usually produced by DijkstraMap, and a maximum value, 053 * and stores only Coord positions that correspond to values no greater than maximum. 054 * This won't behave as-expected if you give it a double[][] that FOV produces; there's a different 055 * constructor for that purpose. 056 * @param dijkstraMap a 2D double array as produced by DijkstraMap 057 * @param maximum the highest value that a position can have in dijkstraMap and still be given a Coord in this 058 */ 059 public Region(double[][] dijkstraMap, double maximum) 060 { 061 CoordPacker.init(); 062 raw = pack(dijkstraMap, maximum); 063 coords = allPacked(raw); 064 dirty = false; 065 } 066 067 /** 068 * A constructor for a Region that takes a 2D char array, the kind produced by most map/dungeon generators in this 069 * library, and a vararg or array of char that will have their Coord positions used where those chars appear in map. 070 * This is optimized for the common case of a single char in using if you only want to, for example, store '.' to 071 * make a Region of floors, or '#' for walls. 072 * @param map a 2D char array that is usually the kind returned by a dungeon or map generator 073 * @param using an array or vararg of char that will have their Coords used where they appear in map 074 */ 075 public Region(char[][] map, char... using) 076 { 077 CoordPacker.init(); 078 raw = pack(map, using); 079 coords = allPacked(raw); 080 dirty = false; 081 } 082 083 /** 084 * A constructor for a Region that takes an array or vararg of Coord and encodes all of them in the Region. 085 * @param points an array or vararg of Coord that will be stored in this Region, none can be null 086 */ 087 public Region(Coord... points) 088 { 089 CoordPacker.init(); 090 raw = packSeveral(points); 091 coords = allPacked(raw); 092 dirty = false; 093 } 094 /** 095 * A constructor for a Region that takes a Collection of Coord, such as a List or Set, and encodes all of them in 096 * the Region. 097 * @param points a Collection of Coord that will be stored in this Region, none can be null 098 */ 099 public Region(Collection<Coord> points) 100 { 101 CoordPacker.init(); 102 raw = packSeveral(points); 103 coords = allPacked(raw); 104 dirty = false; 105 } 106 107 /** 108 * A constructor that copies another Region so this Region will have the same contents. If the other Region is 109 * "dirty", this won't perform "cleanup" on it, but will ensure that this Region is "clean" at creation. 110 * None of the reference types in other will be used directly in this Region, so changes made to this Region won't 111 * be reflected in other. 112 * @param other another Region to copy into this one 113 */ 114 public Region(Region other) 115 { 116 CoordPacker.init(); 117 raw = new short[other.raw.length]; 118 System.arraycopy(other.raw, 0, raw, 0, raw.length); 119 if(other.dirty) 120 { 121 coords = allPacked(raw); 122 } 123 else 124 { 125 coords = new Coord[other.coords.length]; 126 System.arraycopy(other.coords, 0, coords, 0, coords.length); 127 } 128 dirty = false; 129 130 } 131 132 /** 133 * A constructor for a circular Region (possibly truncated at the edges) with a Coord center, an int radius, and a 134 * maximum width and height that the Coords in this Region will not exceed. 135 * @param center the center of the circular Region 136 * @param circleRadius the radius as an int 137 * @param mapWidth one more than the maximum x-position of any Coord this will contain 138 * @param mapHeight one more than the maximum y-position of any Coord this will contain 139 */ 140 public Region(Coord center, int circleRadius, int mapWidth, int mapHeight) 141 { 142 CoordPacker.init(); 143 raw = circle(center, circleRadius, mapWidth, mapHeight); 144 coords = allPacked(raw); 145 dirty = false; 146 } 147 148 /** 149 * A constructor for a rectangular Region that stores Coords for the area from (minX,minY) at the minimum corner to 150 * (width + minX - 1, height + minY - 1) at the maximum corner. All parameters should be non-negative and less than 151 * 256, and height and width will be reduced if a Coord in the rectangle would extend to 256 in either dimension. 152 * This doesn't take two Coords as parameters because that would be confusing with the constructor that takes a 153 * vararg or array of Coord for its parameters. 154 * @param minX lowest x-coordinate in the rectangle; should be between 0 and 255 155 * @param minY lowest y-coordinate in the rectangle; should be between 0 and 255 156 * @param width total width of the rectangle; must be non-negative 157 * @param height total height of the rectangle; must be non-negative 158 */ 159 public Region(int minX, int minY, int width, int height) 160 { 161 CoordPacker.init(); 162 raw = rectangle(minX, minY, width, height); 163 coords = allPacked(raw); 164 dirty = false; 165 } 166 /** 167 * A constructor for a Region that takes a specifically-formatted short array (packed data), as produced by 168 * CoordPacker or sometimes other classes, like RegionMap or RoomFinder. If you don't have such data, the other 169 * constructors are recommended instead. 170 * <br> 171 * Note: arrays of Hilbert indices are occasionally returned in CoordPacker as a different kind of short array, but 172 * those methods are always noted as such and those short arrays won't make sense if passed to this constructor. 173 * They may result in a technically-valid Region with random-seeming contents. In general, if a method in 174 * CoordPacker returns a short array (most of them do), but the name contains Hilbert, it may return the wrong kind 175 * (an array of Hilbert indices is wrong, packed data is right); check the documentation for that method. 176 * @param packedData a short array as produced by CoordPacker (usually), or sometimes RoomFinder or RegionMap 177 */ 178 public Region(short[] packedData) 179 { 180 CoordPacker.init(); 181 raw = new short[packedData.length]; 182 System.arraycopy(packedData, 0, raw, 0, packedData.length); 183 coords = allPacked(raw); 184 dirty = false; 185 } 186 187 /** 188 * Gets a single random Coord from this using the given RNG (which can be seeded); returns null if this is empty. 189 * @param rng the source of random numbers used to get a random Coord from this Region 190 * @return a random Coord in this Region, or null if this is empty 191 */ 192 public Coord getRandomCoord(IRNG rng) 193 { 194 if(CoordPacker.isEmpty(raw)) 195 return null; 196 return singleRandom(raw, rng); 197 } 198 199 /** 200 * Takes this region and walks through its Coords in chunks with length equal to separation, creating a new Region 201 * where one Coord in each chunk is kept and the others are discarded. 202 * @param separation an int where higher numbers mean there will be more distance between Coords, and fewer total 203 * @return a new Region made of the separated Coords 204 */ 205 public Region separated(int separation) 206 { 207 return new Region(fractionPacked(raw, separation)); 208 } 209 210 /** 211 * Takes this region and walks through its Coords in chunks with length equal to separation, creating a new Region 212 * where one randomly-chosen Coord in each chunk is kept and the others are discarded. 213 * @param separation an int where higher numbers mean there will be more distance between Coords, and fewer total 214 * @param rng the source of random numbers used to randomize Coords used, removing any noticeable pattern 215 * @return a new Region made of the separated Coords 216 */ 217 public Region randomSeparated(int separation, IRNG rng) 218 { 219 return new Region(CoordPacker.randomSeparated(raw, separation, rng)); 220 } 221 222 /** 223 * Gets a representation of this Region as a 2D boolean array with the given width and height. If this contains a 224 * Coord with x and y less than width and height, that position will be true in the 2D array this returns, otherwise 225 * it will be false. 226 * @param width the width of the 2D array to return 227 * @param height the height of the 2D array to return 228 * @return a 2D boolean array where present Coords in this Region will be true, and everything else will be false 229 */ 230 public boolean[][] toBooleanArray(int width, int height) 231 { 232 return CoordPacker.unpack(raw, width, height); 233 } 234 235 /** 236 * Gets a representation of this Region as a 2D char array with the given width and height, using on to represent 237 * present Coords and off to represent absent ones. If this contains a Coord with x and y less than width and 238 * height, that position will be equal to on in the 2D array this returns, otherwise it will be equal to off. 239 * @param width the width of the 2D array to return 240 * @param height the height of the 2D array to return 241 * @param on the char to use when a Coord is present in this Region 242 * @param off the char to use where no Coord is present in this Region 243 * @return a 2D char array where present Coords in this Region will be on, and everything else will be off 244 */ 245 public char[][] toCharArray(int width, int height, char on, char off) 246 { 247 return CoordPacker.unpackChar(raw, width, height, on, off); 248 } 249 250 /** 251 * Gets the Coord stored at the given index in this Region, or null if index is out of bounds. 252 * The ordering this class uses may seem unusual, but it is predictable, using the pattern a Hilbert Curve takes to 253 * wind through a 256x256 space (at its maximum). Any two given Coords will always have the same before-after 254 * relationship, regardless of other Coords in the Region. A Region is a dense data structure, like a List or array, 255 * so valid indices shouldn't ever return null. 256 * @param index the index into this Region to get a Coord at; should be less than size() and non-negative. 257 * @return the Coord this Region holds at index, if index is valid; otherwise null 258 */ 259 @Override 260 public Coord get(int index) { 261 if(dirty) 262 { 263 coords = allPacked(raw); 264 dirty = false; 265 } 266 if(index >= 0 && index < coords.length) 267 return coords[index]; 268 return null; 269 } 270 271 /** 272 * Gets the size of this Region as measured in Coords stored. 273 * Performs "cleanup" if the Region is "dirty," even though this doesn't specifically request a Coord. As such, it 274 * may take longer than a call to size() might be expected to, but only just after a "dirtying" method was called. 275 * @return the number of Coords stored in this Region. 276 */ 277 @Override 278 public int size() { 279 if(dirty) 280 { 281 coords = allPacked(raw); 282 dirty = false; 283 } 284 return coords.length; 285 } 286 287 /** 288 * Returns true if there are no Coords in this Region, or false otherwise. Can be more efficient than checking if 289 * size() is greater than 0 because it doesn't depend on the "dirty or clean" state, and can quickly check. 290 * @return 291 */ 292 @Override 293 public boolean isEmpty() { 294 return CoordPacker.isEmpty(raw); 295 } 296 297 /** 298 * Adds a Coord to this Region, returning false if the Coord is null or already included in this, or true otherwise. 299 * Causes the Region to be considered "dirty", which will make anything that gets a Coord from this to perform some 300 * "cleanup" on the Coords this holds when a Coord is requested. You can perform multiple "dirtying" operations in 301 * succession without needing more "cleanups" than the one when a Coord is next requested. 302 * @param coord a Coord to add to this region 303 * @return true if the Coord was added and was not already present; false if the Coord as null or already present 304 */ 305 @Override 306 public boolean add(Coord coord) { 307 if(coord == null || queryPacked(raw, coord.x, coord.y)) 308 return false; 309 raw = insertPacked(raw, coord.x, coord.y); 310 dirty = true; 311 return true; 312 } 313 314 /** 315 * Gets a direct reference to this Region's "raw packed data"; don't modify it unless you know what you're doing! 316 * It's fine to pass this to methods in CoordPacker, since essentially all of those methods won't modify packed data 317 * given as arguments. 318 * @return the raw packed data this class uses; should not be modified carelessly 319 */ 320 public short[] getRaw() { 321 return raw; 322 } 323 324 /** 325 * Sets the "raw packed data" to the given short array, as generated by CoordPacker or some parts of RegionMap. 326 * This hopefully won't need to be consumed too much externally, but is an important bridge between this and 327 * CoordPacker's API, which deals mostly with these special short arrays. 328 * Causes the Region to be considered "dirty", which will make anything that gets a Coord from this to perform some 329 * "cleanup" on the Coords this holds when a Coord is requested. You can perform multiple "dirtying" operations in 330 * succession without needing more "cleanups" than the one when a Coord is next requested. 331 * @param raw a short array of packed data; has a very specific format and should usually not be made manually 332 */ 333 public void setRaw(short[] raw) { 334 this.raw = raw; 335 dirty = true; 336 } 337 338 /** 339 * Gets the Coords contained in this as an array, a direct reference that does permit modifying the Coords in your 340 * own code without altering "dirty/clean" status. This method does "cleanup" in itself if necessary, but once the 341 * Coords are returned you can change them at-will. The Coords may not reflect changes made by this object if it 342 * creates a new Coord array, as it often does. 343 * @return the Coords contained in this Region as an array 344 */ 345 public Coord[] getCoords() { 346 if(dirty) 347 { 348 coords = allPacked(raw); 349 dirty = false; 350 } 351 return coords; 352 } 353 354 /** 355 * Changes this Region to include the given Coords and disregard its previous contents. If any elements of coords 356 * are null, this Region will hold no Coords (a sort of escape hatch to avoid throwing an exception, since all other 357 * methods in this class fail on null entries without potentially crashing a program). 358 * @param coords an array or vararg of Coord that will be used as the entirety of this Region 359 */ 360 public void setCoords(Coord... coords) { 361 if (coords == null) 362 return; 363 raw = packSeveral(coords); 364 if (raw == ALL_WALL) { 365 this.coords = new Coord[0]; 366 dirty = false; 367 } else { 368 this.coords = new Coord[coords.length]; 369 System.arraycopy(coords, 0, this.coords, 0, coords.length); 370 dirty = false; 371 } 372 } 373 374 /** 375 * Prints this Region to System.out as a grid of chars with the given width and height, using '.' for Coords this 376 * contains and '#' for empty space. Consider using toCharArray() instead if you need more customization for what 377 * you want printed. 378 * @param width the width in chars of the grid to print 379 * @param height the height in chars of the grid to print 380 */ 381 public void debugPrint(int width, int height) 382 { 383 DungeonUtility.debugPrint(toCharArray(width, height, '.', '#')); 384 } 385}