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}