├── BiDiAstarSearch.py ├── README.md ├── UniDiAstarSearch.py ├── arena.map └── arena.map.scen /BiDiAstarSearch.py: -------------------------------------------------------------------------------- 1 | import math 2 | from termcolor import colored 3 | 4 | 5 | class Heap: 6 | 7 | def __init__(self): 8 | 9 | self.Discovered = [] 10 | 11 | # method for adding the givenItem to the heap 12 | def addToHeap(self, givenItem): 13 | 14 | self.Discovered.append(givenItem) 15 | self.heapify() 16 | 17 | # method for getting the deep copy of the heap list 18 | def getHeap(self): 19 | 20 | return self.Discovered 21 | 22 | # function for updating the cost of a node in the heap 23 | def updateNodeCostInHeap(self, currentNode, fValue, gValue, sourceNode): 24 | 25 | for node in self.Discovered: 26 | 27 | if node.position == currentNode.position: 28 | 29 | node.g = gValue 30 | node.f = fValue 31 | node.parent = sourceNode 32 | self.heapify() 33 | 34 | # method to change the position of the elements in the heap in order to satisfy the heap property 35 | def heapify(self): 36 | 37 | for item in range(len(self.Discovered)): 38 | 39 | # if the index is greater than or equal to 1 and the parent is greater than children, then swap 40 | while item >= 1 and self.Discovered[item].f <= self.Discovered[item//2].f: 41 | 42 | if self.Discovered[item].f < self.Discovered[item//2].f: 43 | 44 | self.swap(self.Discovered, item, item // 2) 45 | 46 | elif self.Discovered[item].f == self.Discovered[item//2].f: 47 | 48 | if self.Discovered[item].h < self.Discovered[item//2].h: 49 | 50 | self.swap(self.Discovered, item, item // 2) 51 | 52 | item = item // 2 53 | 54 | # method to get the minimum item from the heap 55 | def minItemInHeap(self): 56 | 57 | result = self.Discovered.pop(0) 58 | self.heapify() 59 | return result 60 | 61 | # method to get the value of the root element at the heap 62 | def rootItemAtHeap(self): 63 | 64 | return self.Discovered[0] 65 | 66 | # method for swapping the values in the heap 67 | def swap(self, heap, firstIndex, secondIndex): 68 | 69 | tempVal = heap[firstIndex] 70 | heap[firstIndex] = heap[secondIndex] 71 | heap[secondIndex] = tempVal 72 | 73 | 74 | class Node: 75 | 76 | def __init__(self, parent, position): 77 | 78 | self.parent = parent 79 | self.secondParent = None 80 | self.position = position 81 | 82 | self.g = 0 83 | self.h = 0 84 | self.f = 0 85 | 86 | 87 | class Graph: 88 | 89 | def __init__(self): 90 | 91 | self.map = [] 92 | self.height = 0 93 | self.width = 0 94 | 95 | # method for building the map in the required format 96 | def buildGraph(self, filename): 97 | 98 | file = open(filename, "r") 99 | 100 | lineNumber = 0 101 | 102 | for line in file: 103 | 104 | lineNumber += 1 105 | 106 | # to record the height of the map 107 | if lineNumber == 2: 108 | 109 | line = line.replace("\n", "") 110 | line = line.split(" ") 111 | self.height = int(line[-1]) - 1 112 | 113 | # to record the width of the map 114 | elif lineNumber == 3: 115 | 116 | line = line.replace("\n", "") 117 | line = line.split(" ") 118 | self.width = int(line[-1]) - 1 119 | 120 | # condition to check whether we are only storing those lines from the map which are having their first character as a map terrain 121 | elif line[0] == "@" or line[0] == "T" or line[0] == ".": 122 | 123 | # checking if the line is having the new line character 124 | if line[-1] == "\n": 125 | 126 | # if the line is having new line character, then append the entire line except the last character 127 | self.map.append(line[:-1]) 128 | 129 | else: 130 | 131 | # if the line is not having new line character, then append the entire line 132 | self.map.append(line) 133 | 134 | # method for calculating the heuristic (Euclidean Distance) 135 | def heuristic(self, currentNode, targetNode): 136 | 137 | # -------------Octile Heuristic------------------------------ 138 | 139 | xVal = abs(currentNode.position[0] - targetNode.position[0]) 140 | yVal = abs(currentNode.position[1] - targetNode.position[1]) 141 | 142 | return max(xVal, yVal) + ((math.sqrt(2)-1)*min(xVal, yVal)) 143 | 144 | # -------------Octile Heuristic------------------------------ 145 | 146 | # -------------Euclidean Heuristic------------------------- 147 | 148 | # return math.sqrt((currentNode.position[0] - targetNode.position[0]) ** 2 + (currentNode.position[1] - targetNode.position[1]) ** 2) 149 | 150 | # -------------Euclidean Heuristic------------------------- 151 | 152 | # method for getting the node with minimum value from the either direction 153 | def minValueNodeFromEitherDirection(self, forwardDiscovered, backwardDiscovered, forwardFinalized, backwardFinalised): 154 | 155 | forwardMinItem = forwardDiscovered.rootItemAtHeap() 156 | backwardMinItem = backwardDiscovered.rootItemAtHeap() 157 | 158 | if forwardMinItem.f < backwardMinItem.f: 159 | 160 | result = forwardDiscovered.minItemInHeap() 161 | forwardFinalized.append(result) 162 | return result, "forward" 163 | 164 | elif forwardMinItem.f > backwardMinItem.f: 165 | 166 | result = backwardDiscovered.minItemInHeap() 167 | backwardFinalised.append(result) 168 | return result, "backward" 169 | 170 | elif forwardMinItem.f == backwardMinItem.f: 171 | 172 | if forwardMinItem.h > backwardMinItem.h: 173 | 174 | result = forwardDiscovered.minItemInHeap() 175 | forwardFinalized.append(result) 176 | return result, "forward" 177 | 178 | else: 179 | 180 | result = backwardDiscovered.minItemInHeap() 181 | backwardFinalised.append(result) 182 | return result, "backward" 183 | 184 | def minItemFromList(self, node, givenList): 185 | 186 | for item in givenList: 187 | 188 | if item.position == node.position: 189 | 190 | return True 191 | 192 | else: 193 | 194 | return False 195 | 196 | # method implementing bi-directional search algorithm 197 | def biDirectionalSearch(self, source, target): 198 | 199 | # if the source and the target, are the same, then return an empty path with a cost of 0 200 | if source == target: 201 | 202 | return ([], 0) 203 | 204 | sourceX, sourceY = source[0], source[1] 205 | targetX, targetY = target[0], target[1] 206 | 207 | # if the coordinates of the source or the target node are negative, then return an empty path with a cost of 0 208 | if sourceX < 0 or sourceY < 0 or targetX < 0 or targetY < 0: 209 | 210 | print("The source or target is negative.") 211 | return ([], 0) 212 | 213 | # if the coordinates of the source are out of range, then return an empty path with a cost of 0 214 | if (sourceX > self.height) or (sourceY > self.width): 215 | 216 | print("The source is out of range of the map.") 217 | return ([], 0) 218 | 219 | # if the coordinates of the target are out of range, then return an empty path with a cost of 0 220 | if (targetX > self.height) or (targetY > self.width): 221 | 222 | print("The target is out of range of the map.") 223 | return ([], 0) 224 | 225 | # if the source is not walkable, then return an empty path with a cost of 0 226 | if self.map[sourceX][sourceY] != ".": 227 | 228 | print("The source is not walkable. (", self.map[sourceX][sourceY], ")") 229 | return ([], 0) 230 | 231 | # if the target is not walkable, then return an empty path with a cost of 0 232 | if self.map[targetX][targetY] != ".": 233 | 234 | print("The target is not walkable. (", self.map[targetX][targetY], ")") 235 | return ([], 0) 236 | 237 | sourceNode = Node(None, source) 238 | targetNode = Node(None, target) 239 | 240 | # initialising the forwardDiscovered list as a heap and the finalized list as a normal list 241 | forwardDiscovered = Heap() 242 | forwardFinalized = [] 243 | 244 | backwardDiscovered = Heap() 245 | backwardFinalised = [] 246 | 247 | forwardDiscovered.addToHeap(sourceNode) 248 | backwardDiscovered.addToHeap(targetNode) 249 | 250 | numberOfNodes = 0 251 | 252 | while len(forwardDiscovered.getHeap()) != 0 and len(backwardDiscovered.getHeap()) != 0: 253 | 254 | # get the node with the smallest value from the either direction (forward or backward) 255 | smallestValueNode = self.minValueNodeFromEitherDirection(forwardDiscovered, backwardDiscovered, forwardFinalized, backwardFinalised) 256 | 257 | # if the smallest value node happens to be from the forward frontier 258 | if smallestValueNode[1] == "forward": 259 | 260 | # if the smallest value node happens to be from the forward frontier and it happens to be in the finalised list of the backward frontier 261 | if self.minItemFromList(smallestValueNode[0], backwardFinalised): 262 | 263 | shortestPath = [] 264 | 265 | currentNode = smallestValueNode[0] 266 | totalCost = currentNode.f 267 | 268 | # tracing back to the source node in order to retrieve the path 269 | while currentNode is not None: 270 | shortestPath = [currentNode.position] + shortestPath 271 | currentNode = currentNode.parent 272 | 273 | return shortestPath, totalCost, numberOfNodes 274 | 275 | if True: 276 | 277 | neighborNode = [(0, 1), (0, -1), (1, 0), (-1, 0), (1, 1), (1, -1), (-1, 1), (-1, -1)] 278 | 279 | for node in neighborNode: 280 | 281 | # getting the position of the current node 282 | nodePositionX, nodePositionY = smallestValueNode[0].position[0] + node[0], smallestValueNode[0].position[1] + node[1] 283 | 284 | # if the neighbouring node is out of range, then skip to next node 285 | if nodePositionX > self.height or nodePositionY > self.width or nodePositionY < 0 or nodePositionX < 0: 286 | continue 287 | 288 | # if the neighbouring node is not walkable, then skip to next neighbouring node 289 | if self.map[nodePositionX][nodePositionY] != ".": 290 | continue 291 | 292 | childNodePosition = (nodePositionX, nodePositionY) 293 | childNodeParent = smallestValueNode[0] 294 | childNode = Node(childNodeParent, childNodePosition) 295 | 296 | firstFlagChecker = False 297 | 298 | # if child node is in the finalised list, then skip to the next neighbouring node 299 | for forwardFinalizedNode in forwardFinalized: 300 | 301 | if forwardFinalizedNode.position == childNodePosition: 302 | 303 | firstFlagChecker = True 304 | 305 | break 306 | 307 | if firstFlagChecker: 308 | 309 | continue 310 | 311 | # if the child node is not in the finalized list, then search the forwardDiscovered list 312 | secondFlagChecker = True 313 | 314 | forwardDiscoveredList = forwardDiscovered.getHeap() 315 | 316 | # checking if the node is in the forwardDiscovered list, if the node is in the forwardDiscovered list then skip to the next node 317 | for forwardDiscoveredNode in forwardDiscoveredList: 318 | 319 | if forwardDiscoveredNode.position == childNodePosition: 320 | 321 | secondFlagChecker = False 322 | 323 | break 324 | 325 | if secondFlagChecker: 326 | 327 | # if the child node happens to be in the diagonal 328 | if node == (1, 1) or node == (1, -1) or node == (-1, 1) or node == (-1, -1): 329 | 330 | # if we are moving in the upper right or bottom right corner, then check that there are not obstacles in the corners in order to avoid corner cutting 331 | if (node == (1, 1) or node == (-1, 1)) and self.map[smallestValueNode[0].position[0]][smallestValueNode[0].position[1] + 1] == ".": 332 | 333 | if node == (1, 1) and self.map[smallestValueNode[0].position[0] + 1][smallestValueNode[0].position[1]] == ".": 334 | 335 | numberOfNodes += 1 336 | 337 | childNode.g = childNodeParent.g + math.sqrt(2) 338 | childNode.h = self.heuristic(childNode, targetNode) 339 | childNode.f = childNode.g + childNode.h 340 | forwardDiscovered.addToHeap(childNode) 341 | 342 | elif node == (-1, 1) and self.map[smallestValueNode[0].position[0] - 1][smallestValueNode[0].position[1]] == ".": 343 | 344 | numberOfNodes += 1 345 | 346 | childNode.g = childNodeParent.g + math.sqrt(2) 347 | childNode.h = self.heuristic(childNode, targetNode) 348 | childNode.f = childNode.g + childNode.h 349 | forwardDiscovered.addToHeap(childNode) 350 | 351 | # if we are moving in the upper left or bottom left corner, then check that there are not obstacles in the corners in order to avoid corner cutting 352 | elif (node == (1, -1) or node == (-1, -1)) and (self.map[smallestValueNode[0].position[0]][smallestValueNode[0].position[1] - 1] == "."): 353 | 354 | if node == (1, -1) and self.map[smallestValueNode[0].position[0] + 1][smallestValueNode[0].position[1]] == ".": 355 | 356 | numberOfNodes += 1 357 | 358 | childNode.g = childNodeParent.g + math.sqrt(2) 359 | childNode.h = self.heuristic(childNode, targetNode) 360 | childNode.f = childNode.g + childNode.h 361 | forwardDiscovered.addToHeap(childNode) 362 | 363 | elif node == (-1, -1) and self.map[smallestValueNode[0].position[0] - 1][smallestValueNode[0].position[1]] == ".": 364 | 365 | numberOfNodes += 1 366 | 367 | childNode.g = childNodeParent.g + math.sqrt(2) 368 | childNode.h = self.heuristic(childNode, targetNode) 369 | childNode.f = childNode.g + childNode.h 370 | forwardDiscovered.addToHeap(childNode) 371 | 372 | # if the child node isn't at the diagonal of the current node 373 | else: 374 | 375 | numberOfNodes += 1 376 | 377 | childNode.g = childNodeParent.g + 1 378 | childNode.h = self.heuristic(childNode, targetNode) 379 | childNode.f = childNode.g + childNode.h 380 | forwardDiscovered.addToHeap(childNode) 381 | 382 | # if the child node is not in the finalized list but it's in the forwardDiscovered list, then check whether we are getting a better value for that node 383 | else: 384 | 385 | # if the neighbouring node happens to be at the diagonal of the current node, then set the g value accordingly 386 | if node == (1, 1) or node == (1, -1) or node == (-1, 1) or node == (-1, -1): 387 | 388 | childNodeCurrentGVal = childNodeParent.g + math.sqrt(2) 389 | childNodeCurrentHVal = self.heuristic(childNode, targetNode) 390 | childNodeCurrentFVal = childNodeCurrentHVal + childNodeCurrentGVal 391 | 392 | # if the neighbouring node is not at the diagonal of the current node, then set the g value accordingly 393 | else: 394 | 395 | childNodeCurrentGVal = childNodeParent.g + 1 396 | childNodeCurrentHVal = self.heuristic(childNode, targetNode) 397 | childNodeCurrentFVal = childNodeCurrentHVal + childNodeCurrentGVal 398 | 399 | for nodeIndex in forwardDiscovered.getHeap(): 400 | 401 | if nodeIndex.position == childNode.position and nodeIndex.f <= childNodeCurrentFVal: 402 | 403 | break 404 | 405 | # if we are getting a better g value for the current child node, then update that value accordingly for that node in the forwardDiscovered list 406 | else: 407 | 408 | # if the neighbouring node happens to be at diagonal, then check for the corners accordingly and update the cost of that node in the forwardDiscovered list 409 | if node == (1, 1) or node == (1, -1) or node == (-1, 1) or node == (-1, -1): 410 | 411 | if (node == (1, 1) or node == (-1, 1)) and (self.map[smallestValueNode[0].position[0]][smallestValueNode[0].position[1] + 1] == "."): 412 | 413 | if node == (1, 1) and self.map[smallestValueNode[0].position[0] + 1][smallestValueNode[0].position[1]] == ".": 414 | 415 | forwardDiscovered.updateNodeCostInHeap(childNode, childNodeCurrentFVal, childNodeCurrentGVal, smallestValueNode[0]) 416 | 417 | elif node == (-1, 1) and self.map[smallestValueNode[0].position[0] - 1][smallestValueNode[0].position[1]] == ".": 418 | 419 | forwardDiscovered.updateNodeCostInHeap(childNode, childNodeCurrentFVal, childNodeCurrentGVal, smallestValueNode[0]) 420 | 421 | elif (node == (1, -1) or node == (-1, -1)) and (self.map[smallestValueNode[0].position[0]][smallestValueNode[0].position[1] - 1] == "."): 422 | 423 | if node == (1, -1) and self.map[smallestValueNode[0].position[0] + 1][smallestValueNode[0].position[1]] == ".": 424 | 425 | forwardDiscovered.updateNodeCostInHeap(childNode, childNodeCurrentFVal, childNodeCurrentGVal, smallestValueNode[0]) 426 | 427 | elif node == (-1, -1) and self.map[smallestValueNode[0].position[0] - 1][smallestValueNode[0].position[1]] == ".": 428 | 429 | forwardDiscovered.updateNodeCostInHeap(childNode, childNodeCurrentFVal, childNodeCurrentGVal, smallestValueNode[0]) 430 | 431 | else: 432 | 433 | forwardDiscovered.updateNodeCostInHeap(childNode, childNodeCurrentFVal, childNodeCurrentGVal, smallestValueNode[0]) 434 | 435 | # if the smallest value node happens to be from the backward frontier 436 | elif smallestValueNode[1] == "backward": 437 | 438 | if self.minItemFromList(smallestValueNode[0], forwardFinalized): 439 | 440 | shortestPath = [] 441 | 442 | currentNode = smallestValueNode[0] 443 | totalCost = currentNode.f 444 | 445 | # tracing back to the source node in order to retrieve the path 446 | while currentNode is not None: 447 | 448 | shortestPath = [currentNode.position] + shortestPath 449 | currentNode = currentNode.parent 450 | 451 | return shortestPath, totalCost, numberOfNodes 452 | 453 | if True: 454 | 455 | neighborNode = [(0, 1), (0, -1), (1, 0), (-1, 0), (1, 1), (1, -1), (-1, 1), (-1, -1)] 456 | 457 | for node in neighborNode: 458 | 459 | # getting the position of the current node 460 | nodePositionX, nodePositionY = smallestValueNode[0].position[0] + node[0], smallestValueNode[0].position[1] + node[1] 461 | 462 | # if the neighbouring node is out of range, then skip to next node 463 | if nodePositionX > self.height or nodePositionY > self.width or nodePositionY < 0 or nodePositionX < 0: 464 | continue 465 | 466 | # if the neighbouring node is not walkable, then skip to next neighbouring node 467 | if self.map[nodePositionX][nodePositionY] != ".": 468 | continue 469 | 470 | childNodePosition = (nodePositionX, nodePositionY) 471 | childNodeParent = smallestValueNode[0] 472 | childNode = Node(childNodeParent, childNodePosition) 473 | 474 | firstFlagChecker = False 475 | 476 | # if child node is in the finalised list, then skip to the next neighbouring node 477 | for backwardFinalizedNode in backwardFinalised: 478 | 479 | if backwardFinalizedNode.position == childNodePosition: 480 | 481 | firstFlagChecker = True 482 | 483 | break 484 | 485 | if firstFlagChecker: 486 | 487 | continue 488 | 489 | # if the child node is not in the finalized list, then search the backward frontier's discovered list 490 | secondFlagChecker = True 491 | 492 | backwardDiscoveredList = backwardDiscovered.getHeap() 493 | 494 | # checking if the node is in the forwardDiscovered list, if the node is in the forwardDiscovered list then skip to the next node 495 | for backwardDiscoveredNode in backwardDiscoveredList: 496 | 497 | if backwardDiscoveredNode.position == childNodePosition: 498 | 499 | secondFlagChecker = False 500 | 501 | break 502 | 503 | if secondFlagChecker: 504 | 505 | # if the child node happens to be in the diagonal 506 | if node == (1, 1) or node == (1, -1) or node == (-1, 1) or node == (-1, -1): 507 | 508 | # if we are moving in the upper right or bottom right corner, then check that there are not obstacles in the corners in order to avoid corner cutting 509 | if (node == (1, 1) or node == (-1, 1)) and self.map[smallestValueNode[0].position[0]][smallestValueNode[0].position[1] + 1] == ".": 510 | 511 | if node == (1, 1) and self.map[smallestValueNode[0].position[0] + 1][smallestValueNode[0].position[1]] == ".": 512 | 513 | numberOfNodes += 1 514 | 515 | childNode.g = childNodeParent.g + math.sqrt(2) 516 | childNode.h = self.heuristic(childNode, sourceNode) 517 | childNode.f = childNode.g + childNode.h 518 | backwardDiscovered.addToHeap(childNode) 519 | 520 | elif node == (-1, 1) and self.map[smallestValueNode[0].position[0] - 1][smallestValueNode[0].position[1]] == ".": 521 | 522 | numberOfNodes += 1 523 | 524 | childNode.g = childNodeParent.g + math.sqrt(2) 525 | childNode.h = self.heuristic(childNode, sourceNode) 526 | childNode.f = childNode.g + childNode.h 527 | backwardDiscovered.addToHeap(childNode) 528 | 529 | # if we are moving in the upper left or bottom left corner, then check that there are not obstacles in the corners in order to avoid corner cutting 530 | elif (node == (1, -1) or node == (-1, -1)) and (self.map[smallestValueNode[0].position[0]][smallestValueNode[0].position[1] - 1] == "."): 531 | 532 | if node == (1, -1) and self.map[smallestValueNode[0].position[0] + 1][smallestValueNode[0].position[1]] == ".": 533 | 534 | 535 | numberOfNodes += 1 536 | 537 | childNode.g = childNodeParent.g + math.sqrt(2) 538 | childNode.h = self.heuristic(childNode, sourceNode) 539 | childNode.f = childNode.g + childNode.h 540 | backwardDiscovered.addToHeap(childNode) 541 | 542 | elif node == (-1, -1) and self.map[smallestValueNode[0].position[0] - 1][smallestValueNode[0].position[1]] == ".": 543 | 544 | numberOfNodes += 1 545 | 546 | childNode.g = childNodeParent.g + math.sqrt(2) 547 | childNode.h = self.heuristic(childNode, sourceNode) 548 | childNode.f = childNode.g + childNode.h 549 | backwardDiscovered.addToHeap(childNode) 550 | 551 | # if the child node isn't at the diagonal of the current node 552 | else: 553 | 554 | numberOfNodes += 1 555 | 556 | childNode.g = childNodeParent.g + 1 557 | childNode.h = self.heuristic(childNode, sourceNode) 558 | childNode.f = childNode.g + childNode.h 559 | backwardDiscovered.addToHeap(childNode) 560 | 561 | # if the child node is not in the finalized list but it's in the forwardDiscovered list, then check whether we are getting a better value for that node 562 | else: 563 | 564 | # if the neighbouring node happens to be at the diagonal of the current node, then set the g value accordingly 565 | if node == (1, 1) or node == (1, -1) or node == (-1, 1) or node == (-1, -1): 566 | 567 | childNodeCurrentGVal = childNodeParent.g + math.sqrt(2) 568 | childNodeCurrentHVal = self.heuristic(childNode, targetNode) 569 | childNodeCurrentFVal = childNodeCurrentHVal + childNodeCurrentGVal 570 | 571 | # if the neighbouring node is not at the diagonal of the current node, then set the g value accordingly 572 | else: 573 | 574 | childNodeCurrentGVal = childNodeParent.g + 1 575 | childNodeCurrentHVal = self.heuristic(childNode, targetNode) 576 | childNodeCurrentFVal = childNodeCurrentHVal + childNodeCurrentGVal 577 | 578 | for nodeIndex in forwardDiscovered.getHeap(): 579 | 580 | if nodeIndex.position == childNode.position and nodeIndex.f <= childNodeCurrentFVal: 581 | 582 | break 583 | 584 | # if we are getting a better g value for the current child node, then update that value accordingly for that node in the forwardDiscovered list 585 | else: 586 | 587 | # if the neighbouring node happens to be at diagonal, then check for the corners accordingly and update the cost of that node in the forwardDiscovered list 588 | if node == (1, 1) or node == (1, -1) or node == (-1, 1) or node == (-1, -1): 589 | 590 | if (node == (1, 1) or node == (-1, 1)) and (self.map[smallestValueNode[0].position[0]][smallestValueNode[0].position[1] + 1] == "."): 591 | 592 | if node == (1, 1) and self.map[smallestValueNode[0].position[0] + 1][smallestValueNode[0].position[1]] == ".": 593 | 594 | backwardDiscovered.updateNodeCostInHeap(childNode, childNodeCurrentFVal, childNodeCurrentGVal, smallestValueNode[0]) 595 | 596 | elif node == (-1, 1) and self.map[smallestValueNode[0].position[0] - 1][smallestValueNode[0].position[1]] == ".": 597 | 598 | backwardDiscovered.updateNodeCostInHeap(childNode, childNodeCurrentFVal, childNodeCurrentGVal, smallestValueNode[0]) 599 | 600 | elif (node == (1, -1) or node == (-1, -1)) and (self.map[smallestValueNode[0].position[0]][smallestValueNode[0].position[1] - 1] == "."): 601 | 602 | if node == (1, -1) and self.map[smallestValueNode[0].position[0] + 1][smallestValueNode[0].position[1]] == ".": 603 | 604 | backwardDiscovered.updateNodeCostInHeap(childNode, childNodeCurrentFVal, childNodeCurrentGVal, smallestValueNode[0]) 605 | 606 | elif node == (-1, -1) and self.map[smallestValueNode[0].position[0] - 1][smallestValueNode[0].position[1]] == ".": 607 | 608 | backwardDiscovered.updateNodeCostInHeap(childNode, childNodeCurrentFVal, childNodeCurrentGVal, smallestValueNode[0]) 609 | 610 | else: 611 | 612 | backwardDiscovered.updateNodeCostInHeap(childNode, childNodeCurrentFVal, childNodeCurrentGVal, smallestValueNode[0]) 613 | 614 | print("No possible path exist from the given source to given target.") 615 | return ([], 0) 616 | 617 | 618 | x = Graph() 619 | x.buildGraph("arena.map") 620 | # print(x.biDirectionalSearch((19, 26), (19, 29))) 621 | 622 | file = open("arena.map.scen") 623 | 624 | correct = 0 625 | wrong = 0 626 | unWalkable = 0 627 | testCounter = 0 628 | 629 | for item in file: 630 | 631 | item = item.split("\t") 632 | 633 | if len(item) > 2: 634 | 635 | testCounter += 1 636 | print("Test Case No. :", testCounter) 637 | 638 | item[4], item[5], item[6], item[7] = int(item[4]), int(item[5]), int(item[6]), int(item[7]) 639 | 640 | result = x.biDirectionalSearch((item[5], item[4]), (item[7], item[6])) 641 | 642 | if int(result[1]) == int(float(item[8][:-1])): 643 | 644 | successString = colored("Test Passed: ", "green") 645 | print(successString) 646 | print("The coordinates are SOURCE(", item[5], ",", item[4], ") , TARGET(", item[7], ",", item[6], ")", " || The result I'm getting: [", result[1], "] The result I should be getting: [", float(item[8][:-1]), "]") 647 | print("The Path is: ", result[0]) 648 | print("The number of nodes that have been visited are:", result[2]) 649 | print("") 650 | correct += 1 651 | 652 | elif int(result[1]) == 0: 653 | 654 | unWalkablePath = colored("Test Failed: ", "blue") 655 | print(unWalkablePath) 656 | print("The coordinates are SOURCE(", item[5], ",", item[4], ") , TARGET(", item[7], ",", item[6], ")", " || The result I'm getting: [", result[1], "] The result I should be getting: [", float(item[8][:-1]), "]") 657 | unWalkable += 1 658 | print("") 659 | 660 | else: 661 | 662 | failureString = colored("Test Failed: ", "red") 663 | print(failureString) 664 | print("The coordinates are SOURCE(", item[5], ",", item[4], ") , TARGET(", item[7], ",", item[6], ")", " || The result I'm getting: [", result[1], "] The result I should be getting: [", float(item[8][:-1]), "]") 665 | print("The Path is: ", result[0]) 666 | wrong += 1 667 | if len(result) == 3: 668 | 669 | print("The number of nodes that have been visited are:", result[2]) 670 | 671 | print("") 672 | 673 | print("Number of correct cases: ", correct) 674 | print("Number of wrong cases: ", wrong) 675 | print("Number of not walkable source issues: ", unWalkable) 676 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unidirectional-&-Bidirectional-A-Star-Search-Algorithm-Python-Implementation 2 | 3 | This is the Unidirectional and Bidirectional A Star Search Algorithm Python Implementation. For more info about this algorithm: https://en.wikipedia.org/wiki/A*_search_algorithm 4 | 5 | Important Details Regarding the Implementation (Unidirectional A star search Algorithm): 6 | 7 | 1. For the heuristic, Euclidean distance/octile have been used to get an estimate from the given node to the goal node. 8 | 9 | 2. In order to address the ties (nodes having the same F value in our (discovered) heap, then we prefer using that node whihc are closer to the goal node i.e. node having higher H value.) 10 | 11 | Important Details Regarding the Implementation (Bidirectional A star search Algorithm): 12 | 13 | 1. For the heuristic, Euclidean distance/octile have been used to get an estimate from the given node to the goal node. 14 | 15 | 2. The node selction policy that is being used here is : we select that frontier which offers us the node with minimum f-value. 16 | 17 | 3. The stopping condition that is being used here is: we stop as soon as we come across such a node from either of the frontiers, that happens to be in the closed list of the opposite frontier. 18 | 19 | 20 | NOTE: 21 | 22 | 1. If the source/target node happens to be such a node which is not walkable, then the algorithm will return an empty path with a total cost of 0. 23 | 24 | 2. This particular implementation will only work for 2D maps. 25 | 26 | 3. In order to get more test cases, please refer to https://www.movingai.com/benchmarks/grids.html 27 | 28 | -------------------------------------------------------------------------------- /UniDiAstarSearch.py: -------------------------------------------------------------------------------- 1 | import math 2 | from termcolor import colored 3 | 4 | 5 | class Heap: 6 | 7 | def __init__(self): 8 | 9 | self.discovered = [] 10 | 11 | # method for adding the givenItem to the heap 12 | def addToHeap(self, givenItem): 13 | 14 | self.discovered.append(givenItem) 15 | self.heapify() 16 | 17 | # method for getting the deep copy of the heap list 18 | def getHeap(self): 19 | 20 | return self.discovered 21 | 22 | # function for updating the cost of a node in the heap 23 | def updateNodeCostInHeap(self, currentNode, FValue, GValue, sourceNode): 24 | 25 | for node in self.discovered: 26 | 27 | if node.position == currentNode.position: 28 | 29 | node.f = FValue 30 | node.g = GValue 31 | node.parent = sourceNode 32 | self.heapify() 33 | 34 | # method to change the position of the elements in the heap in order to satisfy the heap property 35 | def heapify(self): 36 | 37 | for item in range(len(self.discovered)): 38 | 39 | # if the index is greater than or equal to 1 and the parent is greater than children, then swap 40 | while item >= 1 and self.discovered[item].f <= self.discovered[item//2].f: 41 | 42 | if self.discovered[item].f < self.discovered[item//2].f: 43 | 44 | self.swap(self.discovered, item, item // 2) 45 | 46 | elif self.discovered[item].f == self.discovered[item//2].f: 47 | 48 | if self.discovered[item].h < self.discovered[item//2].h: 49 | 50 | self.swap(self.discovered, item, item // 2) 51 | 52 | item = item // 2 53 | 54 | # method to get the minimum item from the heap 55 | def minItemFromHeap(self): 56 | 57 | result = self.discovered.pop(0) 58 | self.heapify() 59 | return result 60 | 61 | # method for swapping the values in the heap 62 | def swap(self, heap, firstIndex, secondIndex): 63 | 64 | tempVal = heap[firstIndex] 65 | heap[firstIndex] = heap[secondIndex] 66 | heap[secondIndex] = tempVal 67 | 68 | 69 | class Node: 70 | 71 | def __init__(self, parent, position): 72 | 73 | self.parent = parent 74 | self.position = position 75 | 76 | self.g = 0 77 | self.h = 0 78 | self.f = 0 79 | 80 | 81 | class Graph: 82 | 83 | def __init__(self): 84 | 85 | self.map = [] 86 | self.height = 0 87 | self.width = 0 88 | 89 | # method for building the map in the required format 90 | def buildGraph(self, filename): 91 | 92 | file = open(filename, "r") 93 | 94 | lineNumber = 0 95 | 96 | for line in file: 97 | 98 | lineNumber += 1 99 | 100 | # to record the height of the map 101 | if lineNumber == 2: 102 | 103 | line = line.replace("\n", "") 104 | line = line.split(" ") 105 | self.height = int(line[-1]) - 1 106 | 107 | # to record the width of the map 108 | elif lineNumber == 3: 109 | 110 | line = line.replace("\n", "") 111 | line = line.split(" ") 112 | self.width = int(line[-1]) - 1 113 | 114 | # condition to check whether we are only storing those lines from the map which are having their first character as a map terrain 115 | elif line[0] == "@" or line[0] == "T" or line[0] == ".": 116 | 117 | # checking if the line is having the new line character 118 | if line[-1] == "\n": 119 | 120 | # if the line is having new line character, then append the entire line except the last character 121 | self.map.append(line[:-1]) 122 | 123 | else: 124 | 125 | # if the line is not having new line character, then append the entire line 126 | self.map.append(line) 127 | 128 | # method for calculating the heuristic (Euclidean Distance) 129 | def heuristic(self, currentNode, targetNode): 130 | 131 | # -------------Octile Heuristic---------------------------- 132 | 133 | xVal = abs(currentNode.position[0] - targetNode.position[0]) 134 | yVal = abs(currentNode.position[1] - targetNode.position[1]) 135 | 136 | return max(xVal, yVal) + ((math.sqrt(2)-1)*min(xVal, yVal)) 137 | 138 | # -------------Octile Heuristic---------------------------- 139 | 140 | # -------------Euclidean Heuristic------------------------- 141 | 142 | # return math.sqrt((currentNode.position[0] - targetNode.position[0])**2 + (currentNode.position[1] - targetNode.position[1])**2) 143 | 144 | # -------------Euclidean Heuristic------------------------- 145 | 146 | # method for implementing the a star search algorithm 147 | def aStarSearch(self, source, target): 148 | 149 | # if the source and the target, are the same, then return an empty path with a cost of 0 150 | if source == target: 151 | 152 | return ([], 0) 153 | 154 | sourceX, sourceY = source[0], source[1] 155 | targetX, targetY = target[0], target[1] 156 | 157 | # if the coordinates of the source or the target node are negative, then return an empty path with a cost of 0 158 | if sourceX < 0 or sourceY < 0 or targetX < 0 or targetY < 0: 159 | 160 | print("The source or target is negative.") 161 | return ([], 0) 162 | 163 | # if the coordinates of the source or target are out of range i.e. more than or less than the height or the width of the map, then return an empty path with a cost of 0 164 | if (sourceX > self.height) or (targetX > self.height) or (sourceY > self.width) or (targetY > self.width): 165 | 166 | print("The source or target is out range of the map.") 167 | return ([], 0) 168 | 169 | # if the source is not walkable, then return an empty path with a cost of 0 170 | if self.map[sourceX][sourceY] != '.': 171 | 172 | print("The source is not walkable. (", self.map[sourceX][sourceY], ")") 173 | return ([], 0) 174 | 175 | # if the target is not walkable, then return an empty path with a cost of 0 176 | if self.map[targetX][targetY] != ".": 177 | 178 | print("The target is not walkable. (", self.map[targetX][targetY], ")") 179 | return ([], 0) 180 | 181 | sourceNode = Node(None, source) 182 | targetNode = Node(None, target) 183 | 184 | # initialising the discovered list as a heap and the finalized list as a normal list 185 | discovered = Heap() 186 | finalized = [] 187 | 188 | discovered.addToHeap(sourceNode) 189 | 190 | numberOfNodes = 0 191 | 192 | # while the discovered heap is not empty 193 | while len(discovered.getHeap()) != 0: 194 | 195 | smallest_value_node = discovered.minItemFromHeap() 196 | finalized.append(smallest_value_node) 197 | 198 | # print(smallest_value_node.position) 199 | 200 | # we have reached the target node, then return the entire path with it's total cost 201 | if smallest_value_node.position == target: 202 | 203 | shortestPath = [] 204 | 205 | currentNode = smallest_value_node 206 | totalCost = currentNode.f 207 | 208 | # tracing back to the source node in order to retrieve the path 209 | while currentNode is not None: 210 | 211 | shortestPath = [currentNode.position] + shortestPath 212 | currentNode = currentNode.parent 213 | 214 | return shortestPath, totalCost, numberOfNodes 215 | 216 | neighborNode = [(0, 1), (0, -1), (1, 0), (-1, 0), (1, 1), (1, -1), (-1, 1), (-1, -1)] 217 | 218 | for node in neighborNode: 219 | 220 | # getting the position of the current node 221 | nodePositionX, nodePositionY = smallest_value_node.position[0] + node[0], smallest_value_node.position[1] + node[1] 222 | 223 | # if the neighbouring node is out of range, then skip to next node 224 | if nodePositionX > self.height or nodePositionY > self.width or nodePositionY < 0 or nodePositionX < 0: 225 | 226 | continue 227 | 228 | # if the neighbouring node is not walkable, then skip to next neighbouring node 229 | if self.map[nodePositionX][nodePositionY] != ".": 230 | 231 | continue 232 | childNodePosition = (nodePositionX, nodePositionY) 233 | childNodeParent = smallest_value_node 234 | childNode = Node(childNodeParent, childNodePosition) 235 | 236 | firstFlagChecker = False 237 | 238 | # if child node is in the finalised list, then skip to the next node 239 | for finalizedNode in finalized: 240 | 241 | if finalizedNode.position == childNodePosition: 242 | 243 | firstFlagChecker = True 244 | 245 | break 246 | 247 | if firstFlagChecker: 248 | 249 | continue 250 | 251 | # if the child node is not in the finalized list and not in discovered list 252 | secondFlagChecker = True 253 | discoveredList = discovered.getHeap() 254 | 255 | # checking if the node is in the discovered list, if the node is in the discovered list then skip to the next node 256 | for discoveredNode in discoveredList: 257 | 258 | if discoveredNode.position == childNodePosition: 259 | 260 | secondFlagChecker = False 261 | 262 | break 263 | 264 | # if the node is not in the discovered list, then compute the cost of that node and add it to the discovered list 265 | if secondFlagChecker: 266 | 267 | # if the child node happens to be in the diagonal 268 | if node == (1, 1) or node == (1, -1) or node == (-1, 1) or node == (-1, -1): 269 | 270 | # if we are moving in the upper right or bottom right corner, then check that there are not obstacles in the corners in order to avoid corner cutting 271 | if (node == (1, 1) or node == (-1, 1)) and self.map[smallest_value_node.position[0]][smallest_value_node.position[1] + 1] == ".": 272 | 273 | if node == (1, 1) and self.map[smallest_value_node.position[0] + 1][smallest_value_node.position[1]] == ".": 274 | 275 | numberOfNodes += 1 276 | 277 | childNode.g = childNodeParent.g + math.sqrt(2) 278 | childNode.h = self.heuristic(childNode, targetNode) 279 | childNode.f = childNode.g + childNode.h 280 | discovered.addToHeap(childNode) 281 | 282 | elif node == (-1, 1) and self.map[smallest_value_node.position[0] - 1][smallest_value_node.position[1]] == ".": 283 | 284 | numberOfNodes += 1 285 | 286 | childNode.g = childNodeParent.g + math.sqrt(2) 287 | childNode.h = self.heuristic(childNode, targetNode) 288 | childNode.f = childNode.g + childNode.h 289 | discovered.addToHeap(childNode) 290 | 291 | # if we are moving in the upper left or bottom left corner, then check that there are not obstacles in the corners in order to avoid corner cutting 292 | elif (node == (1, -1) or node == (-1, -1)) and (self.map[smallest_value_node.position[0]][smallest_value_node.position[1] - 1] == "."): 293 | 294 | if node == (1, -1) and self.map[smallest_value_node.position[0] + 1][smallest_value_node.position[1]] == ".": 295 | 296 | numberOfNodes += 1 297 | 298 | childNode.g = childNodeParent.g + math.sqrt(2) 299 | childNode.h = self.heuristic(childNode, targetNode) 300 | childNode.f = childNode.g + childNode.h 301 | discovered.addToHeap(childNode) 302 | 303 | elif node == (-1, -1) and self.map[smallest_value_node.position[0] - 1][smallest_value_node.position[1]] == ".": 304 | 305 | numberOfNodes += 1 306 | 307 | childNode.g = childNodeParent.g + math.sqrt(2) 308 | childNode.h = self.heuristic(childNode, targetNode) 309 | childNode.f = childNode.g + childNode.h 310 | discovered.addToHeap(childNode) 311 | 312 | # if the child node isn't at the diagonal of the current node 313 | elif node == (1, 0) or node == (-1, 0) or node == (0, 1) or node == (0, -1): 314 | 315 | numberOfNodes += 1 316 | 317 | childNode.g = childNodeParent.g + 1 318 | childNode.h = self.heuristic(childNode, targetNode) 319 | childNode.f = childNode.g + childNode.h 320 | discovered.addToHeap(childNode) 321 | 322 | # if the child node is not in the finalized list but it's in the discovered list, then check whether we are getting a better value for that node 323 | else: 324 | 325 | # if the neighbouring node happens to be at the diagonal of the current node, then set the f value accordingly 326 | if node == (1, 1) or node == (1, -1) or node == (-1, 1) or node == (-1, -1): 327 | 328 | childNodeCurrentGVal = childNodeParent.g + math.sqrt(2) 329 | childNodeCurrentHVal = self.heuristic(childNode, targetNode) 330 | childNodeCurrentFVal = childNodeCurrentHVal + childNodeCurrentGVal 331 | 332 | # if the neighbouring node is not at the diagonal of the current node, then set the f value accordingly 333 | else: 334 | 335 | childNodeCurrentGVal = childNodeParent.g + 1 336 | childNodeCurrentHVal = self.heuristic(childNode, targetNode) 337 | childNodeCurrentFVal = childNodeCurrentHVal + childNodeCurrentGVal 338 | 339 | heapList = discovered.getHeap() 340 | 341 | for nodeIndex in heapList: 342 | 343 | if nodeIndex.position == childNode.position and nodeIndex.f <= childNodeCurrentFVal: 344 | 345 | break 346 | 347 | # if we are getting a better f value for the current child node, then update that value accordingly for that node in the discovered list 348 | else: 349 | 350 | # if the neighbouring node happens to be at diagonal, then check for the corners accordingly and update the cost of that node in the discovered list 351 | if node == (1, 1) or node == (1, -1) or node == (-1, 1) or node == (-1, -1): 352 | 353 | if (node == (1, 1) or node == (-1, 1)) and (self.map[smallest_value_node.position[0]][smallest_value_node.position[1] + 1] == "."): 354 | 355 | if node == (1, 1) and self.map[smallest_value_node.position[0] + 1][smallest_value_node.position[1]] == ".": 356 | 357 | discovered.updateNodeCostInHeap(childNode, childNodeCurrentFVal, childNodeCurrentGVal, smallest_value_node) 358 | 359 | elif node == (-1, 1) and self.map[smallest_value_node.position[0] - 1][smallest_value_node.position[1]] == ".": 360 | 361 | discovered.updateNodeCostInHeap(childNode, childNodeCurrentFVal, childNodeCurrentGVal, smallest_value_node) 362 | 363 | elif (node == (1, -1) or node == (-1, -1)) and (self.map[smallest_value_node.position[0]][smallest_value_node.position[1] - 1] == "."): 364 | 365 | if node == (1, -1) and self.map[smallest_value_node.position[0] + 1][smallest_value_node.position[1]] == ".": 366 | 367 | discovered.updateNodeCostInHeap(childNode, childNodeCurrentFVal, childNodeCurrentGVal, smallest_value_node) 368 | 369 | elif node == (-1, -1) and self.map[smallest_value_node.position[0] - 1][smallest_value_node.position[1]] == ".": 370 | 371 | discovered.updateNodeCostInHeap(childNode, childNodeCurrentFVal, childNodeCurrentGVal, smallest_value_node) 372 | 373 | elif node == (1, 0) or node == (-1, 0) or node == (0, 1) or node == (0, -1): 374 | 375 | discovered.updateNodeCostInHeap(childNode, childNodeCurrentFVal, childNodeCurrentGVal, smallest_value_node) 376 | 377 | print("No possible path exist from the given source to given target.") 378 | 379 | return ([], 0) 380 | 381 | 382 | x = Graph() 383 | x.buildGraph("arena.map") 384 | # print(x.aStarSearch((36, 31), (19, 47))) 385 | 386 | file = open("arena.map.scen") 387 | correct = 0 388 | wrong = 0 389 | unWalkable = 0 390 | testCounter = 0 391 | 392 | for item in file: 393 | 394 | item = item.split("\t") 395 | 396 | if len(item) > 2: 397 | 398 | testCounter += 1 399 | print("Test Case No. :", testCounter) 400 | 401 | item[4], item[5], item[6], item[7] = int(item[4]), int(item[5]), int(item[6]), int(item[7]) 402 | 403 | result = x.aStarSearch((item[5], item[4]), (item[7], item[6])) 404 | 405 | if int(result[1]) == int(float(item[8][:-1])): 406 | 407 | successString = colored("Test Passed: ", "green") 408 | print(successString) 409 | print("The coordinates are SOURCE(", item[5], ",", item[4], ") , TARGET(", item[7], ",", item[6], ")", " || The result I'm getting: [", result[1], "] The result I should be getting: [", float(item[8][:-1]), "]") 410 | print("The Path is: ", result[0]) 411 | print("The number of nodes that have been visited are:", result[2]) 412 | correct += 1 413 | print("") 414 | 415 | elif int(result[1]) == 0: 416 | 417 | unWalkablePath = colored("Test Failed: ", "blue") 418 | print(unWalkablePath) 419 | print("The coordinates are SOURCE(", item[5], ",", item[4], ") , TARGET(", item[7], ",", item[6], ")", " || The result I'm getting: [", result[1], "] The result I should be getting: [", float(item[8][:-1]), "]") 420 | unWalkable += 1 421 | print("") 422 | 423 | else: 424 | 425 | failureString = colored("Test Failed: ", "red") 426 | print(failureString) 427 | print("The coordinates are SOURCE(", item[5], ",", item[4], ") , TARGET(", item[7], ",", item[6], ")", " || The result I'm getting: [", result[1], "] The result I should be getting: [", float(item[8][:-1]), "]") 428 | print("The Path is: ", result[0]) 429 | wrong += 1 430 | 431 | if len(result) == 3: 432 | 433 | print("The number of nodes that have been visited are:", result[2]) 434 | 435 | print("") 436 | 437 | print("Number of correct cases: ", correct) 438 | print("Number of wrong cases: ", wrong) 439 | print("Number of not walkable source issues: ", unWalkable) 440 | -------------------------------------------------------------------------------- /arena.map: -------------------------------------------------------------------------------- 1 | type octile 2 | height 49 3 | width 49 4 | map 5 | TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT 6 | TTT............TTTT.TTT...TTTT.TTTT............TT 7 | TT.............TTT........TTT..TTT.............TT 8 | T...............................................T 9 | T...............................................T 10 | T...............................................T 11 | T...............................................T 12 | T.......................TT......................T 13 | T......................TTT......................T 14 | T......................TTT......................T 15 | T...............................................T 16 | T...............................................T 17 | T...............................................T 18 | T...............................................T 19 | T...............................................T 20 | TTT............TTTT............TTTT............TT 21 | TTT............TTTT............TTTT............TT 22 | TTT............TTTT............TTTT............TT 23 | TT.............TTT.............TTT.............TT 24 | TT..............................................T 25 | TT..............................................T 26 | TT..............................................T 27 | TT..............................................T 28 | T...............................................T 29 | T...............................................T 30 | T...............................................T 31 | TT..............................................T 32 | TTT.............................................T 33 | TTT.............................................T 34 | TTT.............................................T 35 | T...............................................T 36 | TTT............TTTT............TTTT............TT 37 | TTT............TTTT............TTTT............TT 38 | TTT............TTTT............TTTT............TT 39 | TT.............TTT.............TTT.............TT 40 | T...............................................T 41 | T...............................................T 42 | T...............................................T 43 | T...............................................T 44 | T...............................................T 45 | T...............................................T 46 | T...............................................T 47 | T...............................................T 48 | T...............................................T 49 | T...............................................T 50 | T...............................................T 51 | T...................TTT...TTT...................T 52 | TTT............TTTT.TTTT..TTTT.TTTT............TT 53 | TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT 54 | 55 | ///////////////////////////////////////////////////////////////////////////////////////////// 56 | article{sturtevant2012benchmarks, 57 | title={Benchmarks for Grid-Based Pathfinding}, 58 | author={Sturtevant, N.}, 59 | journal={Transactions on Computational Intelligence and AI in Games}, 60 | volume={4}, 61 | number={2}, 62 | pages={144 -- 148}, 63 | year={2012}, 64 | url = {http://web.cs.du.edu/~sturtevant/papers/benchmarks.pdf}, 65 | } 66 | ///////////////////////////////////////////////////////////////////////////////////////////// 67 | -------------------------------------------------------------------------------- /arena.map.scen: -------------------------------------------------------------------------------- 1 | version 1 2 | 0 arena.map 49 49 19 26 19 29 3.00000000 3 | 0 arena.map 49 49 44 30 43 28 2.41421356 4 | 0 arena.map 49 49 31 23 33 23 2.00000000 5 | 0 arena.map 49 49 30 22 31 21 1.41421356 6 | 0 arena.map 49 49 40 14 43 16 3.82842712 7 | 0 arena.map 49 49 11 1 13 1 2.00000000 8 | 0 arena.map 49 49 9 40 7 41 2.41421356 9 | 0 arena.map 49 49 12 6 15 4 3.82842712 10 | 0 arena.map 49 49 47 30 44 28 3.82842712 11 | 0 arena.map 49 49 45 46 44 43 3.41421356 12 | 1 arena.map 49 49 20 18 25 16 5.82842712 13 | 1 arena.map 49 49 25 28 21 30 4.82842712 14 | 1 arena.map 49 49 11 9 15 3 7.65685425 15 | 1 arena.map 49 49 18 36 22 40 5.65685425 16 | 1 arena.map 49 49 28 42 24 41 4.41421356 17 | 1 arena.map 49 49 41 14 45 16 4.82842712 18 | 1 arena.map 49 49 25 39 27 43 4.82842712 19 | 1 arena.map 49 49 31 12 26 9 6.24264069 20 | 1 arena.map 49 49 8 24 12 25 4.41421356 21 | 1 arena.map 49 49 41 17 44 14 4.24264069 22 | 2 arena.map 49 49 3 2 10 9 9.89949493 23 | 2 arena.map 49 49 22 22 11 24 11.82842712 24 | 2 arena.map 49 49 32 19 31 11 10.41421356 25 | 2 arena.map 49 49 15 45 7 42 9.24264069 26 | 2 arena.map 49 49 31 28 41 29 10.41421356 27 | 2 arena.map 49 49 31 13 40 8 11.07106781 28 | 2 arena.map 49 49 18 9 13 3 8.07106781 29 | 2 arena.map 49 49 2 14 5 6 9.24264069 30 | 2 arena.map 49 49 13 12 18 6 8.07106781 31 | 2 arena.map 49 49 31 40 23 35 10.07106781 32 | 3 arena.map 49 49 23 18 20 31 14.24264069 33 | 3 arena.map 49 49 24 28 15 20 12.31370850 34 | 3 arena.map 49 49 45 17 34 14 12.24264069 35 | 3 arena.map 49 49 41 10 28 12 13.82842712 36 | 3 arena.map 49 49 42 18 33 30 15.72792206 37 | 3 arena.map 49 49 25 26 22 12 15.24264069 38 | 3 arena.map 49 49 4 34 6 22 12.82842712 39 | 3 arena.map 49 49 14 22 26 13 15.72792206 40 | 3 arena.map 49 49 10 14 20 8 12.48528137 41 | 3 arena.map 49 49 11 35 12 21 14.41421356 42 | 4 arena.map 49 49 30 7 35 24 19.07106781 43 | 4 arena.map 49 49 31 19 39 33 17.31370850 44 | 4 arena.map 49 49 11 27 19 40 16.31370850 45 | 4 arena.map 49 49 28 39 46 40 18.41421356 46 | 4 arena.map 49 49 40 17 26 6 18.55634918 47 | 4 arena.map 49 49 36 44 24 29 19.97056274 48 | 4 arena.map 49 49 1 39 19 39 18.00000000 49 | 4 arena.map 49 49 32 29 36 12 18.65685425 50 | 4 arena.map 49 49 40 32 26 20 18.97056274 51 | 4 arena.map 49 49 14 30 7 45 17.89949493 52 | 5 arena.map 49 49 24 35 43 27 22.31370850 53 | 5 arena.map 49 49 26 41 32 20 23.48528137 54 | 5 arena.map 49 49 28 24 26 45 21.82842712 55 | 5 arena.map 49 49 27 33 43 23 20.14213562 56 | 5 arena.map 49 49 25 41 14 25 20.55634918 57 | 5 arena.map 49 49 45 31 38 12 21.89949493 58 | 5 arena.map 49 49 22 4 37 20 23.97056274 59 | 5 arena.map 49 49 25 22 26 42 20.41421356 60 | 5 arena.map 49 49 46 20 41 38 20.07106781 61 | 5 arena.map 49 49 41 31 20 34 23.07106781 62 | 6 arena.map 49 49 25 25 8 8 26.97056274 63 | 6 arena.map 49 49 15 19 6 40 24.72792206 64 | 6 arena.map 49 49 35 31 33 4 27.82842712 65 | 6 arena.map 49 49 3 37 21 22 24.21320343 66 | 6 arena.map 49 49 10 37 6 12 26.65685425 67 | 6 arena.map 49 49 46 13 24 26 27.38477631 68 | 6 arena.map 49 49 36 31 19 47 25.97056274 69 | 6 arena.map 49 49 11 43 8 17 27.24264069 70 | 6 arena.map 49 49 24 25 13 4 25.55634918 71 | 6 arena.map 49 49 39 2 39 26 24.00000000 72 | 7 arena.map 49 49 18 25 1 4 28.04163055 73 | 7 arena.map 49 49 4 7 27 22 29.21320343 74 | 7 arena.map 49 49 28 14 5 32 30.45584412 75 | 7 arena.map 49 49 38 41 47 14 31.31370850 76 | 7 arena.map 49 49 20 37 33 13 30.55634918 77 | 7 arena.map 49 49 6 45 28 27 29.45584412 78 | 7 arena.map 49 49 33 4 7 18 31.79898987 79 | 7 arena.map 49 49 22 14 43 35 29.69848480 80 | 7 arena.map 49 49 5 31 11 2 31.48528137 81 | 7 arena.map 49 49 37 20 10 12 30.31370850 82 | 8 arena.map 49 49 4 18 33 5 34.38477631 83 | 8 arena.map 49 49 24 37 38 7 35.79898987 84 | 8 arena.map 49 49 35 12 39 45 34.65685425 85 | 8 arena.map 49 49 36 16 30 47 33.48528137 86 | 8 arena.map 49 49 3 1 35 7 34.48528137 87 | 8 arena.map 49 49 43 12 12 17 33.65685425 88 | 8 arena.map 49 49 26 36 47 10 34.69848480 89 | 8 arena.map 49 49 4 17 36 16 34.65685425 90 | 8 arena.map 49 49 34 7 24 38 35.14213562 91 | 8 arena.map 49 49 8 33 33 9 34.94112549 92 | 9 arena.map 49 49 22 10 39 41 38.04163055 93 | 9 arena.map 49 49 15 39 10 4 37.07106781 94 | 9 arena.map 49 49 26 38 38 3 39.97056274 95 | 9 arena.map 49 49 19 11 5 42 36.79898987 96 | 9 arena.map 49 49 17 12 5 45 38.55634918 97 | 9 arena.map 49 49 35 22 6 47 39.35533905 98 | 9 arena.map 49 49 42 39 27 7 38.21320343 99 | 9 arena.map 49 49 5 4 22 34 37.04163055 100 | 9 arena.map 49 49 33 40 21 6 38.97056274 101 | 9 arena.map 49 49 40 12 19 43 39.69848480 102 | 10 arena.map 49 49 44 45 27 9 43.04163055 103 | 10 arena.map 49 49 44 39 19 9 40.35533905 104 | 10 arena.map 49 49 23 41 10 6 40.38477631 105 | 10 arena.map 49 49 14 1 38 34 42.94112549 106 | 10 arena.map 49 49 10 40 32 6 43.11269836 107 | 10 arena.map 49 49 21 13 44 45 41.52691193 108 | 10 arena.map 49 49 28 8 15 44 41.38477631 109 | 10 arena.map 49 49 8 25 45 15 41.14213562 110 | 10 arena.map 49 49 4 3 38 27 43.94112549 111 | 10 arena.map 49 49 6 3 18 38 40.55634918 112 | 11 arena.map 49 49 45 6 8 26 45.28427124 113 | 11 arena.map 49 49 8 4 26 43 46.45584412 114 | 11 arena.map 49 49 37 31 1 3 47.59797974 115 | 11 arena.map 49 49 30 47 40 7 44.14213562 116 | 11 arena.map 49 49 39 6 12 40 45.76955261 117 | 11 arena.map 49 49 21 2 40 41 46.87005768 118 | 11 arena.map 49 49 42 36 10 3 46.25483398 119 | 11 arena.map 49 49 11 6 34 42 45.52691193 120 | 11 arena.map 49 49 14 36 43 1 47.01219330 121 | 11 arena.map 49 49 46 22 7 6 45.62741699 122 | 12 arena.map 49 49 42 40 3 9 51.84062042 123 | 12 arena.map 49 49 2 6 36 40 48.66904755 124 | 12 arena.map 49 49 2 42 24 3 48.11269836 125 | 12 arena.map 49 49 21 45 41 2 51.28427124 126 | 12 arena.map 49 49 3 45 39 11 51.84062042 127 | 12 arena.map 49 49 39 7 3 41 50.08326111 128 | 12 arena.map 49 49 15 42 47 6 49.25483398 129 | 12 arena.map 49 49 5 39 39 3 50.08326111 130 | 12 arena.map 49 49 3 33 46 14 50.87005768 131 | 12 arena.map 49 49 4 32 47 19 48.38477631 132 | 133 | ///////////////////////////////////////////////////////////////////////////////////////////// 134 | article{sturtevant2012benchmarks, 135 | title={Benchmarks for Grid-Based Pathfinding}, 136 | author={Sturtevant, N.}, 137 | journal={Transactions on Computational Intelligence and AI in Games}, 138 | volume={4}, 139 | number={2}, 140 | pages={144 -- 148}, 141 | year={2012}, 142 | url = {http://web.cs.du.edu/~sturtevant/papers/benchmarks.pdf}, 143 | } 144 | ///////////////////////////////////////////////////////////////////////////////////////////// 145 | --------------------------------------------------------------------------------