001package squidpony.squidai;
002
003import squidpony.squidgrid.Direction;
004import squidpony.squidgrid.Radius;
005import squidpony.squidmath.*;
006
007import java.io.Serializable;
008import java.util.Collection;
009
010/**
011 * Calculates the Zone of Influence, also known as Zone of Control, for different points on a map.
012 * Uses GreasedRegion for faster storage and manipulation of zones; it's suggested if you use this class to be
013 * somewhat familiar with the methods for manipulating data in that class, though a GreasedRegion can also be used just
014 * like a Collection of Coord values. This class is very similar in API and implementation to {@link ZOI}, but should be
015 * faster on large maps and produces much less garbage. The main reason to choose between ZOI and
016 * GreasedZOI is whether your existing code uses GreasedRegions, like this class, or uses CoordPacker, like ZOI. If you
017 * don't currently use either, GreasedZOI is probably preferable because the {@link #calculate()} method produces a
018 * value that can be reasonably consumed by Collection-based APIs, while {@link ZOI#calculate()} produces a
019 * harder-to-use {@code short[]} that must be read by CoordPacker. Repeated operations on CoordPacker's {@code short[]}
020 * data will have to keep allocating more arrays and temporary internal data structures, while a GreasedRegion can be
021 * reused many times and only rarely allocates more space.
022 * <br>
023 * Created by Tommy Ettinger on 1/14/2018.
024 */
025public class GreasedZOI implements Serializable {
026    private static final long serialVersionUID = 2L;
027    private DijkstraMap dijkstra;
028    private Coord[][] influences;
029    private GreasedRegion[] groups;
030    private boolean completed;
031    private Radius radius;
032    /**
033     * Constructs a Zone of Influence map. Takes a (quite possibly jagged) array of arrays of Coord influences, where
034     * the elements of the outer array represent different groups of influencing "factions" or groups that exert control
035     * over nearby areas, and the Coord elements of the inner array represent individual spots that are part of those
036     * groups and share influence with all Coord in the same inner array. Also takes a char[][] for a map, which can be
037     * the simplified map with only '#' for walls and '.' for floors, or the final map (with chars like '~' for deep
038     * water as well as walls and floors), and a Radius enum that will be used to determine how distance is calculated.
039     * <br>
040     * Call {@link #calculate()} when you want information out of this.
041     * @param influences an outer array containing influencing groups, each an array containing Coords that influence
042     * @param map a char[][] that is used as an area map; should be bounded
043     * @param radiusStrategy a Radius enum that corresponds to how distance should be measured
044     */
045    public GreasedZOI(Coord[][] influences, char[][] map, Radius radiusStrategy) {
046        this.influences = influences;
047        groups = new GreasedRegion[influences.length];
048        radius = radiusStrategy == null ? Radius.CIRCLE : radiusStrategy;
049        dijkstra = new DijkstraMap(map, radius.matchingMeasurement());
050    }
051    /**
052     * Constructs a Zone of Influence map. Takes an arrays of Coord influences, where each Coord is treated as both a
053     * one-element group of influencing "factions" or groups that exert control over nearby areas, and the individual
054     * spot that makes up one of those groups and spreads influence. Also takes a char[][] for a map, which can be the
055     * simplified map with only '#' for walls and '.' for floors, or the final map (with chars like '~' for deep water
056     * as well as walls and floors), and a Radius enum that will be used to determine how distance is calculated.
057     * <br>
058     * Essentially, this is the same as constructing a ZOI with a Coord[][] where each inner array has only one element.
059     * <br>
060     * Call {@link #calculate()} when you want information out of this.
061     * @param influences an array containing Coords that each have their own independent influence
062     * @param map a char[][] that is used as an area map; should be bounded
063     * @param radiusStrategy a Radius enum that corresponds to how distance should be measured
064     * @see squidpony.squidmath.PoissonDisk PoissonDisk provides a good way to generate evenly spaced Coords
065     */
066    public GreasedZOI(Coord[] influences, char[][] map, Radius radiusStrategy) {
067        this.influences = new Coord[influences.length][];
068        for (int i = 0; i < influences.length; i++) {
069            this.influences[i] = new Coord[] { influences[i] };
070        }
071        groups = new GreasedRegion[influences.length];
072        radius = radiusStrategy == null ? Radius.CIRCLE : radiusStrategy;
073        dijkstra = new DijkstraMap(map, radius.matchingMeasurement());
074    }
075    /**
076     * Constructs a Zone of Influence map. Takes a Collection of Coord influences, where each Coord is treated as both a
077     * one-element group of influencing "factions" or groups that exert control over nearby areas, and the individual
078     * spot that makes up one of those groups and spreads influence. Also takes a char[][] for a map, which can be the
079     * simplified map with only '#' for walls and '.' for floors, or the final map (with chars like '~' for deep water
080     * as well as walls and floors), and a Radius enum that will be used to determine how distance is calculated.
081     * <br>
082     * Essentially, this is the same as constructing a ZOI with a Coord[][] where each inner array has only one element,
083     * drawn from a Collection. It often makes sense to use a GreasedRegion as the Collection of Coord.
084     * <br>
085     * Call {@link #calculate()} when you want information out of this.
086     * @param influences A Collection of Coord, such as a GreasedRegion, where each Coord has independent influence
087     * @param map a char[][] that is used as an area map; should be bounded
088     * @param radiusStrategy a Radius enum that corresponds to how distance should be measured
089     */
090    public GreasedZOI(Collection<Coord> influences, char[][] map, Radius radiusStrategy) {
091        this.influences = new Coord[influences.size()][];
092        int i = 0;
093        for(Coord c : influences) {
094            this.influences[i++] = new Coord[]{c};
095        }
096        groups = new GreasedRegion[this.influences.length];
097        radius = radiusStrategy == null ? Radius.CIRCLE : radiusStrategy;
098        dijkstra = new DijkstraMap(map, radius.matchingMeasurement());
099    }
100
101    /**
102     * Finds the zones of influence for each of the influences (inner arrays of Coord) this was constructed with, and
103     * returns all zones as a GreasedRegion array. This has each zone of influence overlap with its neighbors; this
104     * is useful to find borders using {@link GreasedRegion#and(GreasedRegion)}, and borders are typically between 1 and
105     * 2 cells wide. You can get a different region if you want region A without the overlapping areas it shares with
106     * region B by using {@link GreasedRegion#andNot(GreasedRegion)}. Merging two zones A and B can be done with
107     * {@link GreasedRegion#or(GreasedRegion)}. You can transform the data into a boolean[][] easily with
108     * {@link GreasedRegion#decode()}, where true is contained in the zone and false is not. The methods
109     * {@link GreasedRegion#fringe()}, {@link GreasedRegion#expand()}, {@link GreasedRegion#singleRandom(IRNG)}, and
110     * {@link GreasedRegion#separatedBlue(double)} are also potentially useful for this sort of data. You should save
111     * the {@code GreasedRegion[]} for later use if you want to call
112     * {@link #nearestInfluences(GreasedRegion[], Coord)}.
113     * <br>
114     * The first GreasedRegion in the returned GreasedRegion[] will correspond to the area influenced by the first
115     * Coord[] in the nested array passed to the constructor (or the first Coord if a non-nested array was passed); the
116     * second will correspond to the second, and so on. The length of the GreasedRegion[] this returns will equal the
117     * number of influence groups.
118     * @return a GreasedRegion array, with each item storing a zone's area
119     */
120    public GreasedRegion[] calculate()
121    {
122        for (int i = 0; i < influences.length; i++) {
123            for (int j = 0; j < influences[i].length; j++) {
124                dijkstra.setGoal(influences[i][j]);
125            }
126        }
127        dijkstra.scan(null, null);
128        final double[][] scannedAll = dijkstra.gradientMap;
129
130        for (int i = 0; i < influences.length; i++) {
131
132            /*
133            dijkstra.clearGoals();
134            dijkstra.resetMap();
135            for (int j = 0; j < influences[i].length; j++) {
136                dijkstra.setGoal(influences[i][j]);
137            }
138            double[][] factionScanned = dijkstra.scan(null);
139            for (int y = 0; y < map[0].length; y++) {
140                for (int x = 0; x < map.length; x++) {
141                    influenced[x][y] = (scannedAll[x][y] < DijkstraMap.FLOOR) &&
142                            (factionScanned[x][y] - scannedAll[x][y] <= 1);
143                }
144            }*/
145            groups[i] = increasing(scannedAll, influences[i]);
146        }
147        completed = true;
148        return groups;
149    }
150    protected GreasedRegion increasing(double[][] dm, Coord[] inf) {
151        OrderedSet<Coord> open = new OrderedSet<>(inf), fresh = new OrderedSet<>(64);
152        Direction[] dirs = (radius.equals2D(Radius.DIAMOND)) ? Direction.CARDINALS : Direction.OUTWARDS;
153        GreasedRegion influenced = new GreasedRegion(dijkstra.width, dijkstra.height);
154        final int width = dm.length;
155        final int height = width == 0 ? 0 : dm[0].length;
156
157        int numAssigned = open.size();
158        double diff;
159        while (numAssigned > 0) {
160            numAssigned = 0;
161            for (Coord cell : open) {
162                influenced.insert(cell);
163                for (int d = 0; d < dirs.length; d++) {
164                    Coord adj = cell.translate(dirs[d].deltaX, dirs[d].deltaY);
165                    if (adj.x < 0 || adj.y < 0 || width <= adj.x || height <= adj.y)
166                        /* Outside the map */
167                        continue;
168                    if (!open.contains(adj) && dm[adj.x][adj.y] < DijkstraMap.FLOOR && !influenced.contains(adj)) {
169                        //h = heuristic(dirs[d]);
170                        diff = dm[adj.x][adj.y] - dm[cell.x][cell.y];
171                        if (diff <= 1.0 && diff >= 0) {
172                            fresh.add(adj);
173                            influenced.insert(adj);
174                            ++numAssigned;
175                        }
176                    }
177                }
178            }
179
180            open = new OrderedSet<>(fresh);
181            fresh.clear();
182        }
183
184        return influenced;
185    }
186
187    /**
188     * Given the zones resulting from this class' {@link #calculate()} method and a Coord to check, finds the indices of
189     * all influencing groups in zones that have the Coord in their area, and returns all such indices as an IntVLA.
190     * @param zones a GreasedRegion[] returned by calculate
191     * @param point the Coord to test
192     * @return an IntVLA where each element is the index of an influencing group in zones
193     */
194    public IntVLA nearestInfluences(GreasedRegion[] zones, Coord point)
195    {
196        IntVLA found = new IntVLA(4);
197        for (int i = 0; i < zones.length; i++) {
198            if(zones[i].contains(point))
199                found.add(i);
200        }
201        return found;
202    }
203    /**
204     * This can be given a Coord to check in the results of the latest calculate() call. Finds the indices of all
205     * influencing groups in zones that have the Coord in their area, and returns all such indices as an {@link IntVLA}.
206     * You can convert the IntVLA to an array with {@link IntVLA#toArray()} if you want to match ZOI's behavior.
207     *
208     * @param point the Coord to test
209     * @return an IntVLA where each element is the index of an influencing group in zones
210     */
211    public IntVLA nearestInfluences(Coord point)
212    {
213        if(!completed)
214            return new IntVLA(0);
215        IntVLA found = new IntVLA(4);
216        for (short i = 0; i < groups.length; i++) {
217            if(groups[i].contains(point))
218                found.add(i);
219        }
220        return found;
221    }
222
223    /**
224     * Gets the influencing groups; ideally the result should not be changed without setting it back with setInfluences.
225     * @return influences a jagged array of Coord arrays, where the inner arrays are groups of influences
226     */
227    public Coord[][] getInfluences() {
228        return influences;
229    }
230
231    /**
232     * Changes the influencing groups. This also invalidates the last calculation for the purposes of nearestInfluences,
233     * at least for the overload that takes only a Coord.
234     * @param influences a jagged array of Coord arrays, where the inner arrays are groups of influences
235     */
236    public void setInfluences(Coord[][] influences) {
237        if(groups.length == influences.length)
238        {
239            for (int i = 0; i < groups.length; i++) {
240                groups[i].clear();
241            }
242        }
243        else
244            groups = new GreasedRegion[influences.length];
245        this.influences = influences;
246        completed = false;
247    }
248}