{"id":18069605,"url":"https://github.com/riscy/a_star_on_grids","last_synced_at":"2025-06-17T04:39:36.353Z","repository":{"id":94012542,"uuid":"77487029","full_name":"riscy/a_star_on_grids","owner":"riscy","description":"Best practices for implementing A* with a focus on four- and eight-connected grid worlds.","archived":false,"fork":false,"pushed_at":"2020-11-27T20:02:19.000Z","size":1456,"stargazers_count":44,"open_issues_count":0,"forks_count":3,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-04-11T23:52:48.714Z","etag":null,"topics":["a-star","best-practices","fringe-search","game-ai","pathfinding"],"latest_commit_sha":null,"homepage":null,"language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/riscy.png","metadata":{"files":{"readme":"README.org","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-12-27T22:49:19.000Z","updated_at":"2025-02-15T01:31:29.000Z","dependencies_parsed_at":"2023-04-05T23:07:49.555Z","dependency_job_id":null,"html_url":"https://github.com/riscy/a_star_on_grids","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/riscy/a_star_on_grids","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/riscy%2Fa_star_on_grids","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/riscy%2Fa_star_on_grids/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/riscy%2Fa_star_on_grids/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/riscy%2Fa_star_on_grids/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/riscy","download_url":"https://codeload.github.com/riscy/a_star_on_grids/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/riscy%2Fa_star_on_grids/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260294188,"owners_count":22987600,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["a-star","best-practices","fringe-search","game-ai","pathfinding"],"created_at":"2024-10-31T08:10:43.553Z","updated_at":"2025-06-17T04:39:36.325Z","avatar_url":"https://github.com/riscy.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"#+TITLE: Best practices for A* on grids\n#+OPTIONS: toc:nil author:t creator:nil num:nil\n#+AUTHOR: D. Chris Rayner\n#+EMAIL: dchrisrayner@gmail.com\n#+LATEX_HEADER: \\usepackage[parfill]{parskip}\n#+LATEX_HEADER: \\usepackage{comment}\n#+LATEX_HEADER: \\usepackage{color,hyperref}\n#+LATEX_HEADER: \\definecolor{darkblue}{rgb}{0.2,0.2,0.7}\n#+LATEX_HEADER: \\hypersetup{colorlinks,breaklinks,linkcolor=darkblue,urlcolor=darkblue,anchorcolor=darkblue,citecolor=darkblue}\n#+LATEX_HEADER: \\usepackage{textgreek}\n#+LATEX_CLASS: article\n#+LATEX_CLASS_OPTIONS: [koma,utopia,12pt,microtype,paralist]\n\n#+begin_export latex\n\\begin{comment}\n#+end_export\n[[https://github.com/riscy/a_star_on_grids/actions][https://github.com/riscy/a_star_on_grids/workflows/test/badge.svg]] [[https://github.com/riscy/a_star_on_grids/raw/master/pdf/a_star_on_grids.pdf][https://img.shields.io/badge/download-pdf-orange.svg]] [[https://img.shields.io/badge/version-20180602-blue.svg]]\n\n[[file:img/grid.png]]\n# http://www.veryicon.com/icons/system/icons8-metro-style/timeline-list-grid-grid.html\n#+begin_export latex\n\\end{comment}\n#+end_export\n\n* Table of Contents :TOC_3_gh:noexport:\n- [[#description][Description]]\n- [[#preliminaries][Preliminaries]]\n- [[#move-costs][Move costs]]\n  - [[#avoid-floating-point-arithmetic][Avoid floating point arithmetic]]\n  - [[#avoid-same-cost-diagonal-and-cardinal-moves][Avoid same-cost diagonal and cardinal moves]]\n  - [[#validate-your-move-costs][Validate your move costs]]\n  - [[#use-high-performing-move-costs][Use high-performing move costs]]\n- [[#heuristics][Heuristics]]\n  - [[#use-a-non-overestimating-heuristic][Use a non-overestimating heuristic]]\n  - [[#use-manhattan-distance-on-a-4-connected-grid][Use Manhattan distance on a 4-connected grid]]\n  - [[#use-octile-distance-on-an-8-connected-grid][Use octile distance on an 8-connected grid]]\n  - [[#scale-your-heuristics-up][Scale your heuristics up]]\n- [[#algorithmic-details][Algorithmic details]]\n  - [[#break-ties-in-favor-of-path-depth][Break ties in favor of path depth]]\n  - [[#avoid-recomputing-heuristics][Avoid recomputing heuristics]]\n  - [[#know-whether-to-use-a-heap][Know whether to use a heap]]\n  - [[#consider-fringe-search][Consider Fringe Search]]\n- [[#implementation][Implementation]]\n  - [[#maintain-two-pathfinders][Maintain two pathfinders]]\n  - [[#choose-the-right-language][Choose the right language]]\n  - [[#pack-your-data-structures][Pack your data structures]]\n- [[#additional-resources][Additional resources]]\n- [[#contributing-and-citing][Contributing and citing]]\n\n* Description\n  This document describes ways to improve an A* implementation, focusing on\n  pathfinding in four- and eight-connected grids.  It's pitched at hobbyists and\n  anyone looking for ways to make existing code a bit faster.\n\n  Some accompanying [[https://github.com/riscy/a_star_on_grids/tree/master/src][example code]] is available in C++.\n* Preliminaries\n  Forgoing a complete description, recall that A* is essentially a loop that\n  expands a list of /open/ states that reach toward a goal state.  Each\n  iteration of the A* loop expands the /open list/ with the neighbors of a state\n  already on the open list.  The open state ~i~ that gets chosen is one with the\n  lowest ~f~ value:\n  #+begin_src ruby\n  f_i = g_i + h_i\n  #+end_src\n  which is an estimate of the cost of a path going through ~i~ and continuing to\n  the goal.  Here ~g_i~ is the cost of the cheapest path to state ~i~ that A*\n  has generated so far, and ~h_i~ is an efficiently computed /heuristic\n  estimate/ for the cost to get from ~i~ to the goal.\n\n  (For further detail, visit the resources at the end of the document.)\n* Move costs\n   On an 8-connected grid, the cost of a single diagonal move (~D~) relative to\n   the cost of a cardinal move (~C~) not only affects the appearance of the\n   paths A* generates, but can also affect its efficiency.\n** Avoid floating point arithmetic\n   Prefer integral data types wherever possible.  This is not only faster but\n   helps to avoid the numerical imprecision that can confuse debugging attempts.\n** Avoid same-cost diagonal and cardinal moves\n   When the entity can move cardinally /or/ diagonally once per time-step, the\n   instinct is to tell A* that cardinal and diagonal moves cost the same (e.g.,\n   ~C = D = 1~).  While technically true, this increases the number of unique\n   optimal paths across the grid; A* is more efficient when it has fewer\n   options.\n\n   (Note if you're computing many paths at once via a shortest path tree, for\n   instance using Dijkstra's algorithm, then same-cost diagonal and cardinal\n   moves can be beneficial since you can just use a simple [[https://en.wikipedia.org/wiki/Breadth-first_search][BFS]].)\n** Validate your move costs\n   ~Ensure C \u003c D \u003c 2C~.  If a diagonal move costs /less/ than a cardinal move,\n   A* prefers zigzagging paths.  If a diagonal move costs more than /two/\n   cardinal moves, A* prefers rectilinear paths like you'd see on a 4-connected\n   grid.  Paths tend to look best when the costs lie between these two extremes.\n** Use high-performing move costs\n   The following cost structures work well in practice.  Results can vary\n   depending on the obstacles in the grid, so test before using.\n   - ~D = 99~, ~C = 70~ :: If you prefer a diagonal move to cost ~sqrt(2)~\n        relative to a cardinal move, try ~D = 99~ and ~C = 70~.  This close\n        approximation helps to avoid floating point arithmetic.\n   - ~D = 3~, ~C = 2~ :: This is still close to a ~D/C~ ratio of ~sqrt(2)~ and\n        remains integral.  Moreover, if ~h_i~ is admissible but non-integral\n        for whatever reason, then its [[https://en.wikipedia.org/wiki/Floor_and_ceiling_functions][ceiling]] is admissible and can be used\n        instead.  Nathan Sturtevant showed me this when we wrote [[http://www.aaai.org/ocs/index.php/AAAI/AAAI11/paper/viewFile/3594/3821][Euclidean\n        Heuristic Optimization]] (Rayner, Bowling, Sturtevant), and it made a\n        noticeable difference.\n   - ~D = 99~, ~C = 50~ :: This gives something close to rectilinear costs but\n        retains a preference for diagonal moves over pairs of cardinal moves.\n        On average this keeps the size of the open list smaller, but it can\n        also increase state expansions.  Usually it is noticeably faster.\n* Heuristics\n  Here are some tips on selecting a good heuristic.  I frequently see these\n  details missed on many first implementations of A*.\n** Use a non-overestimating heuristic\n   Heuristics that don't overestimate are called /admissible/.  A* recovers an\n   optimal (cheapest) path when its heuristic is admissible.  A good, admissible\n   grid heuristic is the \"distance\" between two states assuming no obstacles.\n** Use Manhattan distance on a 4-connected grid\n   The distance between two states on a 4-connected grid, assuming no\n   obstacles, is the *Manhattan* (or *L1-norm* or *rectilinear*) distance:\n   #+begin_src ruby\n   h_i = C * (Δx + Δy)\n   #+end_src\n   where ~Δx~ and ~Δy~ are absolute distances between ~i~ and the goal along\n   the ~x~ and ~y~ axes and ~C~ is the cost to take a cardinal move, which may\n   as well be ~1~.\n** Use octile distance on an 8-connected grid\n   When pathfinding on an 8-connected grid, use the *octile* heuristic:\n   #+begin_src ruby\n   h_i = C * Δx + B * Δy   if Δx \u003e Δy\n         C * Δy + B * Δx   else\n   #+end_src\n   where ~B = D - C~ with ~C~ being the cost to take a cardinal move and ~D~\n   being the cost to take a diagonal move.\n\n   Note the octile heuristic can be written without a conditional (albeit with\n   an absolute value), which may help improve instruction level parallelism:\n   #+begin_src ruby\n   h_i = (E * abs(Δx - Δy) + D * (Δx + Δy)) / 2\n   #+end_src\n   where ~E = 2 * C - D~.  You can see how this simplifies further, without\n   floating point arithmetic, if ~D~ (and therefore ~E~) is even.\n   # A proof for this relies on using a 45-degree rotation matrix to\n   # turn what is effectively a norm in Linfty into a norm in L1 space.\n\n   - See an [[https://github.com/riscy/a_star_on_grids/blob/master/src/heuristics.cpp#L59][example implementation of the octile heuristic]]\n   - See an [[https://github.com/riscy/a_star_on_grids/blob/master/src/heuristics.cpp#L67][example implementation of the non-branching octile heuristic]]\n** Scale your heuristics up\n   Once you've selected a good heuristic, try multiplying all of the values it\n   gives you by a constant ~K \u003e 1~ (e.g. ~10~).  This simple change yields an\n   algorithm called Weighted A*, which significantly improves run-time at the\n   cost of small suboptimalities in your paths.\n\n   See an [[https://github.com/riscy/a_star_on_grids/blob/master/src/heuristics.cpp#L74][example implementation of a weighted octile heuristic]].\n* Algorithmic details\n  Some details that tend not to come up in textbook descriptions of A*.\n** Break ties in favor of path depth\n   It is common for more than one state on the open list to have the lowest ~f~\n   cost.  When this is the case it's better to make A* focus on deep solutions\n   rather than a breadth of shallow solutions by tie-breaking on larger ~g~\n   values.  My Ph.D. co-supervisor Nathan Sturtevant created [[http://movingai.com/astar.html][a video demonstration]].\n\n   See [[https://github.com/riscy/a_star_on_grids/blob/master/src/node_heap.h#L9][example tiebreaking code]].\n** Avoid recomputing heuristics\n   To help keep the open list sorted, an implementation of A* might store the\n   ~f_i~ and ~g_i~ values for every open state ~i~.  And since ~f_i = g_i +\n   h_i~, the value of ~h_i~ can always be recovered as ~h_i = f_i - g_i~ for\n   any open state ~i~.  Using these stored values (a form of [[https://en.wikipedia.org/wiki/Memoization][memoization]]) can\n   be less expensive than recomputing ~h_i~.\n\n   For instance, suppose ~i~ is on the open list with ~f~ and ~g~ values of\n   ~f_current~ and ~g_current~.  Then A* iterates to a cheaper path to ~i~ with\n   a cost of ~g_new~.  The corresponding value ~f_new~ can be determined\n   /without/ making another call to the heuristic function:\n   #+begin_src ruby\n   f_new = g_new + f_current - g_current\n   #+end_src\n\n   See [[https://github.com/riscy/a_star_on_grids/blob/master/src/algorithms.cpp#L119][an example of using memoized heuristics]].\n** Know whether to use a heap\n   On larger grids with complex obstacles, implementing your open list as a\n   binary heap (preferably on top of an array) can lead to dramatic performance\n   gains.  This is why it's generally considered a best practice to do so.\n\n   But heaps can hurt.  On smaller grids with few obstacles, a linear scan of\n   the entire open list can be much faster, especially if your implementation is\n   written in a low-level language like C++.\n\n   - See an [[https://github.com/riscy/a_star_on_grids/blob/master/src/algorithms.cpp#L38][A* implementation that uses an array]]\n   - See an [[https://github.com/riscy/a_star_on_grids/blob/master/src/algorithms.cpp#L90][A* implementation that uses a heap]]\n   - See an [[https://github.com/riscy/a_star_on_grids/blob/master/src/node_heap.h][example heap implementation]]\n** Consider Fringe Search\n   [[https://en.wikipedia.org/wiki/Fringe_search][Fringe Search]] is a close cousin of A* that takes a different approach to\n   growing and maintaining the open list.  Just about all of the points in this\n   document apply to Fringe Search, such as choosing a good heuristic, the\n   choice of diagonal vs. cardinal move costs, and using memoized heuristic\n   values.\n\n   With compiler optimizations on, I found Fringe Search to be slower than A*,\n   albeit only if the methods in this document are applied.  But with compiler\n   optimizations off, Fringe Search can be faster than A*.  It's reasonable to\n   /predict/ Fringe Search may be the faster choice in interpreted scripting\n   languages.\n\n   See [[https://github.com/riscy/a_star_on_grids/blob/master/src/algorithms.cpp#L140][an example Fringe Search implementation]].\n* Implementation\n  The following are some tips on the actual implementation of your pathfinder.\n** Maintain two pathfinders\n   During development you'll be constantly changing and refactoring your code.\n   This can be dangerous -- it is surprisingly easy to write a pathfinder that\n   seems to work but has an invisible bug that isn't obvious until much later.\n\n   To prevent this you should write tested code: write a simple but /correct/\n   pathinder and use it to test your production pathfinder.  For example, if\n   you're finding optimal paths, both your simple pathfinder and your optimized\n   pathfinder should return solutions of the same length, even if they visit\n   different states.\n** Choose the right language\n   You'll get huge speed gains by writing your pathfinder in a compiled\n   system-level language like C, or C++, or Rust.\n\n   If you're using a high-level scripting language, you're not necessarily out\n   of luck.  If you're using Python, for example, you could look into compiling\n   your pathfinding module with [[http://cython.readthedocs.io/en/latest/src/tutorial/cython_tutorial.html][Cython]] -- it's surprisingly easy to do.\n** Pack your data structures\n   If you're coding in a low-level language like C, C++, or Rust, be aware of\n   the effects of structure packing -- /especially/ if you're using an explicit\n   graph to represent a large search space.\n\n   If you're using ~gcc~, for example, try giving your compiler the ~-Wpadded~\n   argument and see how much it whines about having to pad your data structures\n   with extra bytes.  Eric Raymond has a [[http://www.catb.org/esr/structure-packing/][great writeup]] on this topic.\n* Additional resources\n  - [[https://en.wikipedia.org/wiki/A*_search_algorithm][A* on Wikipedia]] :: Wikipedia gives a thorough description of A*.\n  - [[http://movingai.com][Nathan Sturtevant's movingai.com]] :: Benchmark problems, tutorials, and\n       videos covering fundamental and advanced topics.\n  - [[http://www.roguebasin.com/index.php?title=The_Incredible_Power_of_Dijkstra_Maps][Dijkstra Maps]] :: Dijkstra Maps have also been called \"differential\n       heuristics\", \"ALT heuristics\", or \"Lipschitz embeddings\".  We looked at\n       smart ways to set these heuristics up in [[https://webdocs.cs.ualberta.ca/~bowling/papers/13ijcai-hsubset.pdf][Subset Selection of Search\n       Heuristics]] (Rayner, Sturtevant, Bowling) but this article describes some\n       extremely novel ways to use these mappings to control game entities.\n  - [[http://theory.stanford.edu/~amitp/GameProgramming/Variations.html][Amit Patel's variants of A*]] :: A listing of some alternatives to A*.\n* Contributing and citing\n  If you have any corrections or contributions -- both much appreciated --\n  feel free to get in touch or simply make a pull request.\n\n  If for any reason you want to cite this document, use the following:\n  #+begin_src bibtex\n  @manual{Rayner2017BestPracticesGrids,\n      author = {D. Chris Rayner},\n      title = {{Best practices for A\\* on grids}},\n      year = 2018\n  }\n  #+end_src\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Friscy%2Fa_star_on_grids","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Friscy%2Fa_star_on_grids","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Friscy%2Fa_star_on_grids/lists"}