Clojure Maze Generation

As inspired by Maarten Metz:

(require '[clojure.string :as s])
(declare print-maze)


(defn create-grid [rows cols]
  (vec (repeat rows (vec (repeat cols #{})))))

(defn north-of [[row col]] [(dec row) col])
(defn south-of [[row col]] [(inc row) col])
(defn west-of  [[row col]] [row (dec col)])
(defn east-of  [[row col]] [row (inc col)])

(defn neighbours [grid cell]
  (filter #(get-in grid %) ((juxt north-of east-of south-of west-of) cell)))

(defn remove-border [grid c1 c2]
  (-> grid
      (update-in c1 conj c2)
      (update-in c2 conj c1)))

(defn find-unvisited-neighbours [grid cell]
  (let [n (neighbours grid cell)]
    (filter #(empty? (get-in grid %)) n)))

(defn generate-maze [rows cols]
  (loop [maze            (create-grid rows cols)
         backtrackstack '([0 0])]
    (if (empty? backtrackstack)
      (print-maze maze)
      (let [unvn (find-unvisited-neighbours maze (first backtrackstack))]
        (if (empty? unvn)
          (recur maze (rest backtrackstack))
          (let [next (rand-nth unvn)]
             (remove-border maze (first backtrackstack) next)
             (conj backtrackstack next))))))))


(defn east-open-border? [maze cell]
  (contains? (get-in maze (east-of cell)) cell))

(defn south-open-border? [maze cell]
  (contains? (get-in maze (south-of cell)) cell))

(defn print-cell-body [maze cell]
  (if (east-open-border? maze cell)
    "    "
    "   |"))

(defn print-cell-bottom [maze cell]
  (if (south-open-border? maze cell)
    "   +"

(defn print-maze [maze]
  (let [result (atom [])
        rows   (range (count maze))
        cols   (range (count (get-in maze [0])))]
    (swap! result conj "+" (repeat (count cols) "---+") "\n")
    (doseq [row rows]
      (swap! result conj "|")
      (doseq [col cols]
        (swap! result conj (print-cell-body maze [row col])))
      (swap! result conj "\n" "+")
      (doseq [col cols]
        (swap! result conj (print-cell-bottom maze [row col])))
      (swap! result conj "\n"))
    (println (s/join (flatten @result)))))
(def m (with-out-str (-> (generate-maze 10 15) print-maze)))

(println m)

We can also write it to a file which will get stored in content addressed storage, so folks can download it:

(spit "results/maze.txt" m)

We can also render it into an image directly. We just have to install some fonts first.

apt-get update
apt-get install fonts-cantarell
(import java.awt.Color)
(import java.awt.Font)
(import java.awt.image.BufferedImage)
(import javax.imageio.ImageIO)

(let [bi (BufferedImage. 600 300 BufferedImage/TYPE_INT_ARGB)
      g (.createGraphics bi)]
  (.setColor g Color/BLACK)
  (loop [lines (clojure.string/split-lines m)
         y 10]
    (.setFont g (Font. "Courier" Font/PLAIN 10))
    (when-let [l (first lines)]
      (.drawString g l 0 y)
      (recur (rest lines) (+ y 10))))
  (ImageIO/write bi "png" ( "results/maze.png")))
© 2018 Nextjournal GmbH