2
0

astar_demo.bmx 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805
  1. Strict
  2. '
  3. ' Simple demo program of an astar path finding algorithm
  4. ' The GUI is pretty crappy
  5. ' There are quite a few comments in the areas of importance. Sometimes this makes
  6. ' the code a little harder to read, but this was done for instructional purposes foremost
  7. Import "astar_graph_walker.bmx"
  8. Import "Callback.bmx"
  9. Global nums:TImage
  10. Local demo:AStarDemo = New AStarDemo
  11. demo.run()
  12. End
  13. Private
  14. ' Small class that encapsulates a position on the map
  15. Type MapPos
  16. Method isAtPos(otherX, otherY)
  17. Return otherX = x And otherY = y
  18. End Method
  19. Field x,y
  20. End Type
  21. ' Class that defines the terrain types that the demo uses
  22. Type Terrain
  23. Method set(weight:Int, filename:String)
  24. _weight = weight
  25. _image = LoadImage(filename)
  26. End Method
  27. Field _colour_r,_color_g,_color_b
  28. Field _weight:Int
  29. Field _image:TImage
  30. End Type
  31. ' Main application class
  32. Type AStarDemo
  33. ' Map stuff
  34. Const blockAreaWidth:Int = 600
  35. Const blockAreaHeight:Int = 600
  36. Const mapHeight:Int = 20
  37. Const mapWidth:Int = 20
  38. Const blockWidth:Int = 600 / 20
  39. Const blockHeight:Int = 600 / 20
  40. ' Block map
  41. Field map:Int[mapWidth, mapHeight]
  42. ' Node version of map
  43. Field nodeMap:AStarNode[mapWidth, mapHeight]
  44. Field startPos:MapPos
  45. Field endPos:MapPos
  46. Field currentMap = 0
  47. ' Terrain information
  48. Const numTerrainTypes = 4
  49. Field terrainFilenames:String[] = [ "road.png", "mountain.png", "water.png", "tree.png" ]
  50. Field terrainWeights:Int[] = [ 1, -1, 4, 2 ]
  51. Field terrains:Terrain[numTerrainTypes]
  52. Field currentTerrainIndex:Int = 1
  53. Field terrainLegendBlockX:Int
  54. Field terrainLegendBlockY:Int
  55. ' Path finding stuff
  56. ' Distance
  57. Const numDistanceFunctions = 4
  58. ' Is there a DATA like statement in Blitz?
  59. Field distanceNames:String[] = [ "(D) Euclidean Sqr(dx*dx+dy*dy)", ..
  60. "(D) Pseudo Euclidean dx*dx+dy*dy", ..
  61. "(D) Manhatten dx+dy", ..
  62. "(D) Diagonal Shortcut dx>dy ? 1.4*dy + (dx-dy) : 1.4*dx + (dy-dx)" ]
  63. Field distanceFunction = AStarGraphWalker.distanceEuclidean
  64. ' Whether we're allowed to chage the costs with the "Q" and "W" keys
  65. Field costChangeAllowed:Int = 0
  66. ' Is the pathfinder allowed to cruise diagonals?
  67. Field allowDiagonalPaths:Int = 0
  68. Field diagonalsAreLonger:Int = 1
  69. ' Time in millisecs that the last path fund took
  70. Field lastRunTime:Int = 0
  71. Field path:TList = Null ' The last path that was found
  72. Field heuristicMultiplier:Float = 1.0
  73. Field showProgress:Int = False ' Do we want to show the progress
  74. ' UI stuff
  75. Field mapButtonBlockX:Int = 0
  76. Field mapButtonBlockY:Int = mapHeight + 1
  77. Field showCosts:Int = 1
  78. Field flagRedraw:Int = 1
  79. Field textScale:Float = 800.0/1024.0
  80. Field message:String = "" ' Any stat messages that are to be displayed
  81. ' Input stuff
  82. ' Used to detect mouse movements
  83. Field oldBlockX:Int = -1
  84. Field oldBlockY:Int = -1
  85. ' Help
  86. Field help:String[] = [ ..
  87. "A - Allow/Disallow diagonals", ..
  88. "Q - Divide terrain costs by 5", ..
  89. "W - Multiply terrain costs by 5", ..
  90. "D - Cycle distance functions", ..
  91. "V - Toggle diagonal multiplier", ..
  92. "S - Point mouse and press to set start node", ..
  93. "E - Point mouse and press to set end node", ..
  94. "P - Run AStar, showing progress", ..
  95. "H - Click and drag on 'H' to change heuristic mult.", ..
  96. "Click white square to load map of that number", ..
  97. "Click red 'S' to save map as map # 'Current Map'", ..
  98. "ESC - Quit program", ..
  99. "", ..
  100. "In progress mode:", ..
  101. "Top Number = Cost to get to that node", ..
  102. "Mid Number = Heurisitc from node to end", ..
  103. "Bottom Number = Cost from start to end", ..
  104. " Through that node", ..
  105. "Press 'P' to pause", ..
  106. "ESC to quit progress mode", ..
  107. "", ..
  108. "Handy Hints", ..
  109. "If DistanceFunc * heuristic Multiplier = ", ..
  110. "True distance to goal, fastest path generated", ..
  111. "", ..
  112. "Heuristic Mult = 0 then optimal path for", ..
  113. "chosen Distance Func "..
  114. ]
  115. ' This is the main entry point for the class
  116. Method run()
  117. ' Setup the graphics stuff
  118. Graphics 1024,768,32
  119. nums = LoadAnimImage("nums.png",5,8,0,12)
  120. Assert nums<> Null,"Can't load number graphic nums.png"
  121. ' Initialise dimensions of maps and other useful data
  122. initialise()
  123. ' Load the first map by default
  124. loadMap(currentMap)
  125. ' Redraw
  126. redraw()
  127. While Not KeyDown(27)
  128. Local m:Int = MouseDown(1)
  129. Local blockX = MouseX() / blockWidth
  130. Local blockY = MouseY() / blockHeight
  131. If m
  132. handleMouseDown(blockX, blockY)
  133. EndIf
  134. processStartPlaced(blockX, blockY)
  135. processEndPlaced(blockX, blockY)
  136. processAllowDiagonalPaths()
  137. processShowProgress()
  138. processCycleDistanceFunction()
  139. processDiagonalsAreLonger()
  140. processDecreaseTerrainCost()
  141. processIncreaseTerrainCost()
  142. If flagRedraw
  143. oldBlockX = -1 ' Mark for refresh of mouse movement
  144. lastRunTime = runAStar()
  145. redraw()
  146. flagRedraw = False
  147. EndIf
  148. Wend
  149. EndGraphics
  150. End Method
  151. '-------------------------------------
  152. ' Input handling functions
  153. '-------------------------------------
  154. Method processIncreaseTerrainCost()
  155. If KeyHit(KEY_W)
  156. Local t
  157. For t = 0 To numTerrainTypes - 1
  158. terrains[t]._weight = terrains[t]._weight * 5
  159. Next
  160. setRedraw(True)
  161. costChangeAllowed = costChangeAllowed + 1
  162. EndIf
  163. End Method
  164. Method processDecreaseTerrainCost()
  165. If KeyHit(KEY_Q) And costChangeAllowed > 0
  166. Local t
  167. For t = 0 To numTerrainTypes - 1
  168. terrains[t]._weight = terrains[t]._weight / 5
  169. Next
  170. setRedraw(True)
  171. costChangeAllowed = costChangeAllowed - 1
  172. EndIf
  173. End Method
  174. Method processDiagonalsAreLonger()
  175. If KeyHit(KEY_V)
  176. diagonalsAreLonger = 1 - diagonalsAreLonger
  177. setRedraw(True)
  178. EndIf
  179. End Method
  180. ' Cycling the distance function to use?
  181. Method processCycleDistanceFunction()
  182. If KeyHit(KEY_D)
  183. distanceFunction = (distanceFunction + 1) Mod numDistanceFunctions
  184. setRedraw(True);
  185. EndIf
  186. End Method
  187. ' Clear the last calculated path?
  188. Method processClearPath()
  189. If KeyHit(KEY_P)
  190. path = Null
  191. setRedraw(True)
  192. EndIf
  193. End Method
  194. Method processShowProgress()
  195. If KeyHit(KEY_P)
  196. showProgress = True
  197. runAStar()
  198. showProgress = False
  199. EndIf
  200. End Method
  201. ' Allow diagonal toggled?
  202. Method processAllowDiagonalPaths()
  203. If KeyHit(KEY_A)
  204. allowDiagonalPaths = 1 - allowDiagonalPaths
  205. setRedraw(True)
  206. EndIf
  207. End Method
  208. ' Check if they want to move the start node
  209. Method processStartPlaced(blockX:Int, blockY:Int)
  210. If KeyHit(KEY_S)
  211. If blockInMapArea(blockX, blockY)
  212. ' setMapBlock(blockX, blockY,0)
  213. startPos.x = blockX
  214. startPos.y = blockY
  215. setRedraw(True)
  216. EndIf
  217. EndIf
  218. End Method
  219. ' Check if they want to move the end node
  220. Method processEndPlaced(blockX:Int, blockY:Int)
  221. If KeyHit(KEY_E)
  222. If blockInMapArea(blockX, blockY)
  223. ' setMapBlock(blockX, blockY,0)
  224. endPos.x = blockX
  225. endPos.y = blockY
  226. setRedraw(True)
  227. EndIf
  228. EndIf
  229. End Method
  230. ' Call this to tell the main engine to redraw
  231. Method setRedraw(doRedraw:Int)
  232. flagRedraw = doRedraw
  233. End Method
  234. ' Use this to change a map block while editing, so that redraws are sped up
  235. Method setMapBlock(blockX, blockY, block)
  236. map[blockX, blockY] = block
  237. End Method
  238. ' Redraws the map and last found path. You can tell it to draw more than once for double buffer purposes
  239. Method redraw(times:Int = 1, flipIt:Int = 1)
  240. While times > 0
  241. drawMap()
  242. drawPath()
  243. drawTerrains()
  244. drawInfo()
  245. drawMapButtons()
  246. If flipIt Then Flip
  247. times :- 1
  248. Wend
  249. End Method
  250. Method saveMap:String(mapNumber:Int)
  251. Local stream:TStream = WriteStream("littleendian::map"+mapNumber)
  252. If stream = Null
  253. Return "Error saving map"
  254. EndIf
  255. Local x,y;
  256. For y = 0 To mapHeight - 1
  257. For x = 0 To mapWidth - 1
  258. WriteInt(stream,map[x,y])
  259. Next
  260. Next
  261. WriteInt stream, startPos.x
  262. WriteInt stream, startPos.y
  263. WriteInt stream, endPos.x
  264. WriteInt stream, endPos.y
  265. CloseStream stream
  266. Return "Done"
  267. End Method
  268. Method loadMap(mapNumber:Int)
  269. Local stream:TStream = ReadStream("littleendian::map"+mapNumber)
  270. If stream = Null
  271. Return
  272. EndIf
  273. Local x,y;
  274. For y = 0 To mapHeight - 1
  275. For x = 0 To mapWidth - 1
  276. map[x,y] = Readint(stream)
  277. Next
  278. Next
  279. startPos.x = Readint(stream)
  280. startPos.y = Readint(stream)
  281. endPos.x = Readint(stream)
  282. endPos.y = Readint(stream)
  283. CloseStream stream
  284. End Method
  285. ' Handle mouse click
  286. Method handleMouseDown(blockX:Int, blockY:Int)
  287. If blockX = oldBlockX And blockY = oldBLockY
  288. setRedraw(False)
  289. Return
  290. EndIf
  291. oldBlockX = blockX
  292. oldBlockY = blockY
  293. ' Make sure they can't edit over the start and end positions
  294. If startPos.isAtPos(blockX, blockY)
  295. setRedraw(False)
  296. Return
  297. EndIf
  298. If endPos.isAtPos(blockX, blockY)
  299. setRedraw(False)
  300. Return
  301. EndIf
  302. ' See if they are selecting a new terrain
  303. If blockX = terrainLegendBlockX And blockY >= terrainLegendBlockY And blockY < terrainLegendBlockY + numTerrainTypes
  304. currentTerrainIndex = blockY - terrainLegendBlockY
  305. Return
  306. EndIf
  307. If blockInMapArea(blockX, blockY)
  308. map[blockX, blockY] = currentTerrainIndex
  309. setRedraw(True)
  310. Return
  311. EndIf
  312. ' See if we have selected a new map
  313. If blockX >= mapButtonBlockX And blockY = mapButtonBlockY
  314. Local block:Int = blockX - mapButtonBlockX
  315. Select block
  316. Case 8
  317. message = saveMap(currentMap)
  318. Case 9
  319. handleHeuristic()
  320. Default
  321. If block < 8
  322. currentMap = block
  323. loadMap(currentMap)
  324. EndIf
  325. End Select
  326. EndIf
  327. setRedraw(True)
  328. Return ' Signal that we've moved the mouse and done something
  329. End Method
  330. Method handleHeuristic()
  331. Local oldX:Int = MouseX()
  332. Local oldY:Int = MouseY()
  333. While MouseDown(1)
  334. Local x:Int = MouseX()
  335. Local y:Int = MouseY()
  336. If x <> oldX
  337. heuristicMultiplier = heuristicMultiplier + (x - oldX) / 10.0
  338. If heuristicMultiplier < 0 heuristicMultiplier = 0
  339. If heuristicMultiplier > 10 heuristicMultiplier = 10
  340. runAStar()
  341. redraw(2)
  342. oldX:Int = x
  343. oldY:Int = y
  344. EndIf
  345. Wend
  346. End Method
  347. ' Are the block coordinates with in the map area?
  348. Method blockInMapArea(blockX:Int, blockY:Int)
  349. Return blockX >= 0 And blockY >= 0 And blockX < mapWidth And blockY < mapHeight
  350. End Method
  351. Method drawPath()
  352. If path <> Null
  353. Local a:Object
  354. For a:Object = EachIn path
  355. Local node:AStarNode = AStarNode(a)
  356. SetColor 255,255,0
  357. DrawRect node._x * blockWidth, node._y * blockHeight, blockWidth - 1, blockHeight - 1
  358. Next
  359. EndIf
  360. End Method
  361. ' Draw the information text in the editor
  362. Method drawInfo()
  363. ' Clear
  364. Local y = mapButtonBlockY * blockHeight + blockHeight
  365. SetColor 0,0,0
  366. DrawRect 0, y, blockAreaWidth, blockHeight*5
  367. ' Draw the distance function used
  368. SetColor 255,255,255
  369. DrawText distanceNames[distanceFunction], 0, y
  370. ' Whether diagonals are allowed or not
  371. Local path:String
  372. If allowDiagonalPaths
  373. path = "(A) Diagonals allowed"
  374. Else
  375. path = "(A) Diagonals not allowed"
  376. EndIf
  377. DrawText path, 0, y + TextHeight(path)
  378. ' How long last path walk took
  379. ' Local percentOfFrame:Float = lastRunTime * (60.0/1000.0) * 100.0
  380. ' DrawText "Milli:" + lastRunTime + " % of 60th:" + percentOfFrame, 0, y + FontHeight() * 5
  381. '
  382. Local diagonals:String;
  383. If diagonalsAreLonger
  384. diagonals = "(V) Diagonals are longer than straights"
  385. Else
  386. diagonals = "(V) Diagonals are same as straights"
  387. EndIf
  388. DrawText diagonals, 0, y + TextHeight(diagonals) * 2
  389. DrawText "Heuristic:" + heuristicMultiplier, 0, y + TextHeight(heuristicMultiplier) * 4
  390. SetColor 255,0,0
  391. DrawText message, 0, y + TextHeight(message) * 5
  392. SetColor 255,255,255
  393. ' Draw help
  394. Local a:String
  395. Local count:Int = 0
  396. For a:String = EachIn help
  397. DrawText a, blockAreaWidth, blockAreaHeight / 4 + count * TextHeight(a)
  398. count = count + 1
  399. Next
  400. End Method
  401. Method drawMapButtons()
  402. Local y = mapButtonBlockY * blockHeight
  403. Local x = mapButtonBlockX * blockWidth
  404. Local startX = x
  405. Local t
  406. Local output:String
  407. For t = 0 To 9
  408. If t < 8
  409. SetColor 255,255,255
  410. output = t
  411. Else If t = 8
  412. SetColor 255,0,0
  413. output = "S"
  414. Else If t = 9
  415. SetColor 0,255,0
  416. output = "H"
  417. EndIf
  418. DrawRect x, y, blockWidth - 1, blockHeight
  419. SetColor 0,0,0
  420. DrawText output, x, y
  421. x = x + blockWidth
  422. Next
  423. ' Current map
  424. y = mapButtonBlockY * blockHeight - blockHeight
  425. x = mapButtonBlockX * blockWidth
  426. SetColor 0,0,0
  427. DrawRect x,y,blockAreaWidth,TextHeight(" ")
  428. SetColor 255,255,255
  429. DrawText "Current map:" + currentMap, x, y
  430. End Method
  431. Method drawTerrains()
  432. Local t
  433. For t = 0 To numTerrainTypes - 1
  434. Local terrain:Terrain = terrains[t]
  435. Local x = terrainLegendBlockX * blockWidth
  436. Local y = (terrainLegendBlockY + t)* blockHeight
  437. SetColor 0,0,0
  438. DrawRect x, y, blockWidth * 5, blockHeight
  439. SetColor 255,255,255
  440. SetScale blockWidth / 32.0, blockHeight / 32.0
  441. DrawImage terrain._image, x, y
  442. SetScale 1,1
  443. If showCosts
  444. SetColor 255,255,255
  445. DrawText terrain._weight, x + blockWidth , y
  446. EndIf
  447. Next
  448. End Method
  449. Method drawMap()
  450. SetColor 0,0,0
  451. DrawRect 0, 0, blockAreaWidth, blockAreaHeight
  452. Local x,y;
  453. For y = 0 To mapHeight - 1
  454. For x = 0 To mapWidth - 1
  455. drawMapBlock(x,y)
  456. Next
  457. Next
  458. End Method
  459. Method drawMapBlock(x:Int, y:Int)
  460. SetColor 255,255,255
  461. If startPos.isAtPos(x,y)
  462. SetColor 255,255,0
  463. Else If endPos.isAtPos(x,y)
  464. SetColor 255,0,0
  465. Else
  466. ' SetColor(terrains[map[x,y]]._colour)
  467. EndIf
  468. SetScale blockWidth / 32.0, blockHeight / 32.0
  469. DrawImage terrains[map[x,y]]._image, x * blockWidth, y * blockHeight
  470. SetScale 1,1
  471. End Method
  472. Method drawOnMap(x:Int, y:Int, r,g,b, margin:Int)
  473. SetColor r,g,b
  474. DrawRect x * blockWidth + margin, y * blockHeight + margin, blockWidth - margin * 2, blockHeight - margin * 2
  475. End Method
  476. Method printOnMap(x:Int, y:Int, text:String, offset:Int)
  477. SetColor 255,255,255
  478. SetScale textScale,textScale
  479. DrawText text, x * blockWidth, y * blockHeight + offset
  480. SetScale 1,1
  481. End Method
  482. Method printNums(s:String, x:Int, y:Int, offset:Int)
  483. x = x * blockWidth + blockWidth / 2 - (Len(s) * 5)/2
  484. y = y * blockHeight + offset
  485. SetColor 255,255,255
  486. SetMaskColor 255,0,255
  487. SetScale 1,1
  488. Local t
  489. For t = 0 To Len(s) - 1
  490. DrawImage nums, x, y, Byte(s[t]) - 46
  491. x:+5
  492. Next
  493. End Method
  494. ' Initialise the map and edit stuff
  495. Method initialise()
  496. ' blockWidth = blockAreaWidth / mapWidth
  497. ' blockHeight = blockAreaHeight / mapHeight
  498. ' Setup other interactive pieces
  499. terrainLegendBlockX = mapWidth + 1
  500. terrainLegendBlockY = 0
  501. ' map = Array:Int[mapWidth, mapHeight]
  502. ' nodeMap = Array:AStarNode[mapWidth, mapHeight]
  503. ' Initialise terrain types
  504. Local t
  505. For t = 0 To numTerrainTypes - 1
  506. Local newTerrain:Terrain = New Terrain
  507. newTerrain.set(terrainWeights[t], terrainFilenames[t])
  508. terrains[t] = newTerrain
  509. Next
  510. startPos = New MapPos
  511. startPos.x = 1
  512. startPos.y = 1
  513. endPos = New MapPos
  514. endPos.x = mapWidth - 2
  515. endPos.y = mapHeight - 2
  516. ' Initialise the map
  517. SeedRnd MilliSecs()
  518. Local y
  519. Local x
  520. For y = 0 To mapHeight - 1
  521. For x = 0 To mapWidth - 1
  522. Local value:Int = 0'Rand(0,numTerrainTypes - 1)
  523. If y = 0 Or y = mapHeight - 1 Or x = 0 Or x = mapWidth - 1
  524. value = 1
  525. EndIf
  526. map[x,y] = value 'Readint(stream)
  527. Next
  528. Next
  529. End Method
  530. ' This runs AStar with the current setup
  531. Method runAStar()
  532. ' The first thing you need to do before using the AStarGraphWalker is to create your nodes, and link them up
  533. ' with edges. What I do is first make an array of the nodes, this makes it easy to map nodes to map blocks if
  534. ' I need to and other things.
  535. ' Then I go over the node array and create links to the neighbouring nodes.
  536. '
  537. ' NOTE: In my implementation you will notice that between two blocks, two edges are created.
  538. ' Block 1 has an edge to block 2 (which is it's neighbour) and viceversa. If you wanted to
  539. ' simplify this, you could, but you'd have to change the datastructure a little. I prefer my way (Except of course
  540. ' that more memory is consumed) because I can have different costs to get from A to B than from B to A, for example
  541. ' if A was at the top of a hill and B at the bottom. In that case, going from A to B is generally easier than B to A
  542. Local x:Int
  543. Local y:Int
  544. For y = 0 To mapHeight - 1
  545. For x = 0 To mapWidth - 1
  546. nodeMap[x,y] = New AStarNode
  547. Local node:AStarNode = nodeMap[x,y]
  548. node._x = x ' AStar needs these positions for distance estimation
  549. node._y = y
  550. Next
  551. Next
  552. ' This is just an array of offsets to give us our neighbours
  553. Const offsetCount:Int = 8
  554. Local xOffset:Int[] = [ 0,1,1,1,0,-1,-1,-1 ]
  555. Local yOffset:Int[] = [ -1,-1,0,1,1,1,0,-1 ]
  556. ' How we move through the offsets, so we can support "no diagnoal" paths
  557. Local offsetStep:Int = 1
  558. If Not allowDiagonalPaths
  559. offsetStep = 2
  560. EndIf
  561. ' This loop builds up all the neighbour lists for a node
  562. For y = 0 To mapHeight - 1
  563. For x = 0 To mapWidth - 1
  564. If map[x,y] = 1
  565. Continue ' We don't worry about this node if we aren't passable
  566. EndIf
  567. Local node:AStarNode = nodeMap[x,y]
  568. ' Now look around the map for neighbours and make nodes
  569. ' Joining the current one with a neighbour
  570. Local off = 0
  571. While off < offsetCount
  572. Local neighbourX = x + xOffset[off]
  573. Local neighbourY = y + yOffset[off]
  574. ' Check that the neighbour position is within the map bounds and is actually
  575. ' not block 1 which I've designated as a block we can't go through at all so no point
  576. ' making a neighbour of it
  577. If map[neighbourX, neighbourY] <> 1 And neighbourX >= 0 And neighbourX < mapWidth And neighbourY >=0 And neighbourY < mapHeight
  578. Local value:Int = terrains[map[x,y]]._weight
  579. Local neighbourValue:Int = terrains[map[neighbourX,neighbourY]]._weight
  580. ' Here is where you calculate the costs of the edge between two nodes. Because our map is square and the neighbour
  581. ' I'm looking at is adjacent to the current node, then I can just take an average of the costs of each block.
  582. ' E.G If the current block is water, and the neighbour is forest, I'd be walking half in water and half in forest
  583. ' This isn't really necessary but I do it anyway
  584. Local edgeCost:Float = (value + neighbourValue) / 2.0
  585. ' Now if it's a diagonal we might want to make the cost of this edge higher to signify that
  586. ' travelling diagonally in a suare environment like ours is a longer distance
  587. If diagonalsAreLonger And xOffset[off] <> 0 And yOffset[off] <> 0
  588. edgeCost = edgeCost * 1.4;
  589. EndIf
  590. node.addNeighbour(nodeMap[neighbourX, neighbourY], edgeCost)
  591. EndIf
  592. off = off + offsetStep
  593. Wend
  594. Next
  595. Next
  596. ' We have our node, so we can set up our AStarGraphWalker now.
  597. Local walker:AStarGraphWalker = New AStarGraphWalker
  598. walker.setParameters(nodeMap[startPos.x,startPos.y], nodeMap[endPos.x,endPos.y], mapWidth * mapHeight)
  599. walker.setDistanceFunction(distanceFunction) ' If not called, default is distanceEuclidean
  600. walker.setHeuristicMultiplier(heuristicMultiplier) ' If not called, default to 1
  601. ' Set up our callback information. The callback is called after one node has been popped and processed
  602. Local callback:WalkerCallback = New WalkerCallback
  603. callback._walker = walker
  604. callback._application = Self
  605. If showProgress
  606. walker.setCallback(callback)
  607. EndIf
  608. Local start:Int = MilliSecs()
  609. Local res:Int = walker.walk()
  610. Local time:Int = MilliSecs() - start
  611. If res
  612. path = walker.getFinalPath()
  613. If showProgress
  614. drawPath()
  615. Flip
  616. EndIf
  617. Else
  618. path = Null
  619. EndIf
  620. 'EndRem
  621. ' Tidy up after ourselves
  622. walker.setCallback(Null)
  623. ' Need to make sure memory gets freed because we have references to objects all over
  624. ' the place
  625. For y = 0 To mapHeight - 1
  626. For x = 0 To mapWidth - 1
  627. Local node:AStarNode = nodeMap[x,y]
  628. Local neighbour:AStarNeighbourInfo
  629. For neighbour:AStarNeighbourInfo = EachIn node.getNeighbours()
  630. neighbour.free()
  631. Next
  632. walker.resetNode(node)
  633. nodeMap[x,y] = Null
  634. Next
  635. Next
  636. Return time
  637. End Method
  638. EndType
  639. ' This is our personal callback function so that we can draw the map. In a real implementation you might not have this
  640. ' or maybe the callback allows you to check to see if the algorithm is taking too long and so pause for a frame and continue
  641. ' next frame. You'd need some more work that though
  642. Type WalkerCallback Extends AStarCallback
  643. Method New()
  644. _waitForKey = True ' If ever set to false, then we are ignoring the callback so we just return
  645. End Method
  646. Method callback()
  647. If _waitForKey = False ' If not waiting for key, then we've finished
  648. While KeyDown(27) Wend
  649. _application.setRedraw(True)
  650. Return
  651. EndIf
  652. Local currentNode:AStarNode = node
  653. _application.redraw(1,0)
  654. Local x:Int
  655. Local y:Int
  656. For y = 0 To _application.mapHeight - 1
  657. For x = 0 To _application.mapWidth - 1
  658. Local node:AStarNode = _application.nodeMap[x,y]
  659. Local col_r=128,col_g=128,col_b=128
  660. Local do:Int = 0;
  661. If node.inClosed() Or Not node.inOpen() Then
  662. Continue
  663. EndIf
  664. If node.inOpen() Then
  665. col_r=0 ; col_g=0 ;col_b=255
  666. do = 1
  667. EndIf
  668. If do Then
  669. _application.drawOnMap(node._x, node._y, col_r, col_g, col_b , 2 )
  670. _application.printNums(oneDec(node.costToGetHere()), node._x , node._y, 4 )
  671. _application.printNums(oneDec(node._goalDistance), node._x , node._y, 11 )
  672. _application.printNums(oneDec(node._key), node._x , node._y, 18 )
  673. EndIf
  674. Next
  675. Next
  676. Flip
  677. If KeyHit(KEY_P)
  678. WaitKey
  679. EndIf
  680. If KeyDown(27)
  681. _waitForKey = False
  682. EndIf
  683. End Method
  684. ' Makes a single decimal place string from a number - ugly....
  685. Method oneDec:String(number:Float)
  686. Return String(Int(number)) + "." + Int((number-Int(number))*10)
  687. End Method
  688. Field _walker:AStarGraphWalker;
  689. Field _application:AStarDemo;
  690. Field _waitForKey:Int
  691. EndType