Skip to content

Commit

Permalink
Allows for infinite Sidewinder mazes
Browse files Browse the repository at this point in the history
  • Loading branch information
defndaines committed Jul 15, 2017
1 parent d026088 commit a7a8aba
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 2 deletions.
17 changes: 17 additions & 0 deletions README.md
Expand Up @@ -203,6 +203,23 @@ Which will produce a maze like:

![Sidewinder Maze](img/sidewinder-maze.png)

Because Sidewinder creates a maze one row at a time, it is possible to create
infinite mazes. The mazes won't be perfect mazes unless completed, though.
These mazes only link south or east, so you'll only be able to use certain
render functions, like `ascii/render` and `png/render`, which are already
optimized to only render the east and south walls per cell. Optional weights can
be passed to the function.

```clojure
(def infini-maze (sw/create-lazy 25 {:south 2 :east 5}))
(def maze (conj (vec (take 7 infini-maze)) (sw/last-row 25)))
(png/render maze)
```

Which will produce a maze like:

![Infinite Sidewinder Maze](img/infinite-sidewinder-maze.png)


### Aldous-Broder

Expand Down
Binary file added img/infinite-sidewinder-maze.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
49 changes: 47 additions & 2 deletions src/meiro/sidewinder.clj
Expand Up @@ -6,12 +6,14 @@
(:require [meiro.core :as m]
[clojure.data.generators :as gen]))

(def ^:private weights

(def ^:private default-weights
"Constants allow for different weights for each direction.
Higher south weight has longer vertical corridors.
Higher east weight has longer horizontal corridors."
{:south 4 :east 5})


(defn possible-directions
"Determine which directions are valid from the provided pos."
[maze pos]
Expand All @@ -20,19 +22,21 @@
#(m/in? maze (first %))
{(m/south pos) :south (m/east pos) :east})))


(defn- link-neighbor
"Link to a random neighbor to south or east.
When linking to south, the link will be created from any position in the
current east-west corridor, not necessarily from `pos`."
[maze pos]
(let [directions (possible-directions maze pos)]
(if (seq directions)
(case (gen/weighted (select-keys weights directions))
(case (gen/weighted (select-keys default-weights directions))
:east (m/link maze pos (m/east pos))
:south (let [from (rand-nth (m/path-west maze pos))]
(m/link maze from (m/south from))))
maze)))


(defn create
"Create a random maze using the sidewinder algorithm."
[grid]
Expand All @@ -41,3 +45,44 @@
grid
(for [row (range (count grid)) col (range (count (first grid)))]
[row col])))


(defn- corridor
"Get a path sequence of linked positions west of the provided position,
including that position."
[row pos]
(cons pos
(take-while
(fn [pos] (= [:east] (get row pos)))
(range (dec pos) -1 -1))))


(defn- create-row
"Create a maze row using the Sidewinder algorithm.
This approach does not create mutual links, but only links to the south
or east."
([width weights]
(reduce
(fn [row pos]
(case (gen/weighted (select-keys weights [:east :south]))
:east (assoc row pos [:east])
:south (let [from (rand-nth (corridor row pos))]
(update row from conj :south))))
(conj (vec (repeat (dec width) [])) [:south])
(range (dec width)))))


(defn last-row
"Create the last row of a Sindwinder maze."
[width]
(conj
(vec (repeat (dec width) [:east]))
[:west]))


(defn create-lazy
"Create a potentially infinite Sidewinder maze."
([width] (create-lazy width default-weights))
([width weights]
(cons (create-row width weights)
(lazy-seq (create-lazy width weights)))))
37 changes: 37 additions & 0 deletions test/meiro/sidewinder_test.clj
Expand Up @@ -3,6 +3,43 @@
[meiro.core :refer :all]
[meiro.sidewinder :refer :all]))


(deftest create-test
(testing "Ensure all cells are linked."
(is (every? #(not-any? empty? %) (create (init 10 12))))))


(deftest corridor-test
(testing "Get corridor paths."
(let [row [[:east] [] [:east] [:east] []]]
(is (= [0]
(#'meiro.sidewinder/corridor row 0)))
(is (= [1 0]
(#'meiro.sidewinder/corridor row 1)))
(is (= [2]
(#'meiro.sidewinder/corridor row 2)))
(is (= [3 2]
(#'meiro.sidewinder/corridor row 3)))
(is (= [4 3 2]
(#'meiro.sidewinder/corridor row 4))))))


(deftest create-row-test
(testing "Weights used to decide direction."
(is (= [[:east] [:east] [:east] [:south]]
(#'meiro.sidewinder/create-row 4 {:south 0 :east 1})))
(is (= [[:south] [:south] [:south] [:south]]
(#'meiro.sidewinder/create-row 4 {:south 1 :east 0})))))


(deftest last-row-test
(testing "Last row can only link to itself."
(is (= [[:east] [:east] [:east] [:west]]
(last-row 4)))))


(deftest create-lazy-test
(testing "Build a maze using infinite approach."
(let [maze (create-lazy 8)]
(is (= 8
(count (first (drop 25 maze))))))))

0 comments on commit a7a8aba

Please sign in to comment.