193 lines
5.4 KiB
EmacsLisp
193 lines
5.4 KiB
EmacsLisp
(require 'cl-lib)
|
|
|
|
(cl-defstruct maze rows cols data)
|
|
|
|
(defmacro maze-pt (w r c)
|
|
`(+ (* (mod ,r (maze-rows ,w)) (maze-cols ,w))
|
|
(mod ,c (maze-cols ,w))))
|
|
|
|
(defmacro maze-ref (w r c)
|
|
`(aref (maze-data ,w) (maze-pt ,w ,r ,c)))
|
|
|
|
(defun new-maze (rows cols)
|
|
(setq rows (1+ rows)
|
|
cols (1+ cols))
|
|
(let ((m (make-maze :rows rows :cols cols :data (make-vector (* rows cols) nil))))
|
|
|
|
(dotimes (r rows)
|
|
(dotimes (c cols)
|
|
(setf (maze-ref m r c) (copy-sequence '(wall ceiling)))))
|
|
|
|
(dotimes (r rows)
|
|
(maze-set m r (1- cols) 'visited))
|
|
|
|
(dotimes (c cols)
|
|
(maze-set m (1- rows) c 'visited))
|
|
|
|
(maze-unset m 0 0 'ceiling) ;; Maze Entrance
|
|
(maze-unset m (1- rows) (- cols 2) 'ceiling) ;; Maze Exit
|
|
|
|
m))
|
|
|
|
(defun maze-is-set (maze r c v)
|
|
(member v (maze-ref maze r c)))
|
|
|
|
(defun maze-set (maze r c v)
|
|
(let ((cell (maze-ref maze r c)))
|
|
(when (not (member v cell))
|
|
(setf (maze-ref maze r c) (cons v cell)))))
|
|
|
|
(defun maze-unset (maze r c v)
|
|
(setf (maze-ref maze r c) (delete v (maze-ref maze r c))))
|
|
|
|
(defun print-maze (maze &optional marks)
|
|
(dotimes (r (1- (maze-rows maze)))
|
|
|
|
(dotimes (c (1- (maze-cols maze)))
|
|
(princ (if (maze-is-set maze r c 'ceiling) "+---" "+ ")))
|
|
(princ "+")
|
|
(terpri)
|
|
|
|
(dotimes (c (1- (maze-cols maze)))
|
|
(princ (if (maze-is-set maze r c 'wall) "|" " "))
|
|
(princ (if (member (cons r c) marks) " * " " ")))
|
|
(princ "|")
|
|
(terpri))
|
|
|
|
(dotimes (c (1- (maze-cols maze)))
|
|
(princ (if (maze-is-set maze (1- (maze-rows maze)) c 'ceiling) "+---" "+ ")))
|
|
(princ "+")
|
|
(terpri))
|
|
|
|
(defun shuffle (lst)
|
|
(sort lst (lambda (a b) (= 1 (random 2)))))
|
|
|
|
(defun to-visit (maze row col)
|
|
(let (unvisited)
|
|
(dolist (p '((0 . +1) (0 . -1) (+1 . 0) (-1 . 0)))
|
|
(let ((r (+ row (car p)))
|
|
(c (+ col (cdr p))))
|
|
(unless (maze-is-set maze r c 'visited)
|
|
(push (cons r c) unvisited))))
|
|
unvisited))
|
|
|
|
(defun make-passage (maze r1 c1 r2 c2)
|
|
(if (= r1 r2)
|
|
(if (< c1 c2)
|
|
(maze-unset maze r2 c2 'wall) ; right
|
|
(maze-unset maze r1 c1 'wall)) ; left
|
|
(if (< r1 r2)
|
|
(maze-unset maze r2 c2 'ceiling) ; up
|
|
(maze-unset maze r1 c1 'ceiling)))) ; down
|
|
|
|
(defun dig-maze (maze row col)
|
|
(let (backup
|
|
(run 0))
|
|
(maze-set maze row col 'visited)
|
|
(push (cons row col) backup)
|
|
(while backup
|
|
(setq run (1+ run))
|
|
(when (> run (/ (+ row col) 3))
|
|
(setq run 0)
|
|
(setq backup (shuffle backup)))
|
|
(setq row (caar backup)
|
|
col (cdar backup))
|
|
(let ((p (shuffle (to-visit maze row col))))
|
|
(if p
|
|
(let ((r (caar p))
|
|
(c (cdar p)))
|
|
(make-passage maze row col r c)
|
|
(maze-set maze r c 'visited)
|
|
(push (cons r c) backup))
|
|
(pop backup)
|
|
(setq backup (shuffle backup))
|
|
(setq run 0))))))
|
|
|
|
(defun generate (rows cols)
|
|
(let* ((m (new-maze rows cols)))
|
|
(dig-maze m (random rows) (random cols))
|
|
(print-maze m)))
|
|
|
|
(defun parse-ceilings (line)
|
|
(let (rtn
|
|
(i 1))
|
|
(while (< i (length line))
|
|
(push (eq ?- (elt line i)) rtn)
|
|
(setq i (+ i 4)))
|
|
(nreverse rtn)))
|
|
|
|
(defun parse-walls (line)
|
|
(let (rtn
|
|
(i 0))
|
|
(while (< i (length line))
|
|
(push (eq ?| (elt line i)) rtn)
|
|
(setq i (+ i 4)))
|
|
(nreverse rtn)))
|
|
|
|
(defun parse-maze (file-name)
|
|
(let ((rtn)
|
|
(lines (with-temp-buffer
|
|
(insert-file-contents-literally file-name)
|
|
(split-string (buffer-string) "\n" t))))
|
|
(while lines
|
|
(push (parse-ceilings (pop lines)) rtn)
|
|
(push (parse-walls (pop lines)) rtn))
|
|
(nreverse rtn)))
|
|
|
|
(defun read-maze (file-name)
|
|
(let* ((raw (parse-maze file-name))
|
|
(rows (1- (/ (length raw) 2)))
|
|
(cols (length (car raw)))
|
|
(maze (new-maze rows cols)))
|
|
(dotimes (r rows)
|
|
(let ((ceilings (pop raw)))
|
|
(dotimes (c cols)
|
|
(unless (pop ceilings)
|
|
(maze-unset maze r c 'ceiling))))
|
|
(let ((walls (pop raw)))
|
|
(dotimes (c cols)
|
|
(unless (pop walls)
|
|
(maze-unset maze r c 'wall)))))
|
|
maze))
|
|
|
|
(defun find-exits (maze row col)
|
|
(let (exits)
|
|
(dolist (p '((0 . +1) (0 . -1) (-1 . 0) (+1 . 0)))
|
|
(let ((r (+ row (car p)))
|
|
(c (+ col (cdr p))))
|
|
(unless
|
|
(cond
|
|
((equal p '(0 . +1)) (maze-is-set maze r c 'wall))
|
|
((equal p '(0 . -1)) (maze-is-set maze row col 'wall))
|
|
((equal p '(+1 . 0)) (maze-is-set maze r c 'ceiling))
|
|
((equal p '(-1 . 0)) (maze-is-set maze row col 'ceiling)))
|
|
(push (cons r c) exits))))
|
|
exits))
|
|
|
|
(defun drop-visited (maze points)
|
|
(let (not-visited)
|
|
(while points
|
|
(unless (maze-is-set maze (caar points) (cdar points) 'visited)
|
|
(push (car points) not-visited))
|
|
(pop points))
|
|
not-visited))
|
|
|
|
(defun solve-maze (maze)
|
|
(let (solution
|
|
(exit (cons (- (maze-rows maze) 2) (- (maze-cols maze) 2)))
|
|
(pt (cons 0 0)))
|
|
(while (not (equal pt exit))
|
|
(maze-set maze (car pt) (cdr pt) 'visited)
|
|
(let ((exits (drop-visited maze (find-exits maze (car pt) (cdr pt)))))
|
|
(if (null exits)
|
|
(setq pt (pop solution))
|
|
(push pt solution)
|
|
(setq pt (pop exits)))))
|
|
(push pt solution)))
|
|
|
|
(defun solve (file-name)
|
|
(let* ((maze (read-maze file-name))
|
|
(solution (solve-maze maze)))
|
|
(print-maze maze solution)))
|
|
|
|
(solve "maze.txt")
|