Advertisement
WarPie90

Shortest path (GBFS/A*)

Jun 15th, 2025 (edited)
460
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Delphi 9.50 KB | None | 0 0
  1. program MazeSolver;
  2. {.$I SRL/OSR.simba}
  3. {$R-}
  4.  
  5.  
  6. type
  7.   EDistanceHeuistics = (dhEuclidean, dhSqaredEuclidean, dhManhattan, dhChebyshev);
  8.  
  9.   TPathFinder = record
  10.     Image: TImage;
  11.     Color: TColor;
  12.     Eightway: Boolean;
  13.  
  14.     Heuristics: EDistanceHeuistics;
  15.     HeuristicsMultiplier: Single;
  16.  
  17.     BFSMeasureBacktrace: Boolean;
  18.   end;
  19.  
  20.   // home brewed search structure for the heuristic BFS lookup
  21.   // maintains order. High cost of insertion O(n). But O(1) pop.
  22.   // Used as an alternative to Min heap for this exact purpose.
  23.   TPathStruct = record
  24.     Node: TPoint;
  25.     Hdist: single;
  26.     Rdist: single;
  27.   end;
  28.  
  29.   TSearchArray = record
  30.     lo, hi: Int32;
  31.     data: array of TPathStruct;
  32.   end;
  33.  
  34.  
  35. procedure TSearchArray.Init(sz: Int32);
  36. begin
  37.   SetLength(Self.data, sz);
  38.  
  39.   Self.hi := -1;
  40.   Self.lo := 0;
  41. end;
  42.  
  43. // find the current value or the closest value in the array.
  44. // Time complexity: O(log n)
  45. function TSearchArray.SearchClosest(value: single): Int32;
  46. var
  47.   l, h: Int32;
  48. begin
  49.   l := Self.Lo;
  50.   h := Self.Hi;
  51.  
  52.   while l <= h do
  53.   begin
  54.     Result := (l + h) div 2;
  55.     if Self.Data[Result].hdist < value then
  56.       h := Result - 1
  57.     else if Self.Data[Result].hdist > value then
  58.       l := Result + 1
  59.     else
  60.       Exit(Result);
  61.   end;
  62. end;
  63.  
  64. // Insert the a value into it's correct position by searching.
  65. // Time complexity: O(n)
  66. // For this usage we are looking at time complexity in range of
  67. // average O(log n) from the binary search, few moves in practice
  68. function TSearchArray.Insert(_node:TPoint; _hdist, _rdist: single): Int32;
  69. var
  70.   idx,i: Int32;
  71. begin
  72.   if (Self.hi = -1) or (Self.Data[Self.hi].hdist >= _hdist) then
  73.     idx := self.hi + 1
  74.   else if (Self.hi >= 5) and (_hdist <= Self.Data[Self.hi-5].hdist) then
  75.   begin
  76.     for idx := self.hi+1 downto Self.hi-5 do
  77.     begin
  78.       Self.data[idx] := Self.data[idx-1];
  79.       if Self.data[idx].hdist >= _hdist then break;
  80.     end;
  81.   end else begin
  82.     idx := Self.SearchClosest(_hdist);
  83.     if Self.Data[idx].hdist > _hdist then Inc(idx);
  84.  
  85.     if (self.hi+1-idx) > 0 then
  86.       Move(Self.data[idx], Self.data[idx+1], (self.hi+1-idx)*(SizeOf(TPathStruct)));
  87.   end;
  88.  
  89.   with Self.data[idx] do
  90.   begin
  91.     hdist := _hdist;
  92.     rdist := _rdist;
  93.     node  := _node;
  94.   end;
  95.  
  96.   Inc(Self.hi);
  97. end;
  98.  
  99. // Pop the largest or smallest value depending on insertion order
  100. // Time complexity: O(1)
  101. procedure TSearchArray.Pop(out _node:TPoint; out _hdist, _rdist: single);
  102. begin
  103.   with self.data[self.hi] do
  104.   begin
  105.     _node  := node;
  106.     _hdist := hdist;
  107.     _rdist := rdist;
  108.   end;
  109.  
  110.   Dec(Self.Hi);
  111. end;
  112.  
  113. function TSearchArray.AsString(): String;
  114. var
  115.   tmp: array of TPathStruct;
  116. begin
  117.   tmp := self.data;
  118.   SetLength(tmp, self.hi+1);
  119.   Result := ToString(tmp);
  120. end;
  121.  
  122.  
  123. function Euclidean(p,q: TPoint): Single;   begin Result := Sqrt(Sqr(p.x-q.x) + Sqr(p.y-q.y)); end;
  124. function SqEuclidean(p,q: TPoint): Single; begin Result := Sqr(p.x-q.x) + Sqr(p.y-q.y); end;
  125. function Manhattan(p,q: TPoint): Single;   begin Result := Abs(p.x-q.x) + Abs(p.y-q.y); end;
  126. function Chebyshev(p,q: TPoint): Single;   begin Result := Max(Abs(p.x-q.x), Abs(p.y-q.y)); end;
  127.  
  128.  
  129.  
  130. (*
  131.   Returns the (approximate) shortest path from start to stop, appximcation depends on the paramters.
  132.  
  133.   * Start: Has to be a valid pixel that is Self.Color;
  134.   * Stop:  Has to be a valid pixel that is Self.Color;
  135.  
  136.   Use TPathFinder.Init to set these parameters for the search:
  137.  
  138.   * Eightway: Floodfill in eight of four directions.
  139.   * Heuristics:
  140.       0: Euclidian distance
  141.       1: Squared euclidian distance
  142.       2: Manhattan distance
  143.       3: Chebyshev distance
  144.   * HeuristicsScale: A mutiplier for the distance heuristics allowing for more appxiate paths (faster!)
  145.   * BFSMeasureBacktrace: Adds a distance value to the map for (often) more accurate backtrace, specially useful
  146.     when you are using sqrared euclidian distance or in the case of HeuristicsScale greater than 1.
  147.  
  148.  
  149.   Note: Default parameters will result in shortest possible path, but is much slower than using any form
  150.         of heuristics. Adding some scaling can result in runtime being up to 10 times faster.
  151.  
  152.   Algorithm is built around the principles of greedy BFS, but extended to match that of an
  153.   A* algorithm under default parameters which ensures actual shortest path. And further if multiplier is set to `0`
  154.   it will act as dijkstras.
  155.  
  156.  
  157.   ```pascal
  158.   procedure TPathfinder.Init(AImage: TImage; AColor: TColor; AEightway:Boolean; AHeuristics:EDistanceHeuistics; AHeuristicsMultiplier:Single=1; AMeasureBacktrace:Boolean=False);
  159.   procedure TPathfinder.InitAStar(AImage: TImage; AColor: TColor; AEightway: Boolean);
  160.   procedure TPathfinder.InitDijkstras(AImage: TImage; AColor: TColor; AEightway: Boolean);
  161.  
  162.   function TPathfinder.FloodFillMatrix(Start, Stop: TPoint): TSingleMatrix;
  163.   function TPathfinder.Backtrace(Matrix: TSingleMatrix; Start, Stop: TPoint): TPointArray;
  164.   function TPathfinder.Solve(Start, Stop: TPoint): TPointArray;
  165.   ```
  166. *)
  167. procedure TPathfinder.Init(AImage: TImage; AColor: TColor; AEightway:Boolean; AHeuristics:EDistanceHeuistics; AHeuristicsMultiplier:Single=1; AMeasureBacktrace:Boolean=False);
  168. begin
  169.   Self.Image                := AImage;
  170.   Self.Color                := AColor;
  171.   Self.BFSMeasureBacktrace  := AMeasureBacktrace;
  172.   Self.Eightway             := AEightway;
  173.   Self.HeuristicsMultiplier := AHeuristicsMultiplier;
  174.   Self.Heuristics           := AHeuristics;
  175. end;
  176.  
  177. procedure TPathfinder.InitAStar(AImage: TImage; AColor: TColor; AEightway: Boolean);
  178. begin
  179.   Self.Image                := AImage;
  180.   Self.Color                := AColor;
  181.   Self.BFSMeasureBacktrace  := False;
  182.   Self.Eightway             := AEightway;
  183.   Self.HeuristicsMultiplier := 1;
  184.   Self.Heuristics           := EDistanceHeuistics.dhEuclidean;
  185. end;
  186.  
  187. procedure TPathfinder.InitDijkstras(AImage: TImage; AColor: TColor; AEightway: Boolean);
  188. begin
  189.   Self.Image                := AImage;
  190.   Self.Color                := AColor;
  191.   Self.BFSMeasureBacktrace  := False;
  192.   Self.Eightway             := AEightway;
  193.   Self.HeuristicsMultiplier := 0;
  194.   Self.Heuristics           := EDistanceHeuistics.dhManhattan;
  195. end;
  196.  
  197.  
  198. (*
  199.   Floodfills a matrix from `start` until it hits `stop`, with incrementing values appeoximating the
  200.   distance from start.
  201.   Can be used for backtracing and visual purposes to show a distance matrix from start to stop.
  202. *)
  203. function TPathfinder.FloodFillMatrix(Start, Stop: TPoint): TSingleMatrix;
  204. var
  205.   score,hdist,a: single;
  206.   arr: TSearchArray;
  207.   p,q: TPoint;
  208.   adj: TPointArray = [[-1,0],[1,0],[0,-1],[0,1],[1,-1],[1,1],[-1,-1],[-1,1]];
  209.   h:function(p,q: TPoint): Single;
  210. begin
  211.   case Self.Heuristics of
  212.     dhEuclidean:       h := @Euclidean;
  213.     dhSqaredEuclidean: h := @SqEuclidean;
  214.     dhManhattan:       h := @Manhattan;
  215.     dhChebyshev:       h := @Chebyshev;
  216.     else               h := @Euclidean;
  217.   end;
  218.  
  219.   arr.Init(Self.Image.Width * Self.Image.Height);
  220.   arr.Insert(start, h(start,stop)*Self.HeuristicsMultiplier, 0);
  221.  
  222.   Result.SetSize(Self.Image.Width, Self.Image.Height);
  223.  
  224.   if not Self.Eightway then
  225.     SetLength(adj, 4);
  226.  
  227.   // heuristical forward search for goal
  228.   while (arr.hi >= 0) do
  229.   begin
  230.     arr.Pop(p, hdist, score); //inline for higher performance
  231.     if (p = stop) then
  232.       Exit;
  233.  
  234.     for q in adj do
  235.       with q do
  236.       begin
  237.         x += p.x;
  238.         y += p.y;
  239.         if Self.Image.InImage(x,y) and (Self.Image.Pixel[x,y] = Self.Color) and (Result[y,x] = 0) then
  240.         begin
  241.           arr.Insert(q, score + h(q,stop) * Self.HeuristicsMultiplier, score+1);
  242.  
  243.           if Self.BFSMeasureBacktrace then
  244.             Result[y,x] := p.DistanceTo(start) + Sqr(score)+1
  245.           else
  246.             Result[y,x] := score+1;
  247.         end;
  248.       end;
  249.   end;
  250. end;
  251.  
  252.  
  253. (*
  254.   Backtraces a distance matrix from stop to start, returning the final path.
  255.  
  256.   For description see overload method.
  257. *)
  258. function TPathfinder.Backtrace(Matrix: TSingleMatrix; Start, Stop: TPoint): TPointArray;
  259. var
  260.   score: single;
  261.   p,q,t: TPoint;
  262.   adj: TPointArray = [[-1,0],[1,0],[0,-1],[0,1],[1,-1],[1,1],[-1,-1],[-1,1]];
  263. begin
  264.   if not Self.Eightway then SetLength(adj, 4);
  265.  
  266.   // backtrace towards 0 distance from stop using the distance matrix
  267.   p := stop;
  268.   score := Matrix[p.y,p.x];
  269.   Result += p; //pre-allocate result size for another boost SetLen(res, Ceil(score))
  270.   repeat
  271.     for q in adj do
  272.     begin
  273.       q.x += p.x;
  274.       q.y += p.y;
  275.       if Self.Image.InImage(q.x,q.y) and
  276.          (Matrix[q.y, q.x] < score) and (Matrix[q.y, q.x] > 0) then
  277.       begin
  278.         t := q;
  279.         score := Matrix[t.y, t.x];
  280.       end;
  281.     end;
  282.     p := t;
  283.     score := Matrix[p.y, p.x];
  284.     Matrix[p.y, p.x] := 0;
  285.     Result += p;
  286.   until score = 0;
  287.   Result += start;
  288. end;
  289.  
  290. function TPathfinder.Solve(Start, Stop: TPoint): TPointArray;
  291. var
  292.   matrix: TSingleMatrix;
  293. begin
  294.   matrix := Self.FloodFillMatrix(Start, Stop);
  295.   Result := Self.Backtrace(matrix, Start, Stop);
  296. end;
  297.  
  298.  
  299. var
  300.   //start := Point(1,18); stop  := Point(564,547);
  301.   stop := Point(637,478); start := Point(4,1);
  302.  
  303.   img: TImage;
  304.   path1: TPointArray;
  305.   t:Double;
  306.   pf: TPathFinder;
  307. begin
  308.   img := new TImage('images\maze.png');
  309.   Swap(start,stop);
  310.  
  311.   pf.Init(img, $FFFFFF, True, EDistanceHeuistics.dhEuclidean, 1);
  312.  
  313.   for 0 to 5 do
  314.   begin
  315.     t := Time();
  316.     path1 := pf.Solve(start, stop);
  317.     WriteLn(Time() - t, 'ms');
  318.   end;
  319.   WriteLn('Length: ', Length(path1));
  320.  
  321.   img.DrawColor := $FF00FF;
  322.   img.DrawTPA(path1);
  323.  
  324.   img.Show();
  325. end.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement