{"id":13813410,"url":"https://github.com/defndaines/meiro","last_synced_at":"2025-05-15T00:33:05.734Z","repository":{"id":146025647,"uuid":"89084384","full_name":"defndaines/meiro","owner":"defndaines","description":"Maze generation code, inspired by Mazes for Programmers.","archived":false,"fork":false,"pushed_at":"2020-07-04T23:15:41.000Z","size":314,"stargazers_count":453,"open_issues_count":1,"forks_count":19,"subscribers_count":10,"default_branch":"main","last_synced_at":"2024-08-04T04:04:02.191Z","etag":null,"topics":["clojure","maze","maze-algorithms"],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/defndaines.png","metadata":{"files":{"readme":"README.md","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":"2017-04-22T16:41:20.000Z","updated_at":"2024-08-04T04:04:06.155Z","dependencies_parsed_at":null,"dependency_job_id":"fac7a054-a213-410a-9872-8f4d258a7278","html_url":"https://github.com/defndaines/meiro","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/defndaines%2Fmeiro","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/defndaines%2Fmeiro/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/defndaines%2Fmeiro/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/defndaines%2Fmeiro/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/defndaines","download_url":"https://codeload.github.com/defndaines/meiro/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225319220,"owners_count":17455727,"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":["clojure","maze","maze-algorithms"],"created_at":"2024-08-04T04:01:16.819Z","updated_at":"2024-11-19T08:30:32.243Z","avatar_url":"https://github.com/defndaines.png","language":"Clojure","funding_links":[],"categories":["Clojure"],"sub_categories":[],"readme":"# Meiro 迷路\n\nMaze generation code, inspired by working through [Mazes for\nProgrammers](https://pragprog.com/book/jbmaze/mazes-for-programmers).\nBecause the book leans on Object Oriented design (coded in Ruby), much of this\nis a re-thinking of the approaches in a Clojure style.\n\nEach maze generation algorithm is in its own namespace.\n\nExcept where otherwise noted, all algorithms produce \"perfect\" mazes. Perfect\nmazes have exactly one path between any two cells in the maze. This also means\nthat you designate any two cells as the start and end and guarantee that there\nis a solution.\n\n[![Dependencies Status](https://versions.deps.co/defndaines/meiro/status.svg)](https://versions.deps.co/defndaines/meiro)\n\n| [Usage](#usage)\n| [Algorithms](#algorithms)\n| [Solutions](#solutions)\n| [Utilities](#utilities)\n| [Presentation](#presentation)\n\n\n## Usage\n\nProject is pretty much complete and does not have functions exposed for\nexternal use (such as through a command-line executable JAR file). All the\nexamples below assume that you are importing into a REPL for execution.\n\n\n### Displaying Mazes\n\nThere are several ways to display a maze. The primary data structure used here\nto store a maze is a vector of vectors, where each cell indicates which\ndirections you can navigate out of the cell to. Each of these cells is\nposition-aware, with cells accessed by `[row column]`.\n\nHere is a 5x5 maze:\n```clojure\n(def maze\n [[[:east] [:south :west :east] [:west :east] [:west :south] [:south]]\n [[:east :south] [:east :north :west] [:south :west] [:north :east] [:west :north]]\n [[:north :east] [:west] [:south :north :east] [:west] [:south]]\n [[:south] [:south] [:south :north :east] [:west :east] [:west :north :south]]\n [[:east :north] [:north :west :east] [:west :north] [:east] [:north :west]]])\n```\n\nThe easiest way to visualize a maze at the REPL is to generate an ASCII\nversion:\n```clojure\nuser=\u003e (require '[meiro.ascii :as ascii])\nnil\nuser=\u003e (print (ascii/render maze))\n+---+---+---+---+---+\n|               |   |\n+---+   +---+   +   +\n|           |       |\n+   +---+   +---+---+\n|       |       |   |\n+---+---+   +---+   +\n|   |   |           |\n+   +   +   +---+   +\n|           |       |\n+---+---+---+---+---+\nnil\n```\n\nAnd if you want to print or share a maze, it can be output as a PNG:\n```clojure\n(require '[meiro.png :as png])\n(require '[meiro.core :as m])\n(require '[meiro.sidewinder :as sw])\n(png/render (sw/create (m/init 15 20)) \"sample-maze.png\")\n```\nWhich creates a PNG file like:\n\n![Sample Maze](img/sample-maze.png)\n\nTo print a maze with masked cells:\n```clojure\n(def grid (ascii/read-grid \"test/meiro/template.txt\"))\n(require '[meiro.backtracker :as b])\n(png/render-masked (b/create grid))\n```\n\n![Masked Maze](img/masked-maze.png)\n\nTo print a circular (polar) maze:\n```clojure\n(require '[meiro.polar :as polar])\n(png/render-polar\n  (b/create (polar/init 10) [0 0] polar/neighbors polar/link))\n```\n\n![Polar Maze](img/polar-maze.png)\n\nTo print a sigma (hex) maze:\n```clojure\n(require '[meiro.hex :as hex])\n(png/render-hex\n  (b/create (m/init 15 20) [7 9] hex/neighbors hex/link))\n```\n\n![Sigma Maze](img/sigma-maze.png)\n\nTo print a delta (triangle) maze:\n```clojure\n(require '[meiro.triangle :as triangle])\n(def grid (ascii/read-grid \"test/meiro/triangle.txt\"))\n(png/render-delta\n  (b/create grid [0 12] triangle/neighbors m/link))\n```\n\n![Delta Maze](img/delta-maze.png)\n\nTo print a maze with an inset:\n```clojure\n(png/render-inset (b/create (m/init 8 25)) 3)\n```\n\n![Inset Maze](img/inset-maze.png)\n\nTo print a maze composed of edges, the image must be bored out of a background\nimage. Use the following:\n```clojure\n(require '[meiro.prim :as prim])\n(def forest (prim/create 25 8))\n(png/render-forest forest)\n```\n\n![Bore Maze](img/bore-maze.png)\n\nIf you want to print an ASCII maze as if it were a series of corridors in\nNetHack:\n```clojure\n(require '[meiro.nethack :as nethack])\n(print (nethack/render-corridor maze))\n\n####### # ####### ####### ####### #\n#     # # # #   # #     # #     # #\n### # # ### ### # ### ##### # ### #\n  # # #       # #   #       # #   #\n### # ### ### # ### ####### ### ###\n#   #   # # # #   #       # #     #\n# # ### # # ### # ### ##### ##### #\n# # #   # #     #   # #         # #\n# # # ### ### ##### # ### ##### # #\n# # # #     # #   # #   # # #   # #\n### ######### ### ####### # #######\n\n```\n\nIf you want to print an ASCII maze as if it were a situated in a\nNetHack room (corners could use some work):\n```clojure\n(print (nethack/render-room maze))\n-------------------------------------\n|.......|.|.......|.......|.......|.|\n|.-----.|.|.-.---.|.-----.|.-----.|.|\n|...|.|.|...|...|.|...|.....|.|...|.|\n|--.|.|.|------.|.|--.|------.|.---.|\n|...|.|...|...|.|...|.......|...|...|\n|.---.|--.|.-.|.|--.|------.|.-----.|\n|.|.|...|.|.|...|.|...|.....|.....|.|\n|.|.|.---.|.|----.|--.|.---------.|.|\n|.|.|.|...|...|.....|.|...|.....|.|.|\n|.|.|.|.-----.|.---.|.|--.|.-.---.|.|\n|...|.........|...|.......|.|.......|\n|------------------------------------\n```\n\n\n## Algorithms\n\nThere are a number of different algorithms for generating mazes.\n\n\n### Binary Tree\n\nBinary Tree produces mazes by visiting each cell in a grid and opening a\npassage either south or east. This causes a bias toward paths which flow down\nand to the right. They will always have a single corridor along both the\nsouthern and eastern edges.\n\nIf you wish to generate and print a random binary-tree maze, you can start up a\nREPL and try to following:\n```clojure\n(require '[meiro.core :as m])\n(require '[meiro.ascii :as ascii])\n(require '[meiro.binary-tree :as bt])\n(png/render (bt/create (m/init 8 25)))\n```\n\nWhich will produce a maze like:\n\n![Binary Tree Maze](img/binary-tree-maze.png)\n\n\n### Sidewinder\n\nSidewinder is based upon Binary Tree, but when it navigates south, it chooses a\nrandom cell from the current horizontal corridor and generates the link from\nthere. The mazes will still flow vertically, but not to the right as with Binary\nTree. All mazes will have a single horizontal corridor along the southern edge.\n\nTo generate a maze using the sidewinder algorithm:\n```clojure\n(require '[meiro.sidewinder :as sw])\n(png/render (sw/create (m/init 8 25)))\n```\n\nWhich will produce a maze like:\n\n![Sidewinder Maze](img/sidewinder-maze.png)\n\nBecause Sidewinder creates a maze one row at a time, it is possible to create\ninfinite mazes. The mazes won't be perfect mazes unless completed, though.\nThese mazes only link south or east, so you'll only be able to use certain\nrender functions, like `ascii/render` and `png/render`, which are already\noptimized to only render the east and south walls per cell. Optional weights can\nbe passed to the function.\n\n```clojure\n(def infini-maze (sw/create-lazy 25 {:south 2 :east 5}))\n(def maze (conj (vec (take 7 infini-maze)) (sw/last-row 25)))\n(png/render maze)\n```\n\nWhich will produce a maze like:\n\n![Infinite Sidewinder Maze](img/infinite-sidewinder-maze.png)\n\n\n### Aldous-Broder\n\nAldous-Broder picks a random cell in the grid and the moves randomly. If it\nvisits a cell which has not been visited before, it links it to the previous\ncell. The algorithm ends when all cells have been visited.\n\nBecause movement is random, it can take a long time for this algorithm to\nfinish. Because movement is completely random, the generated maze has no bias.\n\nTo generate a random-walk maze using Aldous-Broder:\n```clojure\n(require '[meiro.aldous-broder :as ab])\n(png/render (ab/create (m/init 8 25)))\n```\n\nWhich will produce a maze like:\n\n![Aldous-Broder Maze](img/aldous-broder-maze.png)\n\n\n### Wilson's\n\nWilson's starts at a random cell and then does a random walk. When it introduces\na loop by coming back to a visited cell, it erases the loop then continues the\nrandom walk from that point. The algorithm starts slowly, but produces a\ncompletely unbiased maze.\n\nTo generate a loop-erasing, random-walk maze:\n```clojure\n(require '[meiro.wilson :as w])\n(png/render (w/create (m/init 8 25)))\n```\n\nWhich will produce a maze like:\n\n![Wilson's Maze](img/wilsons-maze.png)\n\n\n### Hunt-and-Kill\n\nHunt-and-Kill performs a random walk, but avoids visiting cells which are\nalready linked. When it reaches a dead end but there are still cells to visit,\nit will look for an unvisited cell neighboring a visited cell and begin walking\nagain from there.\n\nHunt-and-kill mazes tend to have long, twisty passages with fewer dead ends than\nmost of the algorithms here. It can be slower because it can visit cells many\ntimes.\n\nTo generate a random-walk maze biased to the first visited cell using\nHunt-and-Kill:\n```clojure\n(require '[meiro.hunt-and-kill :as hk])\n(png/render (hk/create (m/init 8 25)))\n```\n\nWhich will produce a maze like:\n\n![Hunt and Kill Maze](img/hunt-and-kill-maze.png)\n\n\n### Recursive Backtracker\n\nRecursive Backtracker uses a random-walk algorithm. When it encounters a dead\nend, it backtracks to the last unvisited cell and resumes the random walk from\nthat position. It completes when it backtracks to the starting cell. Resulting\nmazes have long, twisty passages and fewer dead ends. It should be faster than\nhunt-and-kill, but has to maintain the stack of all visited cells.\n\nTo generate a random-walk maze biased to the last unvisited cell on the path\nusing Recursive Backtracker:\n```clojure\n(require '[meiro.backtracker :as b])\n(png/render (b/create (m/init 8 25)))\n```\n\nWhich will produce a maze like:\n\n![Recursive Backtracker Maze](img/backtracker-maze.png)\n\n\n### Kruskal's\n\nKruskal's algorithm is focused on generating a minimum spanning tree. I decided\nto use a more graph-centric approach, so the `create` function returns a\n\"forest\", a map which includes the nodes and edges. It uses `x, y` coordinates,\nso is \"backward\" from the other algorithms to this point.\n\nThe algorithm assigns every cell to a distinct forest, and then merges forests\none at a time until there is only one forest remaining.\n\nThe `png/render-forest` function will render a forest directly, or the results\ncan be converted to the standard, grid-style maze using `graph/forest-to-maze`\nbefore passing to other `png` functions.\n\n```clojure\n(require '[meiro.kruskal :as k])\n(require '[meiro.graph :as graph])\n(def forest (k/create 25 8))\n(def maze (graph/forest-to-maze forest))\n(png/render maze)\n```\n\nWhich will produce a maze like:\n\n![Kruskal's Maze](img/kruskal-maze.png)\n\n\n### Prim's\n\nPrim's algorithm generates a minimum spanning tree by starting with a position\nand adding the \"cheapest\" edge available. Weights are assigned randomly to\nensure a less biased maze. Like Kruskal's, the approach is graph-centric and\n`create` returns a collection of edges. The implementation here is a \"True\nPrim's\" approach, using weighted edges. (There are other versions possible, like\nSimplified Prim's, which produce more biased mazes.)\n\n```clojure\n(require '[meiro.prim :as prim])\n(require '[meiro.graph :as graph])\n(def forest (prim/create 25 8))\n(def maze (graph/forest-to-maze forest))\n(png/render maze)\n```\n\nWhich will produce a maze like:\n\n![Prim's Maze](img/prim-maze.png)\n\n\n### Growing Tree\n\nThe Growing Tree algorithm is an abstraction over the approach in Prim's\nalgorithm.\nIt needs to be passed a `queue` which holds the active edges of the growing tree\n(forest), a `poll-fn` which removes an edge from the `queue`, and a `shift-fn`\nwhich transfers the edges of a newly added node from the set of remaining,\nunexplored edges to the `queue`.\n\nThe bias of this algorithm will depend on how edges are added to and removed\nfrom the queue\n\nTo implement Prim's algorithm using Growing Tree:\n```clojure\n(require '[meiro.growing-tree :as grow])\n(require '[meiro.prim :as prim])\n(def forest (grow/create 25 8\n                         (java.util.PriorityQueue.)\n                         prim/poll\n                         prim/to-active!))\n(def maze (graph/forest-to-maze forest))\n(png/render maze)\n```\n\nWhich will produce a maze like:\n\n![Growing Prim's Maze](img/growing-prim-maze.png)\n\nBut, Growing Tree can also be used to implement Recursive Backtracker.\n_Note: If you do not shuffle the new edges, the resulting \"maze\" will mostly be a\nseries of connected corridors._\n```clojure\n(require '[meiro.growing-tree :as grow])\n\n(defn back-poll\n  [q]\n  [(first q) (rest q)])\n\n(defn back-shift\n  [new-edges queue remaining-edges]\n  (reduce\n    (fn [[q es] e]\n      (let [remaining (disj es e)]\n        (if (= es remaining)\n          [q es]\n          [(conj q e)\n           remaining])))\n    [queue remaining-edges]\n    (shuffle new-edges)))\n\n(def forest (grow/create 25 8 '() back-poll back-shift))\n(def maze (graph/forest-to-maze forest))\n(png/render maze)\n```\n\nWhich will produce a maze like:\n\n![Growing Recursive Backtracker Maze](img/growing-backtracker-maze.png)\n\n\n### Eller's\n\nEller's algorithm processes a row at a time, creating forests as it goes. It\nalso behaves like Sidewinder, in that it will connect to the next row from one\nrandom position in a horizontal corridor. When a forest is orphaned, because it\ndoes not have a link to the next row, then it is merged with an adjacent forest.\nWhen the last row is reached, all forests are merged. Note that when forests are\nmerged, they can be linked at any two adjacent nodes (i.e., not necessarily the\nsouthernmost cell).\n\nTo create a maze using Eller's:\n```clojure\n(require '[meiro.eller :as eller])\n(def forest (eller/create 25 8))\n(png/render (graph/forest-to-maze forest))\n```\n\nWhich will produce a maze like:\n\n![Eller's Maze](img/eller-maze.png)\n\n\n### Recursive Division\n\nThe Recursive Division algorithm generates fractal mazes and is distinct among\nall the algorithms here in that is adds walls instead of carving passages.\n\nTo create a maze using Recursive Division:\n```clojure\n(require '[meiro.division :as division])\n(def maze (division/create (m/init 8 25)))\n(png/render maze)\n```\n\nWhich will produce a maze like:\n\n![Recursive Division Maze](img/division-maze.png)\n\nRecursive Division also enables the creation of rooms inside the maze. Do this\nby passing a maximum room size and a creation rate (a percentage of the time\nwhen the subdivision will stop when height and width are below the size).\n```clojure\n(def maze (division/create (m/init 8 25) 4 0.4))\n(png/render maze)\n```\n\nWhich will produce a maze like:\n\n![Recursive Division with Rooms Maze](img/division-room-maze.png)\n\n\n## Solutions\n\nTo calculate the distance from the north-east cell to each cell using Dijkstra's\nalgorithm:\n```clojure\n(require '[meiro.dijkstra :as d])\n(def maze (sw/create (m/init 8 8)))\n(def dist (d/distances maze))\n(print (ascii/render maze (ascii/show-distance dist)))\n```\n\nWhich will produce a maze like:\n```\n+---+---+---+---+---+---+---+---+\n| 0   1 | 4 | n | q   p | o   n |\n+   +---+   +   +---+   +---+   +\n| 1   2   3 | m   l | o   n | m |\n+   +---+---+---+   +---+   +   +\n| 2   3 | m   l | k   l | m | l |\n+---+   +---+   +   +---+   +   +\n| 5   4 | l   k   j   k | l | k |\n+   +---+---+---+   +---+   +   +\n| 6 | 9 | k   j   i | h | k   j |\n+   +   +---+---+   +   +---+   +\n| 7   8   9   a | h   g | j | i |\n+---+---+   +---+---+   +   +   +\n| g   f | a   b | g   f | i   h |\n+---+   +---+   +---+   +---+   +\n| f   e   d   c   d   e   f   g |\n+---+---+---+---+---+---+---+---+\n```\n\nTo calculate and show a solution:\n```clojure\n(def maze (b/create (m/init 8 25)))\n(def sol (d/solution maze [0 0] [0 24]))\n(print (ascii/render maze (ascii/show-solution sol)))\n```\n\nWhich will produce a maze like:\n```\n+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n| *     |           | *   * | *   *                 | *   *   *   *   *   * |         *   * | *   * |\n+   +   +   +   +---+   +   +   +   +---+---+---+   +   +---+   +---+---+   +---+---+   +   +   +   +\n| * |       |   | *   * | *   * | *   * |           | * |   |   |       | * | *   * | * | *   * |   |\n+   +---+---+---+   +---+---+---+---+   +---+---+---+   +   +   +   +   +   +   +   +   +---+---+   +\n| *   * | *   * | * | *   * |       | * | *   *   *   * |   |   |   |   | *   * | *   * |   |       |\n+---+   +   +   +   +   +   +   +   +   +   +---+---+---+   +   +   +   +---+---+---+---+   +   +---+\n| *   * | * | * | *   * | *   * |   | * | * |               |       |   |               |   |       |\n+   +---+   +   +---+---+---+   +---+   +   +---+---+   +   +---+---+   +   +---+---+   +   +---+   +\n| *   *   * | *     | *   *   * | *   * | * | *   * |   |           |       |   |       |       |   |\n+---+---+---+   +   +   +---+---+   +   +   +   +   +---+---+   +   +---+---+   +   +---+---+   +   +\n| *   *   *   * |   | *   *     | * |   | * | * | *   *   * |   |               |           |       |\n+   +---+---+   +---+---+   +---+   +   +   +   +---+---+   +---+---+---+---+   +---+---+   +   +---+\n| *   *   * |   | *   * | * | *   * |   | *   * | *   * | *   *   *   *   *   *   * |   |   |   |   |\n+   +---+   +---+   +   +   +   +---+---+---+---+   +   +---+---+---+---+---+---+   +   +   +   +   +\n|       | *   *   * | *   * | *   *   *   *   *   * | *   *   *   *   *   *   *   * |       |       |\n+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n```\n\n\n## Utilities\n\nThere are a few additional utilities besides deriving solutions.\n\n\n### Longest Path\n\nDijkstra's distances calculation can be used to find the position furthest from\na given start point. If none is provided, it will assume the upper left-hand\ncorner position.\n```clojure\n(d/farthest-pos maze)\n\n[2 20]\n```\n\nBy running this algorithm twice, the second time with the output of the first\nrun, you can determine the longest path in a maze. This can be useful if you are\nlooking to determine start and end points. This function returns a path with\nall the positions.\n```clojure\n(d/longest-path maze)\n\n([6 20] [6 19] [7 19] [7 20] [7 21] [6 21] [5 21] [5 20] [5 19] [5 18] [6 18]\n [7 18] [7 17] [6 17] [6 16] [7 16] [7 15] [6 15] [6 14] [7 14] [7 13] [6 13]\n [5 13] [5 14] [5 15] [4 15] [4 16] [3 16] [2 16] [1 16] [1 15] [2 15] [2 14]\n [2 13] [3 13] [3 14] [4 14] [4 13] [4 12] [4 11] [5 11] [5 12] [6 12] [7 12]\n [7 11] [6 11] [6 10] [7 10] [7 9] [6 9] [6 8] [7 8] [7 7] [7 6] [7 5] [7 4]\n [7 3] [7 2] [7 1] [7 0] [6 0] [5 0] [4 0] [3 0] [2 0] [2 1] [1 1] [1 0] [0 0]\n [0 1] [0 2] [1 2] [1 3] [1 4] [1 5] [1 6] [1 7] [0 7] [0 8] [1 8] [1 9] [2 9]\n [2 8] [2 7] [2 6] [3 6] [4 6] [4 5] [4 4] [5 4] [5 3] [4 3] [3 3] [3 2] [3 1]\n [4 1] [5 1] [6 1] [6 2] [6 3] [6 4] [6 5] [5 5] [5 6] [5 7] [4 7] [4 8] [4 9]\n [5 9] [5 10] [4 10] [3 10] [3 11] [3 12] [2 12] [1 12] [1 13] [0 13] [0 14]\n [0 15][0 16] [0 17] [0 18] [1 18] [2 18] [2 19] [3 19] [3 20] [4 20] [4 21]\n [3 21] [2 21] [1 21] [0 21] [0 20] [0 19] [1 19] [1 20] [2 20])\n```\n\n\n### Braid\n\nBy default, the algorithms produce \"perfect\" mazes, i.e., every position in the\ngrid has one path to any other position in the grid. This inevitably produces\ndead ends. \"Braiding\" is the act of removing dead ends from a maze by linking\nthem with neighbors.\n\nTo enumerate the dead ends in a maze:\n```clojure\n(def maze (b/create (m/init 8 22)))\n(m/dead-ends maze)\n\n([0 10] [0 16] [1 1] [1 21] [2 5] [2 13] [3 0] [3 7] [4 2] [4 13] [4 15] [5 3]\n [5 10] [6 1] [6 15] [6 19] [7 11] [7 21])\n```\n\nYou can remove all dead ends with the `braid` function.\n```clojure\n(m/braid maze)\n```\n\n![Fully Braided Maze](img/fully-braided-maze.png)\n\nIf you don't want to remove all dead ends, you can pass in a rate which will\ndetermine what percentage of the dead ends should be removed (randomly).\n```clojure\n(def braided (m/braid maze 0.4))\n(png/render braided)\n```\n\n![Braided Maze](img/braided-maze.png)\n\n\n### Cull Dead Ends\n\nWhereas braiding eliminates dead ends by connecting them to neighbors, it is\nalso possible to `cull` dead ends, creating a sparse maze. A maze can be culled\nmultiple times to remove more ends. Culled cells will be marked as masked, so\nyou will need to use a rendering function which handles this sensibly.\nCulled mazes will remain perfect mazes.\n```clojure\n(require '[meiro.hunt-and-kill :as hk])\n(def maze (hk/create (m/init 8 22)))\n(png/render-inset (m/cull (m/cull maze 0.6) 0.6) 3)\n```\n\n![Culled Maze](img/culled-maze.png)\n\n\n### Weave\n\nA weave maze can connect to non-adjacent cells provided certain conditions are\nmet.\n- Passages cannot dead end while underneath another cell.\n- Passages must be perpendicular, one north-south, one east-west.\n- Passages cannot change direction while traveling under other passages.\n\nA weave maze will need to be rendered using \"inset\", otherwise it won't be\npossible to visually identify the under passages.\n\n```clojure\n(require '[meiro.weave :as weave])\n(def maze (b/create (m/init 8 25) [0 0] weave/neighbors weave/link))\n(png/render-inset maze 2)\n```\n\n![Weave Maze](img/weave-maze.png)\n\nKruskal's is set up to allow weave to be injected into a maze. This is done by\npre-seeding the algorithm with cells already combined, and then letting the maze\nbuild around it. In order to render a weave maze, it has to be converted to the\nstandard grid format.\n\n```clojure\n(require '[meiro.kruskal :as k])\n(require '[meiro.graph :as graph])\n(def forests (graph/init-forests 25 8))\n(def seeded (reduce k/weave forests\n  (for [x (range 1 25 2) y (range 1 8 2)] [x y])))\n(def forest (k/create 25 8 seeded))\n(def maze (graph/forest-to-maze forest))\n(png/render-inset maze 2)\n```\n\n![Kruskal's Weave Maze](img/kruskal-weave-maze.png)\n\n\n### Three-dimensional Mazes\n\nThe `grid-3d` namespace can be used to generate three-dimensional mazes.\nThe example below takes advantage of the controls which can be passed to the\n`create` function to favor spreading out on a level before ascending or\ndescending.\n\n```clojure\n(require '[meiro.grid-3d :as grid-3d])\n(def grid (grid-3d/init 3 4 5))\n\n(def link-3d (m/link-with grid-3d/direction))\n\n(defn select-fn\n  \"Favor selecting neighbors on the same level.\"\n  [neighbors]\n  (let [n (count neighbors)]\n    (if (and (\u003c 2 n) (\u003c 0.1 (rand)))\n      (rand-nth (take (- n 2) (rest (sort neighbors))))\n      (rand-nth neighbors))))\n\n(def maze (b/create grid\n                    (grid-3d/random-pos grid)\n                    grid-3d/neighbors link-3d select-fn))\n\n(png/render-3d maze)\n```\n\n![3D Maze](img/3d-maze.png)\n\n\n### Wrapping Mazes\n\nSometimes you may want to have maze wrap around, meaning if you step off the\nleft edge of the maze, it re-enters on the right edge. You could use this to\ncreate a maze on a cylinder. You could use this approach to create a Pac-Man\nstyle maze as well.\n\nThis will only wrap along the vertical walls:\n```clojure\n(require '[meiro.wrap :as wrap])\n(def maze (b/create (m/init 8 25) [3 13]\n                    wrap/neighbors-horizontal\n                    wrap/link))\n(png/render-inset maze 2)\n```\n\n![Horizontal Wrap Maze](img/horizontal-wrap-maze.png)\n\nTo wrap off any direction:\n```clojure\n(def maze (b/create (m/init 8 25) [3 13] wrap/neighbors wrap/link))\n(png/render-inset maze 2)\n```\n\n![Wrap Maze](img/wrap-maze.png)\n\n\n## Presentation\n\nI did a presentation on this code at Pivotal in January 2018. It is available\non YouTube here:\n[Maze Generation Algorithms in Clojure – Michael Daines](https://www.youtube.com/watch?v=eUHUX7E2OLk)\n\n\n## License\n\nCopyright © 2017–2020 Michael S. Daines\n\nDistributed under the Eclipse Public License either version 1.0 or (at\nyour option) any later version.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdefndaines%2Fmeiro","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdefndaines%2Fmeiro","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdefndaines%2Fmeiro/lists"}