001package squidpony.squidai.graph;
002
003import squidpony.squidgrid.Direction;
004import squidpony.squidgrid.mapping.DungeonUtility;
005import squidpony.squidmath.Coord;
006
007import java.io.Serializable;
008import java.util.ArrayList;
009import java.util.Arrays;
010import java.util.Map;
011
012/**
013 * A default setting for a DirectedGraph of Coord vertices where each passable cell has a cost to enter it from any
014 * passable neighbor. This should be compatible with the AStar cost maps produced by
015 * {@link DungeonUtility#generateAStarCostMap(char[][], Map, double)}.
016 * <br>
017 * Created by Tommy Ettinger on 7/9/2020.
018 */
019public class CostlyGraph extends DirectedGraph<Coord> implements Serializable {
020        private static final long serialVersionUID = 1L;
021
022        public int width;
023        public int height;
024
025        /**
026         * No-op no-arg constructor, present for {@link Serializable}; if you use this you must call
027         * {@link #init(double[][])} or {@link #init(double[][], boolean)} before using the CostlyGraph.
028         */
029        public CostlyGraph() {
030                super();
031                width = 0;
032                height = 0;
033        }
034
035        /**
036         * Builds a DefaultGraph from a 2D double array that uses negative numbers to represent any kind of inaccessible
037         * cell, with all other numbers treated as possible to enter for a cost equal to that double. This only builds
038         * connections along cardinal directions.
039         *
040         * @see DungeonUtility#generateAStarCostMap(char[][], Map, double) DungeonUtility has methods to generate this type of map
041         * @param map a 2D double array where negative numbers are impassable and non-negative ones represent costs to enter
042         */
043        public CostlyGraph(double[][] map) {
044                this(map, false);
045        }
046
047        /**
048         * Builds a DefaultGraph from a 2D double array that uses negative numbers to represent any kind of inaccessible
049         * cell, with all other numbers treated as possible to enter for a cost equal to that double. If {@code eightWay} is
050         * true, this builds connections along diagonals as well as along cardinals, but if {@code eightWay} is false, it
051         * only builds connections along cardinal directions.
052         * 
053         * @see DungeonUtility#generateAStarCostMap(char[][], Map, double) DungeonUtility has methods to generate this type of map
054         * @param map a 2D double array where negative numbers are impassable and non-negative ones represent costs to enter
055         * @param eightWay if true, this will build connections on diagonals as well as cardinal directions; if false, this will only use cardinal connections
056         */
057        public CostlyGraph(double[][] map, boolean eightWay) {
058                super();
059                init(map, eightWay);
060        }
061
062        /**
063         * Builds a DefaultGraph from a 2D char array that treats {@code '#'}, {@code '+'}, and all box drawing characters
064         * as impassable, but considers all other cells passable for a cost of 1.0. This only builds connections along
065         * cardinal directions.
066         * <br>
067         * This simply delegates to {@link #init(double[][], boolean)} with the result of
068         * {@link DungeonUtility#generateAStarCostMap(char[][])} for the 2D double array. You can get more control by
069         * calling {@link DungeonUtility#generateAStarCostMap(char[][], Map, double)} and passing that to init() or a
070         * constructor that takes a 2D double array.
071         *
072         * @param map a 2D char array where {@code '#'}, {@code '+'}, and all box drawing characters are considered impassable
073         */
074        public CostlyGraph(char[][] map) {
075                super();
076                init(DungeonUtility.generateAStarCostMap(map), false);
077        }
078
079        /**
080         * Builds a DefaultGraph from a 2D char array that treats {@code '#'}, {@code '+'}, and all box drawing characters
081         * as impassable, but considers all other cells passable for a cost of 1.0. If {@code eightWay} is true, this builds
082         * connections along diagonals as well as along cardinals, but if {@code eightWay} is false, it only builds
083         * connections along cardinal directions.
084         * <br>
085         * This simply delegates to {@link #init(double[][], boolean)} with the result of
086         * {@link DungeonUtility#generateAStarCostMap(char[][])} for the 2D double array. You can get more control by
087         * calling {@link DungeonUtility#generateAStarCostMap(char[][], Map, double)} and passing that to init() or a
088         * constructor that takes a 2D double array.
089         *
090         * @param map a 2D char array where {@code '#'}, {@code '+'}, and all box drawing characters are considered impassable
091         * @param eightWay if true, this will build connections on diagonals as well as cardinal directions; if false, this will only use cardinal connections
092         */
093        public CostlyGraph(char[][] map, boolean eightWay) {
094                super();
095                init(DungeonUtility.generateAStarCostMap(map), eightWay);
096        }
097
098        /**
099         * Re-initializes this DefaultGraph from a 2D double array that uses negative numbers to represent any kind of
100         * inaccessible cell, with all other numbers treated as possible to enter for a cost equal to that double. This only
101         * builds connections along cardinal directions.
102         *
103         * @see DungeonUtility#generateAStarCostMap(char[][], Map, double) DungeonUtility has methods to generate this type of map
104         * @param map a 2D double array where negative numbers are impassable and non-negative ones represent costs to enter
105         */
106        public void init(double[][] map) {
107                init(map, false);
108        }
109        /**
110         * Re-initializes this DefaultGraph from a 2D double array that uses negative numbers to represent any kind of
111         * inaccessible cell, with all other numbers treated as possible to enter for a cost equal to that double. If
112         * {@code eightWay} is true, this builds connections along diagonals as well as along cardinals, but if
113         * {@code eightWay} is false, it only builds connections along cardinal directions.
114         *
115         * @see DungeonUtility#generateAStarCostMap(char[][], Map, double) DungeonUtility has methods to generate this type of map
116         * @param map a 2D double array where negative numbers are impassable and non-negative ones represent costs to enter
117         * @param eightWay if true, this will build connections on diagonals as well as cardinal directions; if false, this will only use cardinal connections
118         */
119        public void init(double[][] map, boolean eightWay) {
120                width = map.length;
121                height = map[0].length;
122                Coord.expandPoolTo(width, height);
123                ArrayList<Coord> vs = new ArrayList<>(width * height >>> 1);
124                vertexMap.clear();
125                edgeMap.clear();
126                for (int x = 0; x < width; x++) {
127                        for (int y = 0; y < height; y++) {
128                                if(map[x][y] >= 0.0)
129                                {
130                                        Coord pt = Coord.get(x, y);
131                                        vs.add(pt);
132                                        addVertex(pt);
133                                }
134                        }
135                }
136                final int sz = vs.size();
137                Coord center, off;
138                final Direction[] outer = eightWay ? Direction.CLOCKWISE : Direction.CARDINALS_CLOCKWISE;
139                Direction dir;
140                for (int i = 0; i < sz; i++) {
141                        center = vs.get(i);
142                        for (int j = 0; j < outer.length; j++) {
143                                dir = outer[j];
144                                off = center.translate(dir);
145                                if(off.isWithin(width, height) && map[center.x + dir.deltaX][center.y + dir.deltaY] >= 0.0)
146                                {
147                                        addEdge(off, center, (float)map[center.x][center.y]);
148                                }
149                        }
150                }
151        }
152
153        /**
154         * Sort the vertices of this graph in topological order. That is, for every edge from vertex u to vertex v, u comes
155         * before v in the ordering. This is reflected in the iteration order of the collection returned by
156         * {@link Graph#getVertices()}. Note that the graph cannot contain any cycles for a topological order to exist. If a
157         * cycle exists, this method will do nothing.
158         * @return true if the sort was successful, false if the graph contains a cycle
159         */
160        public boolean topologicalSort() {
161                return algorithms.topologicalSort();
162        }
163
164        /**
165         * Perform a topological sort on the graph, and puts the sorted vertices in the supplied list.
166         * That is, for every edge from vertex u to vertex v, u will come before v in the supplied list.
167         * Note that the graph cannot contain any cycles for a topological order to exist. If a cycle exists, the sorting
168         * procedure will terminate and the supplied list will only contain the vertices up until the point of termination.
169         * @param sortedVertices an ArrayList of V vertices that will be cleared and modified in-place
170         * @return true if the sort was successful, false if the graph contains a cycle
171         */
172        public boolean topologicalSort(ArrayList<Coord> sortedVertices) {
173                return algorithms.topologicalSort(sortedVertices);
174        }
175
176        /**
177         * Find the shortest path between the start and target vertices, using Dijkstra's algorithm implemented with a priority queue.
178         * @param start the starting vertex
179         * @param target the target vertex
180         * @return a list of vertices from start to target containing the ordered vertices of a shortest path, including both the start and target vertices
181         */
182        public ArrayList<Coord> findShortestPath(Coord start, Coord target) {
183                return algorithms.findShortestPath(start, target);
184        }
185
186        /**
187         * Find the shortest path between the start and target vertices, using the A* search algorithm with the provided heuristic, and implemented with a priority queue.
188         * @param start the starting vertex
189         * @param target the target vertex
190         * @param heuristic typically predefined in {@link Heuristic}, this determines how the optimal path will be estimated
191         * @return a list of vertices from start to target containing the ordered vertices of a shortest path, including both the start and target vertices
192         */
193        public ArrayList<Coord> findShortestPath(Coord start, Coord target, Heuristic<Coord> heuristic) {
194                return algorithms.findShortestPath(start, target, heuristic);
195        }
196
197        /**
198         * Find the shortest path between the start and target vertices, using the A* search algorithm with the provided
199         * heuristic, and implemented with a priority queue. Fills path with a list of vertices from start to target
200         * containing the ordered vertices of a shortest path, including both the start and target vertices.
201         * @param start the starting vertex
202         * @param target the target vertex
203         * @param path the list of vertices to which the path vertices should be added
204         * @return true if a path was found, or false if no path could be found
205         */
206        public boolean findShortestPath(Coord start, Coord target, ArrayList<Coord> path, Heuristic<Coord> heuristic) {
207                return algorithms.findShortestPath(start, target, path, heuristic);
208        }
209
210        /**
211         * Find the shortest path between the start and target vertices, using Dijkstra's algorithm implemented with a priority queue.
212         * @param start the starting vertex
213         * @param target the target vertex
214         * @return the sum of the weights in a shortest path from the starting vertex to the target vertex
215         */
216        public double findMinimumDistance(Coord start, Coord target) {
217                return algorithms.findMinimumDistance(start, target);
218        }
219
220        /**
221         * Perform a breadth first search starting from the specified vertex.
222         * @param coord the vertex at which to start the search
223         * @param maxVertices the maximum number of vertices to process before terminating the search
224         * @param maxDepth the maximum edge distance (the number of edges in a shortest path between vertices) a vertex should have to be
225         *                 considered for processing. If a vertex has a distance larger than the maxDepth, it will not be added to the
226         *                 returned graph
227         * @return a Graph object containing all the processed vertices, and the edges from which each vertex was encountered.
228         * The vertices and edges in the returned graph will be in the order they were encountered in the search, and this will be
229         * reflected in the iteration order of the collections returned by {@link Graph#getVertices()} and {@link Graph#getEdges()}.
230         */
231        public Graph<Coord> breadthFirstSearch(Coord coord, int maxVertices, int maxDepth) {
232                return algorithms.breadthFirstSearch(coord, maxVertices, maxDepth);
233        }
234
235        /**
236         * Perform a breadth first search starting from the specified vertex.
237         * @param coord the vertex at which to start the search
238         * @return a Graph object containing all the processed vertices (all the vertices in this graph), and the edges from which each vertex was encountered.
239         * The vertices and edges in the returned graph will be in the order they were encountered in the search, and this will be
240         * reflected in the iteration order of the collections returned by {@link Graph#getVertices()} and {@link Graph#getEdges()}.
241         */
242        public Graph<Coord> breadthFirstSearch(Coord coord) {
243                return algorithms.breadthFirstSearch(coord);
244        }
245
246        /**
247         * Perform a depth first search starting from the specified vertex.
248         * @param coord the vertex at which to start the search
249         * @param maxVertices the maximum number of vertices to process before terminating the search
250         * @param maxDepth the maximum edge distance (the number of edges in a shortest path between vertices) a vertex should have to be
251         *                 considered for processing. If a vertex has a distance larger than the maxDepth, it will not be added to the
252         *                 returned graph
253         * @return a Graph object containing all the processed vertices, and the edges from which each vertex was encountered.
254         * The vertices and edges in the returned graph will be in the order they were encountered in the search, and this will be
255         * reflected in the iteration order of the collections returned by {@link Graph#getVertices()} and {@link Graph#getEdges()}.
256         */
257        public Graph<Coord> depthFirstSearch(Coord coord, int maxVertices, int maxDepth) {
258                return algorithms.depthFirstSearch(coord, maxVertices, maxDepth);
259        }
260
261        /**
262         * Perform a depth first search starting from the specified vertex.
263         * @param coord the vertex at which to start the search
264         * @return a Graph object containing all the processed vertices (all the vertices in this graph), and the edges from which each vertex was encountered.
265         * The vertices and edges in the returned graph will be in the order they were encountered in the search, and this will be
266         * reflected in the iteration order of the collections returned by {@link Graph#getVertices()} and {@link Graph#getEdges()}.
267         */
268        public Graph<Coord> depthFirstSearch(Coord coord) {
269                return algorithms.depthFirstSearch(coord);
270        }
271
272        /**
273         * Checks whether there are any cycles in the graph using depth first searches.
274         * @return true if the graph contains a cycle, false otherwise
275         */
276        public boolean detectCycle() {
277                return algorithms.detectCycle();
278        }
279
280        /**
281         * Creates a 1D char array (which can be passed to {@link String#valueOf(char[])}) filled with a grid made of the
282         * vertices in this Graph and their estimated costs, if this has done an estimate. Each estimate is rounded to the
283         * nearest int and only printed if it is 4 digits or less; otherwise this puts '####' in the grid cell. This is a
284         * building-block for toString() implementations that may have debugging uses as well.
285         * @return a 1D char array containing newline-separated rows of space-separated grid cells that contain estimated costs or '####' for unexplored
286         */
287        public char[] show() {
288                final int w5 = width * 5;
289                final char[] cs = new char[w5 * height];
290                Arrays.fill(cs,  '#');
291                for (int i = 4; i < cs.length; i += 5) {
292                        cs[i] = (i + 1) % w5 == 0 ? '\n' : ' ';
293                }
294                final int vs = vertexMap.size(), rid = algorithms.lastRunID();
295                Node<Coord> nc;
296                for (int i = 0; i < vs; i++) {
297                        nc = vertexMap.getAt(i);
298                        if(!nc.seen || nc.lastRunID != rid || nc.distance >= 9999.5)
299                                continue;
300                        int d = (int) (nc.distance + 0.5), x = nc.object.x * 5, y = nc.object.y;
301                        cs[y * w5 + x    ] = (d >= 1000) ? (char) ('0' + d / 1000) : ' ';
302                        cs[y * w5 + x + 1] = (d >= 100)  ? (char) ('0' + d / 100 % 10) : ' ';
303                        cs[y * w5 + x + 2] = (d >= 10)   ? (char) ('0' + d / 10 % 10) : ' ';
304                        cs[y * w5 + x + 3] = (char) ('0' + d % 10);
305                }
306                return cs;
307        }
308
309}