Clojure——學習迷宮生成

背景

初學clojure,想着看一些算法來熟悉clojure語法及相關算法實現。
找到一個各類語言生成迷宮的網站:http://rosettacode.org/wiki/Maze_generation
在上述網站能夠看到clojure的實現版,本文就是以初學者的視角解讀改程序。javascript

小試牛刀

先看一些簡單的示例,以幫助咱們理解迷宮生成程序。html

綁定符號x++

(defn f [x]
  (let [x++ (+ x 5)]
    #{[x x++]}))
(println (f 1))
=> #'sinlov.clojure.base-learn/f
#{[1 6]}
=> nil

Tips: 上述程序將x++綁定爲x+5,不一樣於c語言中的自增運算符。java

集合過濾

(select odd? (set [1 2 3 4 5]))
=> #{1 3 5}
(select (partial odd?) (set [1 2 3 4 5]))
=> #{1 3 5}

select語法參考文檔:http://clojuredocs.org/clojure.set/select
partial解釋見下文算法

vec交叉合併

(interleave [0 1 2] ['a 'b 'c])
=> (0 a 1 b 2 c)
(interleave [0 1 2] ['a 'b 'c] ['b])
=> (0 a b)
(interleave [0 1 2] ['a 'b 'c] (repeat 'z))
=> (0 a z 1 b z 2 c z)

文檔:http://clojuredocs.org/clojure.core/interleave編程

transduce

transducer是clojure裏面的一種編程思想,使用transducer能夠簡化不少語法。
能夠參考這篇文章連接,幫助理解
文檔:http://clojuredocs.org/clojure.core/transducedom

思路

筆者閱讀了迷宮生成算法,將思路整理以下oop

座標點與符號映射關係

好比迷宮的左上角是如何生成的,不一樣大小的迷宮如何肯定?
通過閱讀源碼發現,一個座標點的符號與其周圍4個臨接點相關,若是按照座標點表示,5個點排序順序是一致的。
網站

好比,上述座標點(5,5),和其4個臨界點。能夠看到在該座標系內,一個點與其臨界點作成的集合排序必定是下面的順序:
ui

好比迷宮左上角座標是(0, 0),該點五元組應該是3d

不在迷宮,不在迷宮,(0, 0), (0, 1), (1, 0)

假設不在迷宮或者該位置爲空,記爲0;若是是牆記爲1
那麼上述五元組能夠換算爲11100

再好比迷宮右上角,五元組爲

(n-1, 0), 不在迷宮, (n, 0), (n, 1), 不在迷宮

可換算爲10110

按照如上規則能夠生成以下表:

["  " "  " "  " "  " "· " "╵ " "╴ " "┘ "
"  " "  " "  " "  " "╶─" "└─" "──" "┴─"
"  " "  " "  " "  " "╷ " "│ " "┐ " "┤ "
"  " "  " "  " "  " "┌─" "├─" "┬─" "┼─"]

程序代碼

(ns maze.core
  (:require [clojure.set :refer [intersection
                                 select]]
            [clojure.string :as str]))

;; 獲得周圍臨界點
(defn neighborhood
  ([] (neighborhood [0 0]))
  ([coord] (neighborhood coord 1))
  ([[y x] r]
   (let [y-- (- y r) y++ (+ y r)
         x-- (- x r) x++ (+ x r)]
     #{[y++ x] [y-- x] [y x--] [y x++]})))

;; 判斷位置是否爲空
(defn cell-empty? [maze coords]
  (= :empty (get-in maze coords)))

;; 判斷位置是否爲牆
(defn wall? [maze coords]
  (= :wall (get-in maze coords)))

;; 過濾迷宮中指定類型的點的集合
(defn filter-maze
  ([pred maze coords]
   (select (partial pred maze) (set coords)))
  ([pred maze]
   (filter-maze
     pred
     maze
     (for [y (range (count maze))
           x (range (count (nth maze y)))]
       [y x]))))

;; 建立新迷宮
(defn create-empty-maze [width height]
  (let [width (inc (* 2 width))
        height (inc (* 2 height))]
    (vec (take height
               (interleave
                 (repeat (vec (take width (repeat :wall))))
                 (repeat (vec (take width (cycle [:wall :empty])))))))))

(defn next-step [possible-steps]
  (rand-nth (vec possible-steps)))

;; 核心算法,深度優先遞歸
(defn create-random-maze [width height]
  (loop [maze (create-empty-maze width height)
         stack []
         nonvisited (filter-maze cell-empty? maze)
         visited #{}
         coords (next-step nonvisited)]
    (if (empty? nonvisited)
      maze
      (let [nonvisited-neighbors (intersection (neighborhood coords 2) nonvisited)]
        (cond
          (seq nonvisited-neighbors)
          (let [next-coords (next-step nonvisited-neighbors)
                wall-coords (map #(+ %1 (/ (- %2 %1) 2)) coords next-coords)]
            (recur (assoc-in maze wall-coords :empty)
                   (conj stack coords)
                   (disj nonvisited next-coords)
                   (conj visited next-coords)
                   next-coords))

          (seq stack)
          (recur maze (pop stack) nonvisited visited (last stack)))))))

;; 迷宮座標與字符映射
(def cell-code->str
  ["  " "  " "  " "  " "· " "╵ " "╴ " "┘ "
   "  " "  " "  " "  " "╶─" "└─" "──" "┴─"
   "  " "  " "  " "  " "╷ " "│ " "┐ " "┤ "
   "  " "  " "  " "  " "┌─" "├─" "┬─" "┼─"])

;; 獲取迷宮座標的類型
;; 使用5 bit表示一個點對應的字符映射
;; 例如:00111對應┘ 
(defn cell-code [maze coord]
  (transduce
    (comp
      (map (partial wall? maze))
      (keep-indexed (fn [idx el] (when el idx)))
      (map (partial bit-shift-left 1)))
    (completing bit-or)
    0
    (sort (cons coord (neighborhood coord)))))

(defn cell->str [maze coord]
  (get cell-code->str (cell-code maze coord)))

;; 將迷宮座標轉換爲字符
(defn maze->str [maze]
  (->> (for [y (range (count maze))]
         (for [x (range (count (nth maze y)))]
           (cell->str maze [y x])))
       (map str/join)
       (str/join \newline)))

;; 生成迷宮
(println (maze->str (create-random-maze 10 10)))

上述程序輸出:

相關文章
相關標籤/搜索