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