Repository navigation
Expand file tree
/
Copy pathmaze.hs
More file actions
143 lines (126 loc) · 4.21 KB
/
Copy pathmaze.hs
File metadata and controls
143 lines (126 loc) · 4.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
-- Maze generator and solver in Haskell
-- Joe Wingbermuehle
-- 20070602 <> 20140815
module Main where
import Control.Monad
import Control.Monad.State
import Control.Monad.Reader
import Data.Map
import System.Random
data ElementType = Space | Wall | Marked | Visited | Door
deriving (Eq)
data Direction = DLeft | DRight | DUp | DDown
deriving (Enum)
data MazeData = MazeData {
width :: Int,
height :: Int,
gen :: StdGen,
maze :: Map (Int, Int) ElementType
}
instance Show ElementType where
show Space = " "
show Wall = "[]"
show Marked = "++"
show Visited = show Space
show Door = show Space
instance Show MazeData where
show = showMaze 0 0
where
showMaze x y md =
if x == (width md) then
if y + 1 == (height md) then ""
else "\n" ++ (showMaze 0 (y + 1) md)
else
let e = findWithDefault Wall (x, y) (maze md) in
(show e) ++ (showMaze (x + 1) y md)
type MazeState a = ReaderT (Int, Int) (State MazeData) a
-- Initialize an uncarved maze.
initMaze :: Int -> Int -> Int -> MazeData
initMaze w h s =
let xs = [0 .. w - 1] in
let ys = [0 .. h - 1] in
let top = [(x, 0) | x <- xs] in
let bottom = [(x, h - 1) | x <- xs] in
let left = [(0, y) | y <- ys] in
let right = [(w - 1, y) | y <- ys] in
let start = [(2, 2)] in
let spaces = Prelude.foldl (++) [] [top, bottom, left, right, start] in
let m1 = fromList [(p, Space) | p <- spaces] in
let doors = [(2, 2), (2, 1), (w - 3, h - 2)] in
let m2 = fromList [(p, Door) | p <- doors] in
MazeData {
width = w, height = h, gen = mkStdGen s,
maze = union m1 m2
}
-- Get the next position in the specified direction.
getPosition :: (Int, Int) -> Direction -> (Int, Int)
getPosition (x, y) DLeft = (x - 1, y)
getPosition (x, y) DRight = (x + 1, y)
getPosition (x, y) DUp = (x, y - 1)
getPosition (x, y) DDown = (x, y + 1)
-- Move in the specified direction.
move :: ElementType -> Direction -> MazeState (Int, Int)
move fill d = do
pos <- ask; st <- get
let mid = getPosition pos d
let m = insert mid fill $ maze st
let next = getPosition mid d
put $ st { maze = insert next fill m }
return next
-- Determine if we can carve in the specified direction.
canMove :: ElementType -> Direction -> MazeState Bool
canMove match d = do
pos <- ask; st <- get
let mid = getPosition pos d
let e1 = findWithDefault Wall mid $ maze st
let next = getPosition mid d
let e2 = findWithDefault Wall next $ maze st
return $ e1 == match && e2 == match
-- Carve the maze starting at the speciified position.
carve :: MazeState ()
carve = do
st <- get
let (start, g') = randomR (0, 3) $ gen st
put $ st { gen = g' }
mapM_ tryCarve [toEnum ((start + i) `mod` 4) | i <- [0 .. 3]]
where
tryCarve d = do
cm <- canMove Wall d
when cm (move Space d >>= (\n -> local (const n) carve))
-- Find a path from the current position to the end position.
solveTo :: (Int, Int) -> MazeState Bool
solveTo stop = do
pos <- ask
if pos == stop then
return True
else do
st <- get; pos <- ask
put $ st { maze = insert pos Marked (maze st) }
foldM trySolve False [toEnum i | i <- [0 .. 3]]
where
trySolve result d
| result = return True
| otherwise = do
cm <- canMove Space d
if cm then do
next <- move Marked d
found <- local (const next) $ solveTo stop
if found then return True
else (move Visited d >> return False)
else return False
-- Generate a random maze.
generate :: Int -> Int -> Int -> MazeData
generate w h s = execState (runReaderT carve (2, 2)) (initMaze w h s)
-- Solve a maze.
solve :: MazeData -> MazeData
solve md =
let start = (2, 2) in
let stop = ((width md) - 3, (height md - 3)) in
execState (runReaderT (solveTo stop) start) md
-- Generate and display a solved random maze.
main :: IO ()
main = do
let maze = generate 39 23 5
let solved = solve maze
putStrLn $ show maze
putStrLn $ show solved