├── LICENSE ├── README.md ├── release-v1.0 └── APathfinding-Visual.jar └── src ├── APathfinding.java ├── ControlHandler.java ├── Frame.java ├── Node.java ├── Sort.java └── style.java /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 DevonCrawford 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A* Pathfinding Visualization 2 | 3 | This is my first attempt at A* pathfinding. I made this visualization to show the beauty of pathfinding. You can modify all major features of my algorithm through the graphics interface. Here I will go through the major features of my program. 4 | 5 | ## Basic Controls 6 | You must create a map to start the pathfinding. The start node is blue, end node is red and the walls are black. 7 | 8 | To create nodes: 9 | - Start: hold 's' + left click 10 | - End: hold 'e' + left click 11 | - Wall: left click 12 | 13 | To delete nodes: 14 | - same as creation, except right click! 15 | 16 | ![basic-controls](https://cloud.githubusercontent.com/assets/25334129/22450191/f433bb24-e732-11e6-8004-19b923cf4d08.gif) 17 | 18 | ### Diagonal 19 | My algorithm supports both diagonal and non diagonal pathfinding. 20 | 21 | Simply check the "diagonal" box at the bottom left of the screen. 22 | 23 | ![diagonal-vs-non-diagonal](https://cloud.githubusercontent.com/assets/25334129/22450200/fd49d752-e732-11e6-9684-f9284486d6eb.gif) 24 | 25 | ### Variable Speed 26 | You may change the speed of the visualization during runtime. 27 | - By default, speed is 50%. 28 | 29 | Notice: speed only works when showSteps is true. 30 | 31 | if showSteps is false, well, that leads into the next section.. 32 | 33 | ### Show Steps or Timed Efficiency 34 | You may choose to view a step-by-step process of the algorithm by selecting "showSteps" box at the bottom left. 35 | - If showSteps is false, the algorithm will skip visuals until the end, and process as fast as possible. 36 | 37 | This is useful for when you want to analyze the efficiency of my algorithm in different coniditons. The example below shows "showSteps" as false, where it times the algorithm and outputs "Completed in 4ms" at the bottom left. 38 | 39 | ![showsteps](https://cloud.githubusercontent.com/assets/25334129/22450236/2f7d1d9c-e733-11e6-87ea-60bc0ecac146.gif) 40 | 41 | ### Complicated Stuff 42 | Those are the basics! Now you can be free to make the map as complicated as you desire. (Not really, because making the map too large will overflow the stack). But go ahead! 43 | 44 | ![complicated-drawings](https://cloud.githubusercontent.com/assets/25334129/22450232/2b790d14-e733-11e6-8a91-4b4cba372f9b.gif) 45 | 46 | ### Zoom 47 | You can (kind of) zoom in and out. I wouldn't really advise it. It does not zoom into your mouse, only towards the top left corner, and making the map too big will crash the program. This needs some work. However, If you zoom in far enough you can view each nodes information. The top left is the "F cost", bottom left is "G cost" and bottom right is "H cost". I will work on properly implementing a zoom feature soon. 48 | 49 | *a project by Devon Crawford.* 50 | -------------------------------------------------------------------------------- /release-v1.0/APathfinding-Visual.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevonCrawford/A-Pathfinding-Visualization/78ea534858a17e1be38d63c1c3a6b071752bb51d/release-v1.0/APathfinding-Visual.jar -------------------------------------------------------------------------------- /src/APathfinding.java: -------------------------------------------------------------------------------- 1 | import java.util.ArrayList; 2 | 3 | public class APathfinding { 4 | private int size, diagonalMoveCost; 5 | private long runTime; 6 | private double kValue; 7 | private Frame frame; 8 | private Node startNode, endNode, par; 9 | private boolean diagonal, running, noPath, complete, trig; 10 | private ArrayList borders, open, closed, path; 11 | private Sort sort = new Sort(); 12 | 13 | public APathfinding(int size) { 14 | this.size = size; 15 | 16 | diagonalMoveCost = (int) (Math.sqrt(2 * (Math.pow(size, 2)))); 17 | kValue = Math.PI / 2; 18 | diagonal = true; 19 | trig = false; 20 | running = false; 21 | complete = false; 22 | 23 | borders = new ArrayList(); 24 | open = new ArrayList(); 25 | closed = new ArrayList(); 26 | path = new ArrayList(); 27 | } 28 | 29 | public APathfinding(Frame frame, int size) { 30 | this.frame = frame; 31 | this.size = size; 32 | 33 | diagonalMoveCost = (int) (Math.sqrt(2 * (Math.pow(size, 2)))); 34 | kValue = Math.PI / 2; 35 | diagonal = true; 36 | trig = false; 37 | running = false; 38 | complete = false; 39 | 40 | borders = new ArrayList(); 41 | open = new ArrayList(); 42 | closed = new ArrayList(); 43 | path = new ArrayList(); 44 | } 45 | 46 | public APathfinding(Frame frame, int size, Node start, Node end) { 47 | this.frame = frame; 48 | this.size = size; 49 | startNode = start; 50 | endNode = end; 51 | 52 | diagonalMoveCost = (int) (Math.sqrt(2 * (Math.pow(size, 2)))); 53 | diagonal = true; 54 | trig = false; 55 | running = false; 56 | complete = false; 57 | 58 | borders = new ArrayList(); 59 | open = new ArrayList(); 60 | closed = new ArrayList(); 61 | path = new ArrayList(); 62 | } 63 | 64 | public void start(Node s, Node e) { 65 | running = true; 66 | startNode = s; 67 | startNode.setG(0); 68 | endNode = e; 69 | 70 | // Adding the starting node to the closed list 71 | addClosed(startNode); 72 | 73 | long startTime = System.currentTimeMillis(); 74 | 75 | findPath(startNode); 76 | 77 | complete = true; 78 | long endTime = System.currentTimeMillis(); 79 | runTime = endTime - startTime; 80 | System.out.println("Completed: " + runTime + "ms"); 81 | } 82 | 83 | public void setup(Node s, Node e) { 84 | running = true; 85 | startNode = s; 86 | startNode.setG(0); 87 | par = startNode; 88 | endNode = e; 89 | 90 | // Adding the starting node to the closed list 91 | addClosed(startNode); 92 | } 93 | 94 | public void setStart(Node s) { 95 | startNode = s; 96 | startNode.setG(0); 97 | } 98 | 99 | public void setEnd(Node e) { 100 | endNode = e; 101 | } 102 | 103 | public boolean isRunning() { 104 | return running; 105 | } 106 | 107 | public boolean isComplete() { 108 | return complete; 109 | } 110 | 111 | public Node getStart() { 112 | return startNode; 113 | } 114 | 115 | public Node getEnd() { 116 | return endNode; 117 | } 118 | 119 | public Node getPar() { 120 | return par; 121 | } 122 | 123 | public boolean isNoPath() { 124 | return noPath; 125 | } 126 | 127 | public boolean isDiagonal() { 128 | return diagonal; 129 | } 130 | 131 | public boolean isTrig() { 132 | return trig; 133 | } 134 | 135 | public void setDiagonal(boolean d) { 136 | diagonal = d; 137 | } 138 | 139 | public void setTrig(boolean t) { 140 | trig = t; 141 | } 142 | 143 | public void setSize(int s) { 144 | size = s; 145 | diagonalMoveCost = (int) (Math.sqrt(2 * (Math.pow(size, 2)))); 146 | } 147 | 148 | public void findPath(Node parent) { 149 | Node openNode = null; 150 | 151 | if (diagonal) { 152 | // Detects and adds one step of nodes to open list 153 | for (int i = 0; i < 3; i++) { 154 | for (int j = 0; j < 3; j++) { 155 | if (i == 1 && j == 1) { 156 | continue; 157 | } 158 | int possibleX = (parent.getX() - size) + (size * i); 159 | int possibleY = (parent.getY() - size) + (size * j); 160 | 161 | // Possible coordinates of borders 162 | // Using (crossBorderX, parent.getY()) 163 | // and (parent.getX(), crossBorderY()) 164 | // To see if there are borders in the way 165 | int crossBorderX = parent.getX() + (possibleX - parent.getX()); 166 | int crossBorderY = parent.getY() + (possibleY - parent.getY()); 167 | 168 | // Disables ability to cut corners around borders 169 | if (searchBorder(crossBorderX, parent.getY()) != -1 170 | | searchBorder(parent.getX(), crossBorderY) != -1 && ((j == 0 | j == 2) && i != 1)) { 171 | continue; 172 | } 173 | 174 | calculateNodeValues(possibleX, possibleY, openNode, parent); 175 | } 176 | } 177 | } 178 | else if (!trig) { 179 | // Detects and adds one step of nodes to open list 180 | for (int i = 0; i < 3; i++) { 181 | for (int j = 0; j < 3; j++) { 182 | if((i == 0 && j == 0) || (i == 0 && j == 2) || 183 | (i == 1 && j == 1) || (i == 2 && j == 0) || 184 | (i == 2 && j == 2)) { 185 | continue; 186 | } 187 | int possibleX = (parent.getX() - size) + (size * i); 188 | int possibleY = (parent.getY() - size) + (size * j); 189 | 190 | calculateNodeValues(possibleX, possibleY, openNode, parent); 191 | } 192 | } 193 | } 194 | else { 195 | for (int i = 0; i < 4; i++) { 196 | // Uses cosine and sine functions to get circle of points 197 | // around parent 198 | int possibleX = (int) Math.round(parent.getX() + (-size * Math.cos(kValue * i))); 199 | int possibleY = (int) Math.round(parent.getY() + (-size * Math.sin(kValue * i))); 200 | 201 | calculateNodeValues(possibleX, possibleY, openNode, parent); 202 | } 203 | } 204 | // frame.repaint(); 205 | 206 | // Set the new parent node 207 | parent = lowestFCost(); 208 | 209 | if (parent == null) { 210 | System.out.println("END> NO PATH"); 211 | noPath = true; 212 | running = false; 213 | frame.repaint(); 214 | return; 215 | } 216 | 217 | if (Node.isEqual(parent, endNode)) { 218 | endNode.setParent(parent.getParent()); 219 | 220 | connectPath(); 221 | running = false; 222 | complete = true; 223 | frame.repaint(); 224 | return; 225 | } 226 | 227 | // Remove parent node from open list 228 | removeOpen(parent); 229 | // Add parent node to closed list 230 | addClosed(parent); 231 | 232 | // Allows correction for shortest path during runtime 233 | // When new parent Node is selected.. Checks all adjacent open 234 | // Nodes.. Then checks if the (G Score of parent + open Node 235 | // distance from parent) is less than the current G score 236 | // of the open node.. If true.. Sets parent of open Node 237 | // as new parent.. and re-calculates G, and F values 238 | if (diagonal) { 239 | for (int i = 0; i < 3; i++) { 240 | for (int j = 0; j < 3; j++) { 241 | if (i == 1 && j == 1) { 242 | continue; 243 | } 244 | int possibleX = (parent.getX() - size) + (size * i); 245 | int possibleY = (parent.getY() - size) + (size * j); 246 | Node openCheck = getOpenNode(possibleX, possibleY); 247 | 248 | // If spot being looked at, is an open node 249 | if (openCheck != null) { 250 | int distanceX = parent.getX() - openCheck.getX(); 251 | int distanceY = parent.getY() - openCheck.getY(); 252 | int newG = parent.getG(); 253 | 254 | if (distanceX != 0 && distanceY != 0) { 255 | newG += diagonalMoveCost; 256 | } else { 257 | newG += size; 258 | } 259 | 260 | if (newG < openCheck.getG()) { 261 | int s = searchOpen(possibleX, possibleY); 262 | open.get(s).setParent(parent); 263 | open.get(s).setG(newG); 264 | open.get(s).setF(open.get(s).getG() + open.get(s).getH()); 265 | } 266 | } 267 | } 268 | } 269 | } 270 | if(!frame.showSteps()) { 271 | findPath(parent); 272 | } 273 | else { 274 | par = parent; 275 | } 276 | } 277 | 278 | public void calculateNodeValues(int possibleX, int possibleY, Node openNode, Node parent) { 279 | // If the coordinates are outside of the borders 280 | if (possibleX < 0 | possibleY < 0 | possibleX >= frame.getWidth() | possibleY >= frame.getHeight()) { 281 | return; 282 | } 283 | 284 | // If the node is already a border node or a closed node or an 285 | // already open node, then don't make open node 286 | if (searchBorder(possibleX, possibleY) != -1 | searchClosed(possibleX, possibleY) != -1 287 | | searchOpen(possibleX, possibleY) != -1) { 288 | return; 289 | } 290 | // Create an open node with the available x and y 291 | // coordinates 292 | openNode = new Node(possibleX, possibleY); 293 | 294 | // Set the parent of the open node 295 | openNode.setParent(parent); 296 | 297 | // Calculating G cost 298 | // Cost to move from parent node to one open node (x 299 | // and 300 | // y 301 | // separately) 302 | int GxMoveCost = openNode.getX() - parent.getX(); 303 | int GyMoveCost = openNode.getY() - parent.getY(); 304 | int gCost = parent.getG(); 305 | 306 | if (GxMoveCost != 0 && GyMoveCost != 0) { 307 | gCost += diagonalMoveCost; 308 | } else { 309 | gCost += size; 310 | } 311 | openNode.setG(gCost); 312 | 313 | // Calculating H Cost 314 | int HxDiff = Math.abs(endNode.getX() - openNode.getX()); 315 | int HyDiff = Math.abs(endNode.getY() - openNode.getY()); 316 | int hCost = HxDiff + HyDiff; 317 | openNode.setH(hCost); 318 | 319 | // Calculating F Cost 320 | int fCost = gCost + hCost; 321 | openNode.setF(fCost); 322 | 323 | addOpen(openNode); 324 | } 325 | 326 | public void connectPath() { 327 | if (getPathList().size() == 0) { 328 | Node parentNode = endNode.getParent(); 329 | 330 | while (!Node.isEqual(parentNode, startNode)) { 331 | addPath(parentNode); 332 | 333 | for (int i = 0; i < getClosedList().size(); i++) { 334 | Node current = getClosedList().get(i); 335 | 336 | if (Node.isEqual(current, parentNode)) { 337 | parentNode = current.getParent(); 338 | break; 339 | } 340 | } 341 | } 342 | reverse(getPathList()); 343 | } 344 | 345 | } 346 | 347 | public void addBorder(Node node) { 348 | if (borders.size() == 0) { 349 | borders.add(node); 350 | } else if (!checkBorderDuplicate(node)) { 351 | borders.add(node); 352 | } 353 | } 354 | 355 | public void addOpen(Node node) { 356 | if (open.size() == 0) { 357 | open.add(node); 358 | } else if (!checkOpenDuplicate(node)) { 359 | open.add(node); 360 | } 361 | } 362 | 363 | public void addClosed(Node node) { 364 | if (closed.size() == 0) { 365 | closed.add(node); 366 | } else if (!checkClosedDuplicate(node)) { 367 | closed.add(node); 368 | } 369 | } 370 | 371 | public void addPath(Node node) { 372 | if (path.size() == 0) { 373 | path.add(node); 374 | } else { 375 | path.add(node); 376 | } 377 | } 378 | 379 | public void removePath(int location) { 380 | path.remove(location); 381 | } 382 | 383 | public void removeBorder(int location) { 384 | borders.remove(location); 385 | } 386 | 387 | public void removeOpen(int location) { 388 | open.remove(location); 389 | } 390 | 391 | public void removeOpen(Node node) { 392 | for (int i = 0; i < open.size(); i++) { 393 | if (node.getX() == open.get(i).getX() && node.getY() == open.get(i).getY()) { 394 | open.remove(i); 395 | } 396 | } 397 | } 398 | 399 | public void removeClosed(int location) { 400 | closed.remove(location); 401 | } 402 | 403 | public boolean checkBorderDuplicate(Node node) { 404 | for (int i = 0; i < borders.size(); i++) { 405 | if (node.getX() == borders.get(i).getX() && node.getY() == borders.get(i).getY()) { 406 | return true; 407 | } 408 | } 409 | return false; 410 | } 411 | 412 | public boolean checkOpenDuplicate(Node node) { 413 | for (int i = 0; i < open.size(); i++) { 414 | if (node.getX() == open.get(i).getX() && node.getY() == open.get(i).getY()) { 415 | return true; 416 | } 417 | } 418 | return false; 419 | } 420 | 421 | public boolean checkClosedDuplicate(Node node) { 422 | for (int i = 0; i < closed.size(); i++) { 423 | if (node.getX() == closed.get(i).getX() && node.getY() == closed.get(i).getY()) { 424 | return true; 425 | } 426 | } 427 | return false; 428 | } 429 | 430 | public int searchBorder(int xSearch, int ySearch) { 431 | int Location = -1; 432 | 433 | for (int i = 0; i < borders.size(); i++) { 434 | if (borders.get(i).getX() == xSearch && borders.get(i).getY() == ySearch) { 435 | Location = i; 436 | break; 437 | } 438 | } 439 | return Location; 440 | } 441 | 442 | public int searchClosed(int xSearch, int ySearch) { 443 | int Location = -1; 444 | 445 | for (int i = 0; i < closed.size(); i++) { 446 | if (closed.get(i).getX() == xSearch && closed.get(i).getY() == ySearch) { 447 | Location = i; 448 | break; 449 | } 450 | } 451 | return Location; 452 | } 453 | 454 | public int searchOpen(int xSearch, int ySearch) { 455 | int Location = -1; 456 | 457 | for (int i = 0; i < open.size(); i++) { 458 | if (open.get(i).getX() == xSearch && open.get(i).getY() == ySearch) { 459 | Location = i; 460 | break; 461 | } 462 | } 463 | return Location; 464 | } 465 | 466 | public void reverse(ArrayList list) { 467 | int j = list.size() - 1; 468 | 469 | for (int i = 0; i < j; i++) { 470 | Object temp = list.get(i); 471 | list.remove(i); 472 | list.add(i, list.get(j - 1)); 473 | list.remove(j); 474 | list.add(j, temp); 475 | j--; 476 | } 477 | } 478 | 479 | public Node lowestFCost() { 480 | if (open.size() > 0) { 481 | sort.bubbleSort(open); 482 | return open.get(0); 483 | } 484 | return null; 485 | } 486 | 487 | public ArrayList getBorderList() { 488 | return borders; 489 | } 490 | 491 | public ArrayList getOpenList() { 492 | return open; 493 | } 494 | 495 | public Node getOpen(int location) { 496 | return open.get(location); 497 | } 498 | 499 | public ArrayList getClosedList() { 500 | return closed; 501 | } 502 | 503 | public ArrayList getPathList() { 504 | return path; 505 | } 506 | 507 | public long getRunTime() { 508 | return runTime; 509 | } 510 | 511 | public void reset() { 512 | while(open.size() > 0) { 513 | open.remove(0); 514 | } 515 | 516 | while(closed.size() > 0) { 517 | closed.remove(0); 518 | } 519 | 520 | while(path.size() > 0) { 521 | path.remove(0); 522 | } 523 | noPath = false; 524 | running = false; 525 | complete = false; 526 | } 527 | 528 | public Node getOpenNode(int x, int y) { 529 | for (int i = 0; i < open.size(); i++) { 530 | if (open.get(i).getX() == x && open.get(i).getY() == y) { 531 | return open.get(i); 532 | } 533 | } 534 | return null; 535 | } 536 | 537 | public void printBorderList() { 538 | for (int i = 0; i < borders.size(); i++) { 539 | System.out.print(borders.get(i).getX() + ", " + borders.get(i).getY()); 540 | System.out.println(); 541 | } 542 | System.out.println("==============="); 543 | } 544 | 545 | public void printOpenList() { 546 | for (int i = 0; i < open.size(); i++) { 547 | System.out.print(open.get(i).getX() + ", " + open.get(i).getY()); 548 | System.out.println(); 549 | } 550 | System.out.println("==============="); 551 | } 552 | 553 | public void printPathList() { 554 | for (int i = 0; i < path.size(); i++) { 555 | System.out.print(i + ": " + path.get(i).getX() + ", " + path.get(i).getY() + ": " + path.get(i).getF()); 556 | System.out.println(); 557 | } 558 | System.out.println("==============="); 559 | } 560 | } 561 | -------------------------------------------------------------------------------- /src/ControlHandler.java: -------------------------------------------------------------------------------- 1 | import java.awt.Color; 2 | import java.awt.Dimension; 3 | import java.awt.Insets; 4 | import java.util.ArrayList; 5 | 6 | import javax.swing.JButton; 7 | import javax.swing.JCheckBox; 8 | import javax.swing.JLabel; 9 | import javax.swing.JSlider; 10 | import javax.swing.event.ChangeEvent; 11 | import javax.swing.event.ChangeListener; 12 | 13 | /* This class manages all components used on the main 14 | * control panel (bottom left) Meant to remove some 15 | * excessive graphics code from "Frame.java" class 16 | * by Devon Crawford 17 | */ 18 | public class ControlHandler { 19 | private Frame frame; 20 | private JLabel modeText, speedT, speedC, openT, 21 | closedT, pathT, openC, closedC, pathC, noPathT; 22 | private JCheckBox showStepsCheck, diagonalCheck, trigCheck; 23 | private JSlider speed; 24 | private JButton run; 25 | private ArrayList labels; 26 | private ArrayList checks; 27 | private ArrayList sliders; 28 | private ArrayList buttons; 29 | Dimension npD; 30 | 31 | public ControlHandler(Frame frame) { 32 | this.frame = frame; 33 | labels = new ArrayList(); 34 | checks = new ArrayList(); 35 | sliders = new ArrayList(); 36 | buttons = new ArrayList(); 37 | 38 | // Set up JLabels 39 | modeText = new JLabel("Mode: "); 40 | modeText.setName("modeText"); 41 | modeText.setFont(style.bigText); 42 | modeText.setForeground(style.darkText); 43 | modeText.setVisible(true); 44 | 45 | speedT = new JLabel("Speed: "); 46 | speedT.setName("speedT"); 47 | speedT.setFont(style.numbers); 48 | speedT.setVisible(true); 49 | 50 | speedC = new JLabel("50"); 51 | speedC.setName("speedC"); 52 | speedC.setFont(style.numbers); 53 | speedC.setVisible(true); 54 | 55 | openT = new JLabel("Open"); 56 | openT.setName("openT"); 57 | openT.setFont(style.numbers); 58 | openT.setVisible(true); 59 | 60 | openC = new JLabel("0"); 61 | openC.setName("openC"); 62 | openC.setFont(style.numbers); 63 | openC.setVisible(true); 64 | 65 | closedT = new JLabel("Closed"); 66 | closedT.setName("closedT"); 67 | closedT.setFont(style.numbers); 68 | closedT.setVisible(true); 69 | 70 | closedC = new JLabel("0"); 71 | closedC.setName("closedC"); 72 | closedC.setFont(style.numbers); 73 | closedC.setVisible(true); 74 | 75 | pathT = new JLabel("Path"); 76 | pathT.setName("pathT"); 77 | pathT.setFont(style.numbers); 78 | pathT.setVisible(true); 79 | 80 | pathC = new JLabel("0"); 81 | pathC.setName("pathC"); 82 | pathC.setFont(style.numbers); 83 | pathC.setVisible(true); 84 | 85 | noPathT = new JLabel("NO PATH"); 86 | noPathT.setName("noPathT"); 87 | noPathT.setForeground(Color.white); 88 | noPathT.setFont(style.REALBIGText); 89 | npD = noPathT.getPreferredSize(); 90 | 91 | // Add JLabels to list 92 | labels.add(modeText); 93 | labels.add(speedT); 94 | labels.add(speedC); 95 | labels.add(openT); 96 | labels.add(openC); 97 | labels.add(closedT); 98 | labels.add(closedC); 99 | labels.add(pathT); 100 | labels.add(pathC); 101 | labels.add(noPathT); 102 | 103 | // Set up JCheckBoxes 104 | showStepsCheck = new JCheckBox(); 105 | showStepsCheck.setText("showSteps"); 106 | showStepsCheck.setName("showStepsCheck"); 107 | showStepsCheck.setSelected(true); 108 | showStepsCheck.setOpaque(false); 109 | showStepsCheck.setFocusable(false); 110 | showStepsCheck.setVisible(true); 111 | 112 | diagonalCheck = new JCheckBox(); 113 | diagonalCheck.setText("Diagonal"); 114 | diagonalCheck.setName("diagonalCheck"); 115 | diagonalCheck.setOpaque(false); 116 | diagonalCheck.setSelected(true); 117 | diagonalCheck.setFocusable(false); 118 | diagonalCheck.setVisible(true); 119 | 120 | trigCheck = new JCheckBox(); 121 | trigCheck.setText("Trig"); 122 | trigCheck.setName("trigCheck"); 123 | trigCheck.setOpaque(false); 124 | trigCheck.setFocusable(false); 125 | trigCheck.setVisible(true); 126 | 127 | // Add JCheckboxes to list 128 | checks.add(showStepsCheck); 129 | checks.add(diagonalCheck); 130 | checks.add(trigCheck); 131 | 132 | // Set up JSliders 133 | speed = new JSlider(); 134 | speed.setName("speed"); 135 | speed.setOpaque(false); 136 | speed.setVisible(true); 137 | speed.setFocusable(false); 138 | speed.addChangeListener(new ChangeListener() { 139 | @Override 140 | public void stateChanged(ChangeEvent e) { 141 | JSlider source = (JSlider) e.getSource(); 142 | speed.setValue(source.getValue()); 143 | frame.setSpeed(); 144 | frame.repaint(); 145 | } 146 | }); 147 | 148 | // Add JSliders to list 149 | sliders.add(speed); 150 | 151 | // Set up JButtons 152 | run = new JButton(); 153 | run.setText("run"); 154 | run.setName("run"); 155 | run.setFocusable(false); 156 | run.addActionListener(frame); 157 | run.setMargin(new Insets(0,0,0,0)); 158 | run.setVisible(true); 159 | 160 | // Add JButtons to list 161 | buttons.add(run); 162 | } 163 | 164 | // Gets a specific JLabel by name 165 | public JLabel getL(String t) { 166 | for(int i = 0; i < labels.size(); i++) { 167 | if(labels.get(i).getName().equals(t)) { 168 | return labels.get(i); 169 | } 170 | } 171 | return null; 172 | } 173 | 174 | // Gets specific JCheckBox by name 175 | public JCheckBox getC(String t) { 176 | for(int i = 0; i < checks.size(); i++) { 177 | if(checks.get(i).getName().equals(t)) { 178 | return checks.get(i); 179 | } 180 | } 181 | return null; 182 | } 183 | 184 | // Gets specific JCheckBox by name 185 | public JSlider getS(String t) { 186 | for(int i = 0; i < sliders.size(); i++) { 187 | if(sliders.get(i).getName().equals(t)) { 188 | return sliders.get(i); 189 | } 190 | } 191 | return null; 192 | } 193 | 194 | // Gets specific JCheckBox by name 195 | public JButton getB(String t) { 196 | for(int i = 0; i < buttons.size(); i++) { 197 | if(buttons.get(i).getName().equals(t)) { 198 | return buttons.get(i); 199 | } 200 | } 201 | return null; 202 | } 203 | 204 | public void noPathTBounds() { 205 | noPathT.setBounds((int)((frame.getWidth()/2)-(npD.getWidth()/2)), 206 | (int)((frame.getHeight()/2)-70), 207 | (int)npD.getWidth(), (int)npD.getHeight()); 208 | } 209 | 210 | public void position() { 211 | // Set label bounds 212 | speedT.setBounds(180, frame.getHeight()-88, 60, 20); 213 | speedC.setBounds(224, frame.getHeight()-88, 60, 20); 214 | openT.setBounds(254, frame.getHeight()-92, 60, 20); 215 | openC.setBounds(300, frame.getHeight()-92, 60, 20); 216 | closedT.setBounds(254, frame.getHeight()-76, 60, 20); 217 | closedC.setBounds(300, frame.getHeight()-76, 60, 20); 218 | pathT.setBounds(254, frame.getHeight()-60, 60, 20); 219 | pathC.setBounds(300, frame.getHeight()-60, 60, 20); 220 | Dimension size = modeText.getPreferredSize(); 221 | modeText.setBounds(20, frame.getHeight() - 39, size.width, size.height); 222 | 223 | // Set check box bounds 224 | showStepsCheck.setBounds(20, frame.getHeight()-88, 90, 20); 225 | diagonalCheck.setBounds(20, frame.getHeight()-64, 90, 20); 226 | trigCheck.setBounds(112, frame.getHeight()-63, 50, 20); 227 | 228 | // Set slider bounds 229 | speed.setBounds(178, frame.getHeight()-63, 68, 20); 230 | 231 | // Set button bounds 232 | run.setBounds(116, frame.getHeight()-88, 52, 22); 233 | } 234 | 235 | // Sets text of JLabels to lightText 236 | public void hoverColour() { 237 | modeText.setForeground(style.lightText); 238 | showStepsCheck.setForeground(style.lightText); 239 | diagonalCheck.setForeground(style.lightText); 240 | trigCheck.setForeground(style.lightText); 241 | speed.setForeground(style.lightText); 242 | speedT.setForeground(style.lightText); 243 | speedC.setForeground(style.lightText); 244 | openT.setForeground(style.lightText); 245 | openC.setForeground(style.lightText); 246 | closedT.setForeground(style.lightText); 247 | closedC.setForeground(style.lightText); 248 | pathT.setForeground(style.lightText); 249 | pathC.setForeground(style.lightText); 250 | } 251 | 252 | // Sets text of JLabels to darkText 253 | public void nonHoverColour() { 254 | modeText.setForeground(style.darkText); 255 | showStepsCheck.setForeground(style.darkText); 256 | diagonalCheck.setForeground(style.darkText); 257 | trigCheck.setForeground(style.darkText); 258 | speed.setForeground(style.darkText); 259 | speedT.setForeground(style.darkText); 260 | speedC.setForeground(style.darkText); 261 | openT.setForeground(style.darkText); 262 | openC.setForeground(style.darkText); 263 | closedC.setForeground(style.darkText); 264 | closedT.setForeground(style.darkText); 265 | pathT.setForeground(style.darkText); 266 | pathC.setForeground(style.darkText); 267 | } 268 | 269 | // Adds all components to frame 270 | public void addAll() { 271 | frame.add(showStepsCheck); 272 | frame.add(diagonalCheck); 273 | frame.add(trigCheck); 274 | frame.add(run); 275 | frame.add(modeText); 276 | frame.add(openT); 277 | frame.add(openC); 278 | frame.add(closedT); 279 | frame.add(closedC); 280 | frame.add(pathT); 281 | frame.add(pathC); 282 | frame.add(speed); 283 | frame.add(speedT); 284 | frame.add(speedC); 285 | } 286 | 287 | } 288 | -------------------------------------------------------------------------------- /src/Frame.java: -------------------------------------------------------------------------------- 1 | import java.awt.Color; 2 | import java.awt.Dimension; 3 | import java.awt.Graphics; 4 | import java.awt.Insets; 5 | import java.awt.event.ActionEvent; 6 | import java.awt.event.ActionListener; 7 | import java.awt.event.KeyEvent; 8 | import java.awt.event.KeyListener; 9 | import java.awt.event.MouseEvent; 10 | import java.awt.event.MouseListener; 11 | import java.awt.event.MouseMotionListener; 12 | import java.awt.event.MouseWheelEvent; 13 | import java.awt.event.MouseWheelListener; 14 | 15 | import javax.swing.JButton; 16 | import javax.swing.JCheckBox; 17 | import javax.swing.JFrame; 18 | import javax.swing.JLabel; 19 | import javax.swing.JPanel; 20 | import javax.swing.JSlider; 21 | import javax.swing.SwingUtilities; 22 | import javax.swing.Timer; 23 | import javax.swing.event.ChangeEvent; 24 | import javax.swing.event.ChangeListener; 25 | 26 | /* The main graphics class for APathfinding. Controls the window, 27 | * and all path finding node graphics. Need to work on zoom function, 28 | * currently only zooms to top left corner rather than towards mouse 29 | * by Devon Crawford 30 | */ 31 | public class Frame extends JPanel 32 | implements ActionListener, MouseListener, MouseMotionListener, MouseWheelListener, KeyListener { 33 | 34 | ControlHandler ch; 35 | JFrame window; 36 | APathfinding pathfinding; 37 | boolean showSteps, btnHover; 38 | int size; 39 | double a1, a2; 40 | char currentKey = (char) 0; 41 | Node startNode, endNode; 42 | String mode; 43 | 44 | Timer timer = new Timer(100, this); 45 | int r = randomWithRange(0, 255); 46 | int G = randomWithRange(0, 255); 47 | int b = randomWithRange(0, 255); 48 | 49 | public static void main(String[] args) { 50 | new Frame(); 51 | } 52 | 53 | public Frame() { 54 | ch = new ControlHandler(this); 55 | size = 25; 56 | mode = "Map Creation"; 57 | showSteps = true; 58 | btnHover = false; 59 | setLayout(null); 60 | addMouseListener(this); 61 | addMouseMotionListener(this); 62 | addMouseWheelListener(this); 63 | addKeyListener(this); 64 | setFocusable(true); 65 | setFocusTraversalKeysEnabled(false); 66 | 67 | // Set up pathfinding 68 | pathfinding = new APathfinding(this, size); 69 | pathfinding.setDiagonal(true); 70 | 71 | // Calculating value of a in speed function 1 72 | a1 = (5000.0000 / (Math.pow(25.0000/5000, 1/49))); 73 | a2 = 625.0000; 74 | 75 | // Set up window 76 | window = new JFrame(); 77 | window.setContentPane(this); 78 | window.setTitle("A* Pathfinding Visualization"); 79 | window.getContentPane().setPreferredSize(new Dimension(700, 600)); 80 | window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 81 | window.pack(); 82 | window.setLocationRelativeTo(null); 83 | window.setVisible(true); 84 | 85 | // Add all controls 86 | ch.addAll(); 87 | 88 | this.revalidate(); 89 | this.repaint(); 90 | } 91 | 92 | public void paintComponent(Graphics g) { 93 | super.paintComponent(g); 94 | 95 | // Grab dimensions of panel 96 | int height = getHeight(); 97 | int width = getWidth(); 98 | 99 | // If no path is found 100 | if (pathfinding.isNoPath()) { 101 | // Set timer for animation 102 | timer.setDelay(50); 103 | timer.start(); 104 | 105 | // Set text of "run" button to "clear" 106 | ch.getB("run").setText("clear"); 107 | 108 | // Set mode to "No Path" 109 | mode = "No Path"; 110 | 111 | // Set up flicker animation 112 | Color flicker = new Color(r, G, b); 113 | g.setColor(flicker); 114 | g.fillRect(0, 0, getWidth(), getHeight()); 115 | 116 | // Place "No Path" text on screen in center 117 | ch.noPathTBounds(); 118 | ch.getL("noPathT").setVisible(true); 119 | this.add(ch.getL("noPathT")); 120 | this.revalidate(); 121 | } 122 | 123 | // If pathfinding is complete (found path) 124 | if (pathfinding.isComplete()) { 125 | // Set run button to clear 126 | ch.getB("run").setText("clear"); 127 | 128 | // Set timer delay, start for background animation 129 | timer.setDelay(50); 130 | timer.start(); 131 | 132 | // Make the background flicker 133 | Color flicker = new Color(r, G, b); 134 | g.setColor(flicker); 135 | g.fillRect(0, 0, getWidth(), getHeight()); 136 | 137 | // Set completed mode 138 | if(showSteps) { 139 | mode = "Completed"; 140 | } 141 | else { 142 | mode = "Completed in " + pathfinding.getRunTime() + "ms"; 143 | } 144 | } 145 | 146 | // Draws grid 147 | g.setColor(Color.lightGray); 148 | for (int j = 0; j < this.getHeight(); j += size) { 149 | for (int i = 0; i < this.getWidth(); i += size) { 150 | g.drawRect(i, j, size, size); 151 | } 152 | } 153 | 154 | // Draws all borders 155 | g.setColor(Color.black); 156 | for (int i = 0; i < pathfinding.getBorderList().size(); i++) { 157 | g.fillRect(pathfinding.getBorderList().get(i).getX() + 1, pathfinding.getBorderList().get(i).getY() + 1, 158 | size - 1, size - 1); 159 | } 160 | 161 | // Draws all open Nodes (path finding nodes) 162 | for (int i = 0; i < pathfinding.getOpenList().size(); i++) { 163 | Node current = pathfinding.getOpenList().get(i); 164 | g.setColor(style.greenHighlight); 165 | g.fillRect(current.getX() + 1, current.getY() + 1, size - 1, size - 1); 166 | 167 | drawInfo(current, g); 168 | } 169 | 170 | // Draws all closed nodes 171 | for (int i = 0; i < pathfinding.getClosedList().size(); i++) { 172 | Node current = pathfinding.getClosedList().get(i); 173 | 174 | g.setColor(style.redHighlight); 175 | g.fillRect(current.getX() + 1, current.getY() + 1, size - 1, size - 1); 176 | 177 | drawInfo(current, g); 178 | } 179 | 180 | // Draw all final path nodes 181 | for (int i = 0; i < pathfinding.getPathList().size(); i++) { 182 | Node current = pathfinding.getPathList().get(i); 183 | 184 | g.setColor(style.blueHighlight); 185 | g.fillRect(current.getX() + 1, current.getY() + 1, size - 1, size - 1); 186 | 187 | drawInfo(current, g); 188 | } 189 | 190 | // Draws start of path 191 | if (startNode != null) { 192 | g.setColor(Color.blue); 193 | g.fillRect(startNode.getX() + 1, startNode.getY() + 1, size - 1, size - 1); 194 | } 195 | // Draws end of path 196 | if (endNode != null) { 197 | g.setColor(Color.red); 198 | g.fillRect(endNode.getX() + 1, endNode.getY() + 1, size - 1, size - 1); 199 | } 200 | 201 | // If control panel is being hovered, change colours 202 | if(btnHover) { 203 | g.setColor(style.darkText); 204 | ch.hoverColour(); 205 | } 206 | else { 207 | g.setColor(style.btnPanel); 208 | ch.nonHoverColour(); 209 | } 210 | // Drawing control panel rectangle 211 | g.fillRect(10, height-96, 322, 90); 212 | 213 | // Setting mode text 214 | ch.getL("modeText").setText("Mode: " + mode); 215 | 216 | // Position all controls 217 | ch.position(); 218 | 219 | // Setting numbers in pathfinding lists 220 | ch.getL("openC").setText(Integer.toString(pathfinding.getOpenList().size())); 221 | ch.getL("closedC").setText(Integer.toString(pathfinding.getClosedList().size())); 222 | ch.getL("pathC").setText(Integer.toString(pathfinding.getPathList().size())); 223 | 224 | // Setting speed number text in showSteps or !showSteps mode 225 | if(showSteps) { 226 | ch.getL("speedC").setText(Integer.toString(ch.getS("speed").getValue())); 227 | } 228 | else { 229 | ch.getL("speedC").setText("N/A"); 230 | } 231 | 232 | // Getting values from checkboxes 233 | showSteps = ch.getC("showStepsCheck").isSelected(); 234 | pathfinding.setDiagonal(ch.getC("diagonalCheck").isSelected()); 235 | pathfinding.setTrig(ch.getC("trigCheck").isSelected()); 236 | } 237 | 238 | // Draws info (f, g, h) on current node 239 | public void drawInfo(Node current, Graphics g) { 240 | if (size > 50) { 241 | g.setFont(style.numbers); 242 | g.setColor(Color.black); 243 | g.drawString(Integer.toString(current.getF()), current.getX() + 4, current.getY() + 16); 244 | g.setFont(style.smallNumbers); 245 | g.drawString(Integer.toString(current.getG()), current.getX() + 4, current.getY() + size - 7); 246 | g.drawString(Integer.toString(current.getH()), current.getX() + size - 26, current.getY() + size - 7); 247 | } 248 | } 249 | 250 | public void MapCalculations(MouseEvent e) { 251 | // If left mouse button is clicked 252 | if (SwingUtilities.isLeftMouseButton(e)) { 253 | // If 's' is pressed create start node 254 | if (currentKey == 's') { 255 | int xRollover = e.getX() % size; 256 | int yRollover = e.getY() % size; 257 | 258 | if (startNode == null) { 259 | startNode = new Node(e.getX() - xRollover, e.getY() - yRollover); 260 | } else { 261 | startNode.setXY(e.getX() - xRollover, e.getY() - yRollover); 262 | } 263 | repaint(); 264 | } 265 | // If 'e' is pressed create end node 266 | else if (currentKey == 'e') { 267 | int xRollover = e.getX() % size; 268 | int yRollover = e.getY() % size; 269 | 270 | if (endNode == null) { 271 | endNode = new Node(e.getX() - xRollover, e.getY() - yRollover); 272 | } else { 273 | endNode.setXY(e.getX() - xRollover, e.getY() - yRollover); 274 | } 275 | repaint(); 276 | } 277 | // Otherwise, create a wall 278 | else { 279 | int xBorder = e.getX() - (e.getX() % size); 280 | int yBorder = e.getY() - (e.getY() % size); 281 | 282 | Node newBorder = new Node(xBorder, yBorder); 283 | pathfinding.addBorder(newBorder); 284 | 285 | repaint(); 286 | } 287 | } 288 | // If right mouse button is clicked 289 | else if (SwingUtilities.isRightMouseButton(e)) { 290 | int mouseBoxX = e.getX() - (e.getX() % size); 291 | int mouseBoxY = e.getY() - (e.getY() % size); 292 | 293 | // If 's' is pressed remove start node 294 | if (currentKey == 's') { 295 | if (startNode != null && mouseBoxX == startNode.getX() && startNode.getY() == mouseBoxY) { 296 | startNode = null; 297 | repaint(); 298 | } 299 | } 300 | // If 'e' is pressed remove end node 301 | else if (currentKey == 'e') { 302 | if (endNode != null && mouseBoxX == endNode.getX() && endNode.getY() == mouseBoxY) { 303 | endNode = null; 304 | repaint(); 305 | } 306 | } 307 | // Otherwise, remove wall 308 | else { 309 | int Location = pathfinding.searchBorder(mouseBoxX, mouseBoxY); 310 | if (Location != -1) { 311 | pathfinding.removeBorder(Location); 312 | } 313 | repaint(); 314 | } 315 | } 316 | } 317 | 318 | @Override 319 | public void mouseClicked(MouseEvent e) { 320 | MapCalculations(e); 321 | } 322 | 323 | @Override 324 | public void mousePressed(MouseEvent e) {} 325 | 326 | @Override 327 | public void mouseReleased(MouseEvent e) {} 328 | 329 | @Override 330 | public void mouseEntered(MouseEvent e) {} 331 | 332 | @Override 333 | public void mouseExited(MouseEvent e) {} 334 | 335 | @Override 336 | public void mouseDragged(MouseEvent e) { 337 | MapCalculations(e); 338 | } 339 | 340 | @Override 341 | // Track mouse on movement 342 | public void mouseMoved(MouseEvent e) { 343 | int x = e.getX(); 344 | int y = e.getY(); 345 | int height = this.getHeight(); 346 | 347 | // Detects if mouse is within button panel 348 | if(x >= 10 && x <= 332 && y >= (height-96) && y <= (height-6)) { 349 | btnHover = true; 350 | } 351 | else { 352 | btnHover = false; 353 | } 354 | repaint(); 355 | } 356 | 357 | @Override 358 | public void keyTyped(KeyEvent e) {} 359 | 360 | @Override 361 | public void keyPressed(KeyEvent e) { 362 | char key = e.getKeyChar(); 363 | currentKey = key; 364 | 365 | // Start if space is pressed 366 | if (currentKey == KeyEvent.VK_SPACE) { 367 | ch.getB("run").setText("stop"); 368 | start(); 369 | } 370 | } 371 | 372 | @Override 373 | public void keyReleased(KeyEvent e) { 374 | currentKey = (char) 0; 375 | } 376 | 377 | // Starts path finding 378 | void start() { 379 | if(startNode != null && endNode != null) { 380 | if (!showSteps) { 381 | pathfinding.start(startNode, endNode); 382 | } else { 383 | pathfinding.setup(startNode, endNode); 384 | setSpeed(); 385 | timer.start(); 386 | } 387 | } 388 | else { 389 | System.out.println("ERROR: Needs start and end points to run."); 390 | } 391 | } 392 | 393 | @Override 394 | // Scales the map with mouse wheel scroll 395 | public void mouseWheelMoved(MouseWheelEvent m) { 396 | int rotation = m.getWheelRotation(); 397 | double prevSize = size; 398 | int scroll = 3; 399 | 400 | // Changes size of grid based on scroll 401 | if (rotation == -1 && size + scroll < 200) { 402 | size += scroll; 403 | } else if (rotation == 1 && size - scroll > 2) { 404 | size += -scroll; 405 | } 406 | pathfinding.setSize(size); 407 | double ratio = size / prevSize; 408 | 409 | // new X and Y values for Start 410 | if (startNode != null) { 411 | int sX = (int) Math.round(startNode.getX() * ratio); 412 | int sY = (int) Math.round(startNode.getY() * ratio); 413 | startNode.setXY(sX, sY); 414 | } 415 | 416 | // new X and Y values for End 417 | if (endNode != null) { 418 | int eX = (int) Math.round(endNode.getX() * ratio); 419 | int eY = (int) Math.round(endNode.getY() * ratio); 420 | endNode.setXY(eX, eY); 421 | } 422 | 423 | // new X and Y values for borders 424 | for (int i = 0; i < pathfinding.getBorderList().size(); i++) { 425 | int newX = (int) Math.round((pathfinding.getBorderList().get(i).getX() * ratio)); 426 | int newY = (int) Math.round((pathfinding.getBorderList().get(i).getY() * ratio)); 427 | pathfinding.getBorderList().get(i).setXY(newX, newY); 428 | } 429 | 430 | // New X and Y for Open nodes 431 | for (int i = 0; i < pathfinding.getOpenList().size(); i++) { 432 | int newX = (int) Math.round((pathfinding.getOpenList().get(i).getX() * ratio)); 433 | int newY = (int) Math.round((pathfinding.getOpenList().get(i).getY() * ratio)); 434 | pathfinding.getOpenList().get(i).setXY(newX, newY); 435 | } 436 | 437 | // New X and Y for Closed Nodes 438 | for (int i = 0; i < pathfinding.getClosedList().size(); i++) { 439 | if (!Node.isEqual(pathfinding.getClosedList().get(i), startNode)) { 440 | int newX = (int) Math.round((pathfinding.getClosedList().get(i).getX() * ratio)); 441 | int newY = (int) Math.round((pathfinding.getClosedList().get(i).getY() * ratio)); 442 | pathfinding.getClosedList().get(i).setXY(newX, newY); 443 | } 444 | } 445 | repaint(); 446 | } 447 | 448 | @Override 449 | public void actionPerformed(ActionEvent e) { 450 | // Moves one step ahead in path finding (called on timer) 451 | if (pathfinding.isRunning() && showSteps) { 452 | pathfinding.findPath(pathfinding.getPar()); 453 | mode = "Running"; 454 | } 455 | // Finish pathfinding background flicker! 456 | if (pathfinding.isComplete() || pathfinding.isNoPath()) { 457 | r = (int) (Math.random() * ((r + 15) - (r - 15)) + (r - 15)); 458 | G = (int) (Math.random() * ((G + 15) - (G - 15)) + (G - 15)); 459 | b = (int) (Math.random() * ((b + 15) - (b - 15)) + (b - 15)); 460 | 461 | if (r >= 240 | r <= 15) { 462 | r = randomWithRange(0, 255); 463 | } 464 | if (G >= 240 | G <= 15) { 465 | G = randomWithRange(0, 255); 466 | } 467 | if (b >= 240 | b <= 15) { 468 | b = randomWithRange(0, 255); 469 | } 470 | } 471 | 472 | // Actions of run/stop/clear button 473 | if(e.getActionCommand() != null) { 474 | if(e.getActionCommand().equals("run") && !pathfinding.isRunning()) { 475 | ch.getB("run").setText("stop"); 476 | start(); 477 | } 478 | else if(e.getActionCommand().equals("clear")) { 479 | ch.getB("run").setText("run"); 480 | mode = "Map Creation"; 481 | ch.getL("noPathT").setVisible(false); 482 | pathfinding.reset(); 483 | } 484 | else if(e.getActionCommand().equals("stop")) { 485 | ch.getB("run").setText("start"); 486 | timer.stop(); 487 | } 488 | else if(e.getActionCommand().equals("start")) { 489 | ch.getB("run").setText("stop"); 490 | timer.start(); 491 | } 492 | } 493 | repaint(); 494 | } 495 | 496 | // Returns random number between min and max 497 | int randomWithRange(int min, int max) 498 | { 499 | int range = (max - min) + 1; 500 | return (int)(Math.random() * range) + min; 501 | } 502 | 503 | // Calculates delay with two exponential functions 504 | void setSpeed() { 505 | int delay = 0; 506 | int value = ch.getS("speed").getValue(); 507 | 508 | if(value == 0) { 509 | timer.stop(); 510 | } 511 | else if(value >= 1 && value < 50) { 512 | if(!timer.isRunning()) { 513 | timer.start(); 514 | } 515 | // Exponential function. value(1) == delay(5000). value (50) == delay(25) 516 | delay = (int)(a1 * (Math.pow(25/5000.0000, value / 49.0000))); 517 | } 518 | else if(value >= 50 && value <= 100) { 519 | if(!timer.isRunning()) { 520 | timer.start(); 521 | } 522 | // Exponential function. value (50) == delay(25). value(100) == delay(1). 523 | delay = (int)(a2 * (Math.pow(1/25.0000, value/50.0000))); 524 | } 525 | timer.setDelay(delay); 526 | } 527 | 528 | boolean showSteps() { 529 | return showSteps; 530 | } 531 | } 532 | 533 | 534 | -------------------------------------------------------------------------------- /src/Node.java: -------------------------------------------------------------------------------- 1 | 2 | /* Node.java is an object used for every node (or block) 3 | * on the grid for path finding. Including walls, open, closed 4 | * path, start and end nodes. This class allows each node to 5 | * store its position, calculations (f, g, h), parents, and 6 | * determine equalities to other nodes. 7 | * by Devon Crawford 8 | */ 9 | public class Node { 10 | private int x, y, g, h, f; 11 | private Node parent; 12 | 13 | public Node(int x, int y) { 14 | this.x = x; 15 | this.y = y; 16 | } 17 | 18 | public int getX() { 19 | return x; 20 | } 21 | 22 | public int getY() { 23 | return y; 24 | } 25 | 26 | public int getG() { 27 | return g; 28 | } 29 | 30 | public int getH() { 31 | return h; 32 | } 33 | 34 | public int getF() { 35 | return f; 36 | } 37 | 38 | public Node getNode() { 39 | return parent; 40 | } 41 | 42 | public Node getParent() { 43 | return parent; 44 | } 45 | 46 | public void setXY(int x, int y) { 47 | this.x = x; 48 | this.y = y; 49 | } 50 | 51 | public void setG(int g) { 52 | this.g = g; 53 | } 54 | 55 | public void setH(int h) { 56 | this.h = h; 57 | } 58 | 59 | public void setF(int f) { 60 | this.f = f; 61 | } 62 | 63 | public void setParent(Node parent) { 64 | this.parent = parent; 65 | } 66 | 67 | public static boolean isEqual(Node s, Node e) { 68 | if (s.getX() == e.getX() && s.getY() == e.getY()) { 69 | return true; 70 | } 71 | return false; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Sort.java: -------------------------------------------------------------------------------- 1 | import java.util.ArrayList; 2 | 3 | /* Sort functions used in path finding to 4 | * determine lowest F cost Node. Bubble sort is mainly 5 | * used, quick sort needs work. Currently not working 6 | * by Devon Crawford 7 | */ 8 | public class Sort { 9 | 10 | private boolean lowToHigh, highToLow; 11 | 12 | public Sort() { 13 | lowToHigh = true; 14 | highToLow = false; 15 | } 16 | 17 | public void bubbleSort(int[] data) { 18 | int Switch = -1; 19 | int temp; 20 | 21 | while (Switch != 0) { 22 | Switch = 0; 23 | 24 | if (lowToHigh) { 25 | for (int i = 0; i < data.length - 1; i++) { 26 | if (data[i] > data[i + 1]) { 27 | temp = data[i]; 28 | data[i] = data[i + 1]; 29 | data[i + 1] = temp; 30 | Switch = 1; 31 | } 32 | } 33 | } else if (highToLow) { 34 | for (int i = 0; i < data.length - 1; i++) { 35 | if (data[i] < data[i + 1]) { 36 | temp = data[i]; 37 | data[i] = data[i + 1]; 38 | data[i + 1] = temp; 39 | Switch = 1; 40 | } 41 | } 42 | } 43 | } 44 | } 45 | 46 | public void bubbleSort(ArrayList list) { 47 | int Switch = -1; 48 | Node temp; 49 | 50 | while (Switch != 0) { 51 | Switch = 0; 52 | 53 | if (lowToHigh) { 54 | for (int i = 0; i < list.size() - 1; i++) { 55 | if (list.get(i).getF() > list.get(i + 1).getF()) { 56 | temp = list.get(i); 57 | list.remove(i); 58 | list.add(i + 1, temp); 59 | Switch = 1; 60 | } 61 | } 62 | } else if (highToLow) { 63 | for (int i = 0; i < list.size() - 1; i++) { 64 | if (list.get(i).getF() < list.get(i + 1).getF()) { 65 | temp = list.get(i); 66 | list.remove(i); 67 | list.add(i + 1, temp); 68 | Switch = 1; 69 | } 70 | } 71 | } 72 | } 73 | } 74 | 75 | // low is 0, high is numbers.length - 1 76 | // TODO: FIX HIGH TO LOW QUICKSORT 77 | public void quickSort(int[] numbers, int low, int high) { 78 | int i = low, j = high; 79 | 80 | int pivot = numbers[low + (high - low) / 2]; 81 | 82 | while (i <= j) { 83 | 84 | if (lowToHigh) { 85 | while (numbers[i] < pivot) { 86 | i++; 87 | } 88 | 89 | while (numbers[j] > pivot) { 90 | j--; 91 | } 92 | } else if (highToLow) { 93 | while (numbers[i] > pivot) { 94 | i++; 95 | } 96 | 97 | while (numbers[j] < pivot) { 98 | j--; 99 | } 100 | } 101 | 102 | if (i <= j) { 103 | int temp = numbers[i]; 104 | numbers[i] = numbers[j]; 105 | numbers[j] = temp; 106 | i++; 107 | j--; 108 | } 109 | } 110 | 111 | if (low < j) 112 | quickSort(numbers, low, j); 113 | if (i < high) 114 | quickSort(numbers, i, high); 115 | } // end of quick sort 116 | 117 | public void setLowToHigh() { 118 | lowToHigh = true; 119 | highToLow = false; 120 | } 121 | 122 | public void setHighToLow() { 123 | lowToHigh = false; 124 | highToLow = true; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/style.java: -------------------------------------------------------------------------------- 1 | import java.awt.Color; 2 | import java.awt.Font; 3 | 4 | /* Custom fonts and colours used in "Frame.java" class 5 | * by Devon Crawford 6 | */ 7 | public @interface style { 8 | Font bigText = new Font("arial", Font.BOLD, 24); 9 | Font REALBIGText = new Font("arial", Font.BOLD, 72); 10 | Font numbers = new Font("arial", Font.BOLD, 12); 11 | Font smallNumbers = new Font("arial", Font.PLAIN, 11); 12 | Color greenHighlight = new Color(132, 255, 138); 13 | Color redHighlight = new Color(253, 90, 90); 14 | Color blueHighlight = new Color(32, 233, 255); 15 | Color btnPanel = new Color(120, 120, 120, 80); 16 | Color darkText = new Color(48, 48, 48); 17 | Color lightText = new Color(232, 232, 232); 18 | } 19 | --------------------------------------------------------------------------------