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}