uGBEPathFinder.pas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. unit uGBEPathFinder;
  2. (*
  3. Implementation of the A* algorithm https://fr.wikipedia.org/wiki/Algorithme_A*
  4. Written by Gregory Bersegeay
  5. *)
  6. interface
  7. uses
  8. System.SysUtils,
  9. System.Types,
  10. System.UITypes,
  11. System.Classes,
  12. System.Generics.Collections;
  13. Type
  14. TGBENoeud = record
  15. public
  16. CoutDeplacement, Heuristique, EstimationCout: integer;
  17. Position, Parent: TPoint;
  18. end;
  19. TGBEPathFinderMode = (DeplacementsMinimum, CoutMinimum);
  20. TGBEPathFinder = class
  21. fNoeudDepart, fNoeudArrivee: TGBENoeud;
  22. ListeNoeudsPossibles: TDictionary<TPoint, TGBENoeud>;
  23. ListeNoeudsVoisins: TDictionary<TPoint, TGBENoeud>;
  24. flGrille, fhGrille, fCoutDeplacementCote, fCoutDeplacementDiagonal: integer;
  25. fAutoriserDeplacementDiagonal, fQuePremiereEtape: boolean;
  26. fMode: TGBEPathFinderMode;
  27. // Allows you to calculate the cost from a given point to the arrival
  28. function CalculerCoutArrivee(Point: TPoint): integer;
  29. (*
  30. 2nd part: allows to trace only the path from the tracks explored in step 1
  31. We will go through the list of nodes explored in step 1 starting from the arrival node and going back
  32. to the departure node in order to list only the nodes necessary for the constitution of the path
  33. *)
  34. procedure OptimiserChemin;
  35. // Retrieves the least expensive node in a list
  36. function RechercheCoutTotalMin(Liste: TDictionary<TPoint, TGBENoeud>)
  37. : TGBENoeud;
  38. // Allows you to list the neighbors of a given node
  39. procedure ListerVoisins(UnNoeud: TGBENoeud);
  40. public
  41. ListeChemin: TDictionary<TPoint, TGBENoeud>;
  42. ListeNoeudsObstacles: TDictionary<TPoint, TGBENoeud>;
  43. constructor Create; virtual;
  44. destructor Destroy; override;
  45. (*
  46. Algorithm A*: 1st step
  47. We explore all the tracks until we find the arrival node
  48. *)
  49. function RechercherChemin: boolean;
  50. property NoeudDepart: TGBENoeud read fNoeudDepart write fNoeudDepart;
  51. property NoeudArrivee: TGBENoeud read fNoeudArrivee write fNoeudArrivee;
  52. property LargeurGrille: integer read flGrille write flGrille;
  53. property HauteurGrille: integer read fhGrille write fhGrille;
  54. property CoutDeplacementCote: integer read fCoutDeplacementCote
  55. write fCoutDeplacementCote;
  56. property CoutDeplacementDiagonal: integer read fCoutDeplacementDiagonal
  57. write fCoutDeplacementDiagonal;
  58. property AutoriserDeplacementDiagonal: boolean
  59. read fAutoriserDeplacementDiagonal write fAutoriserDeplacementDiagonal;
  60. property QuePremiereEtape: boolean read fQuePremiereEtape
  61. write fQuePremiereEtape;
  62. property Mode: TGBEPathFinderMode read fMode write fMode;
  63. end;
  64. implementation // --------------------------------------------------------------
  65. // TGBEPathFinder
  66. // ----------------------------------------------------------------------------
  67. constructor TGBEPathFinder.Create;
  68. begin
  69. LargeurGrille := 12;
  70. HauteurGrille := 10;
  71. CoutDeplacementCote := 10;
  72. CoutDeplacementDiagonal := 15;
  73. AutoriserDeplacementDiagonal := true;
  74. QuePremiereEtape := false;
  75. Mode := TGBEPathFinderMode.DeplacementsMinimum;
  76. ListeNoeudsPossibles := TDictionary<TPoint, TGBENoeud>.Create;
  77. ListeChemin := TDictionary<TPoint, TGBENoeud>.Create;
  78. ListeNoeudsObstacles := TDictionary<TPoint, TGBENoeud>.Create;
  79. ListeNoeudsVoisins := TDictionary<TPoint, TGBENoeud>.Create;
  80. end;
  81. // ----------------------------------------------------------------------------
  82. function TGBEPathFinder.CalculerCoutArrivee(Point: TPoint): integer;
  83. var
  84. valeurDiagonale, valeurCote, absX, absY: integer;
  85. begin
  86. absX := abs(Point.X - NoeudArrivee.Position.X);
  87. absY := abs(Point.Y - NoeudArrivee.Position.Y);
  88. if absX > absY then
  89. begin
  90. valeurDiagonale := absY * CoutDeplacementDiagonal;
  91. valeurCote := (absX - absY) * CoutDeplacementCote;
  92. end
  93. else
  94. begin
  95. valeurDiagonale := absX * CoutDeplacementDiagonal;
  96. valeurCote := (absY - absX) * CoutDeplacementCote;
  97. end;
  98. result := valeurDiagonale + valeurCote;
  99. end;
  100. // ----------------------------------------------------------------------------
  101. function TGBEPathFinder.RechercherChemin: boolean;
  102. var
  103. UnNoeud: TGBENoeud;
  104. unVoisin: TPoint;
  105. begin
  106. result := false;
  107. // Initialize return to false (indicating that no path was found)
  108. ListeChemin.Clear;
  109. ListeNoeudsVoisins.Clear;
  110. ListeNoeudsPossibles.Clear;
  111. ListeNoeudsPossibles.Add(NoeudDepart.Position, NoeudDepart);
  112. // at the beginning, we place ourselves on the starting node, it is the only possible node
  113. while ListeNoeudsPossibles.Count > 0 do
  114. begin // As long as the list of possible nodes is not empty
  115. UnNoeud := RechercheCoutTotalMin(ListeNoeudsPossibles);
  116. // search for the possible node with the minimum cost
  117. ListeNoeudsPossibles.Remove(UnNoeud.Position);
  118. // we remove the found node from the list of possible nodes
  119. ListeChemin.Add(UnNoeud.Position, UnNoeud);
  120. // we add it to the list of nodes traveled to find the path
  121. if UnNoeud.Position = NoeudArrivee.Position then
  122. begin // if the found node is the arrival node (test on the position)
  123. NoeudArrivee := UnNoeud;
  124. // we take the information from the found node to complete the information
  125. // from the arrival node (among other things the position of its parent)
  126. ListeNoeudsPossibles.Clear;
  127. result := true; // We found a way
  128. break; // we exit the while
  129. end;
  130. ListerVoisins(UnNoeud);
  131. // We fill in the list of neighboring nodes of the found node
  132. for unVoisin in ListeNoeudsVoisins.Keys do
  133. begin // Traversing neighboring nodes
  134. if ListeChemin.ContainsKey(unVoisin) then
  135. continue;
  136. // If the neighbor is already in the list of nodes traversed,
  137. // we move on to the next iteration
  138. if not(ListeNoeudsPossibles.ContainsKey(unVoisin)) then
  139. begin
  140. // If the neighbor is not already in the list of possible nodes, we add it.
  141. ListeNoeudsPossibles.Add(unVoisin, ListeNoeudsVoisins.Items[unVoisin]);
  142. end;
  143. end;
  144. end;
  145. // 1st step completed, if we have found a solution and we wish to do the 2nd step,
  146. // so we move on to "optimization"
  147. if result and not(QuePremiereEtape) then
  148. OptimiserChemin;
  149. end;
  150. // ----------------------------------------------------------------------------
  151. procedure TGBEPathFinder.OptimiserChemin;
  152. var
  153. ListeOptimisee: TList<TGBENoeud>;
  154. iNoeud: TGBENoeud;
  155. begin
  156. ListeOptimisee := TList<TGBENoeud>.Create;
  157. // We go through a temporary list
  158. iNoeud := NoeudArrivee; // We start from the arrival node
  159. while iNoeud.Position <> NoeudDepart.Position do
  160. begin // As long as we are not on the starting node
  161. ListeOptimisee.Add(iNoeud);
  162. // We place the current node in the temporary list
  163. ListeChemin.TryGetValue(iNoeud.Parent, iNoeud);
  164. // the new current node becomes the parent node of the current node
  165. end;
  166. ListeOptimisee.Add(NoeudDepart);
  167. // We add the starting node to the end of the list
  168. ListeOptimisee.Reverse;
  169. // We reverse the list (to have the nodes in the order of starting node to arrival node)
  170. ListeChemin.Clear;
  171. for iNoeud in ListeOptimisee do
  172. // We replace the optimized list found in ListPath
  173. ListeChemin.Add(iNoeud.Position, iNoeud);
  174. FreeAndNil(ListeOptimisee);
  175. end;
  176. // ----------------------------------------------------------------------------
  177. function TGBEPathFinder.RechercheCoutTotalMin
  178. (Liste: TDictionary<TPoint, TGBENoeud>): TGBENoeud;
  179. var
  180. iNoeud: TPoint;
  181. tableau: TArray<TPair<TPoint, TGBENoeud>>;
  182. begin
  183. if Liste.Count > 0 then
  184. begin
  185. tableau := Liste.ToArray;
  186. // Tip to retrieve the first element of a TDictionary (no first method on the TDictionary)
  187. result := tableau[0].Value;
  188. // Tip for retrieving the first element of a TDictionary
  189. for iNoeud in Liste.Keys do
  190. begin // List browsing
  191. if Liste.Items[iNoeud].EstimationCout < result.EstimationCout then
  192. result := Liste.Items[iNoeud]
  193. else
  194. begin
  195. if Liste.Items[iNoeud].EstimationCout = result.EstimationCout then
  196. begin
  197. case Mode of
  198. DeplacementsMinimum:
  199. begin
  200. if Liste.Items[iNoeud].Heuristique < result.Heuristique then
  201. result := Liste.Items[iNoeud]
  202. else
  203. begin
  204. if Liste.Items[iNoeud].Heuristique = result.Heuristique then
  205. begin
  206. if Liste.Items[iNoeud].CoutDeplacement < result.CoutDeplacement
  207. then
  208. result := Liste.Items[iNoeud];
  209. end;
  210. end;
  211. end;
  212. CoutMinimum:
  213. begin
  214. if Liste.Items[iNoeud].CoutDeplacement < result.CoutDeplacement
  215. then
  216. result := Liste.Items[iNoeud]
  217. else
  218. begin
  219. if Liste.Items[iNoeud].CoutDeplacement = result.CoutDeplacement
  220. then
  221. begin
  222. if Liste.Items[iNoeud].Heuristique < result.Heuristique then
  223. result := Liste.Items[iNoeud];
  224. end;
  225. end;
  226. end;
  227. end;
  228. end;
  229. end;
  230. end;
  231. end;
  232. end;
  233. // ----------------------------------------------------------------------------
  234. procedure TGBEPathFinder.ListerVoisins(UnNoeud: TGBENoeud);
  235. var
  236. unVoisin: TGBENoeud;
  237. X, Y: integer;
  238. begin
  239. ListeNoeudsVoisins.Clear;
  240. // Traversing the 8 positions around the given node
  241. for X := -1 to 1 do
  242. begin
  243. for Y := -1 to 1 do
  244. begin
  245. if (X = 0) and (Y = 0) then
  246. continue;
  247. if not(AutoriserDeplacementDiagonal) then
  248. begin // if diagonal movements are allowed
  249. if (X = -1) and (Y = -1) then
  250. continue;
  251. if (X = 1) and (Y = -1) then
  252. continue;
  253. if (X = 1) and (Y = 1) then
  254. continue;
  255. if (X = -1) and (Y = 1) then
  256. continue;
  257. end;
  258. unVoisin.Position.X := UnNoeud.Position.X + X;
  259. unVoisin.Position.Y := UnNoeud.Position.Y + Y;
  260. // The neighbor must be in the grid
  261. if (unVoisin.Position.X >= 0) and (unVoisin.Position.X < LargeurGrille)
  262. and (unVoisin.Position.Y >= 0) and (unVoisin.Position.Y < HauteurGrille)
  263. then
  264. begin
  265. if (unVoisin.Position.X <> UnNoeud.Position.X) and
  266. (unVoisin.Position.Y <> UnNoeud.Position.Y) then
  267. unVoisin.CoutDeplacement := CoutDeplacementDiagonal
  268. else
  269. unVoisin.CoutDeplacement := CoutDeplacementCote;
  270. unVoisin.Parent := UnNoeud.Position;
  271. // If the neighbor is not in the list of obstacle nodes,
  272. // we can add it to the list of neighbor nodes
  273. if (not(ListeNoeudsObstacles.ContainsKey(unVoisin.Position))) then
  274. begin
  275. // We calculate its costs
  276. unVoisin.Heuristique := CalculerCoutArrivee(unVoisin.Position);
  277. unVoisin.EstimationCout := unVoisin.CoutDeplacement +
  278. unVoisin.Heuristique;
  279. // We add the node to the neighbor list
  280. ListeNoeudsVoisins.Add(unVoisin.Position, unVoisin);
  281. end;
  282. end;
  283. end;
  284. end;
  285. end;
  286. // ----------------------------------------------------------------------------
  287. destructor TGBEPathFinder.Destroy;
  288. begin
  289. FreeAndNil(ListeNoeudsPossibles);
  290. FreeAndNil(ListeChemin);
  291. FreeAndNil(ListeNoeudsObstacles);
  292. FreeAndNil(ListeNoeudsVoisins);
  293. inherited;
  294. end;
  295. end.