Skip to content

Instantly share code, notes, and snippets.

@schmalz
Last active January 4, 2018 11:42
Show Gist options
  • Save schmalz/13ddc34ca5f65c3beb7ecab27c579ac1 to your computer and use it in GitHub Desktop.
Save schmalz/13ddc34ca5f65c3beb7ecab27c579ac1 to your computer and use it in GitHub Desktop.
Land of Lisp: Grand Theft Wumpus
(defparameter *max-label-length* 30)
(defun dot-label (exp)
"Produce a DOT label from a Lisp `exp`."
(if exp
(let ((s (write-to-string exp :pretty nil)))
(if (> (length s) *max-label-length*)
(concatenate 'string (subseq s 0 (- *max-label-length* 3)) "...")
s))
""))
(defun dot-name (exp)
"Produce a valid DOT name from a Lisp `exp`."
(substitute-if #\_ (complement #'alphanumericp) (prin1-to-string exp)))
(defun nodes->dot (nodes)
"Given an association-list containing node information, produce DOT information (on standard out)."
(mapc (lambda (node)
(fresh-line)
(princ (dot-name (car node)))
(princ "[label=\"")
(princ (dot-label node))
(princ "\"];"))
nodes))
(defun edges->dot (edges)
"Given an association-list containing node information, produce DOT information (on standard out)."
(mapc (lambda (node)
(mapc (lambda (edge)
(fresh-line)
(princ (dot-name (car node)))
(princ "->")
(princ (dot-name (car edge)))
(princ "[label=\"")
(princ (dot-label (cdr edge)))
(princ "\"];"))
(cdr node)))
edges))
(defun uedges->dot (edges)
(maplist (lambda (lst)
(mapc (lambda (edge)
(unless (assoc (car edge) (cdr lst))
(fresh-line)
(princ (dot-name (caar lst)))
(princ "--")
(princ (dot-name (car edge)))
(princ "[label=\"")
(princ (dot-label (cdr edge)))
(princ "\"];")))
(cdar lst)))
edges))
(defun graph->dot (nodes edges)
(princ "digraph{")
(nodes->dot nodes)
(edges->dot edges)
(princ "}"))
(defun ugraph->dot (nodes edges)
(princ "graph{")
(nodes->dot nodes)
(uedges->dot edges)
(princ "}"))
(defun dot->png (fname thunk)
(with-open-file (*standard-output* fname :direction :output :if-exists :supersede)
(funcall thunk))
(sb-ext:run-program "/usr/bin/dot" `("-Tpng" "-O" ,fname))) ; This is SBCL-specific.
(defun graph->png (fname nodes edges)
(dot->png fname
(lambda () (graph->dot nodes edges))))
(defun ugraph->png (fname nodes edges)
(dot->png fname
(lambda () (ugraph->dot nodes edges))))
(load "graph-util.lisp")
(defparameter *node-count* 30)
(defparameter *edge-count* 45)
(defparameter *worm-count* 3)
(defparameter *cop-odds* 15)
(defparameter *city-edges* nil)
(defparameter *city-nodes* nil)
(defparameter *player-pos* nil)
(defparameter *visited-nodes* nil)
(defun random-node ()
(1+ (random *node-count*)))
(defun edge-pair (a b)
(unless (eql a b)
(list (cons a b) (cons b a))))
(defun make-edge-list ()
(apply #'append (loop repeat *edge-count* collect (edge-pair (random-node) (random-node)))))
(defun direct-edges (node edge-list)
(remove-if-not (lambda (x)
(eql (car x) node))
edge-list))
(defun get-connected (node edge-list)
(let ((visited nil))
(labels ((traverse (node)
(unless (member node visited)
(push node visited)
(mapc (lambda (edge)
(traverse (cdr edge)))
(direct-edges node edge-list)))))
(traverse node))
visited))
(defun find-islands (nodes edge-list)
"Find any islands (unconnected nodes) in a list of edges."
(let ((islands nil))
(labels ((find-island (nodes)
(let* ((connected (get-connected (car nodes) edge-list))
(unconnected (set-difference nodes connected)))
(push connected islands)
(when unconnected
(find-island unconnected)))))
(find-island nodes))
islands))
(defun connect-with-bridges (islands)
(when (cdr islands)
(append (edge-pair (caar islands) (caadr islands))
(connect-with-bridges (cdr islands)))))
(defun connect-all-islands (nodes edge-list)
(append (connect-with-bridges (find-islands nodes edge-list)) edge-list))
(defun add-cops (edge-alist edges-with-cops)
(mapcar (lambda (x)
(let ((node1 (car x))
(node1-edges (cdr x)))
(cons node1
(mapcar (lambda (edge)
(let ((node2 (car edge)))
(if (intersection (edge-pair node1 node2)
edges-with-cops
:test #'equal)
(list node2 'cops)
edge)))
node1-edges))))
edge-alist))
(defun edges-to-alist (edge-list)
(mapcar (lambda (node1)
(cons node1
(mapcar (lambda (edge)
(list (cdr edge)))
(remove-duplicates (direct-edges node1 edge-list)
:test #'equal))))
(remove-duplicates (mapcar #'car edge-list))))
(defun make-city-edges ()
(let* ((nodes (loop for i from 1 to *node-count*
collect i))
(edge-list (connect-all-islands nodes (make-edge-list)))
(cops (remove-if-not (lambda (x)
(declare (ignore x))
(zerop (random *cop-odds*)))
edge-list)))
(add-cops (edges-to-alist edge-list) cops)))
(defun neighbours (node edge-alist)
(mapcar #'car (cdr (assoc node edge-alist))))
(defun within-one (a b edge-alist)
(member b (neighbours a edge-alist)))
(defun within-two (a b edge-alist)
(or (within-one a b edge-alist)
(some (lambda (x)
(within-one x b edge-alist))
(neighbours a edge-alist))))
(defun make-city-nodes (edge-alist)
(let ((wumpus (random-node))
(glow-worms (loop for i below *worm-count*
collect (random-node))))
(loop for n from 1 to *node-count*
collect (append (list n)
(cond ((eql n wumpus) '(wumpus))
((within-two n wumpus edge-alist) '(blood!)))
(cond ((member n glow-worms)
'(glow-worm))
((some (lambda (worm)
(within-one n worm edge-alist))
glow-worms)
'(lights!)))
(when (some #'cdr (cdr (assoc n edge-alist)))
'(sirens!))))))
(defun find-empty-node ()
(let ((x (random-node)))
(if (cdr (assoc x *city-nodes*))
(find-empty-node)
x)))
(defun draw-city ()
(ugraph->png "city" *city-nodes* *city-edges*))
(defun known-city-nodes ()
(mapcar (lambda (node)
(if (member node *visited-nodes*)
(let ((n (assoc node *city-nodes*)))
(if (eql node *player-pos*)
(append n '(*))
n))
(list node '?)))
(remove-duplicates
(append *visited-nodes*
(mapcan (lambda (node)
(mapcar #'car
(cdr (assoc node
*city-edges*))))
*visited-nodes*)))))
(defun known-city-edges ()
(mapcar (lambda (node)
(cons node (mapcar (lambda (x)
(if (member (car x) *visited-nodes*)
x
(list (car x))))
(cdr (assoc node *city-edges*)))))
*visited-nodes*))
(defun draw-known-city ()
(ugraph->png "known-city" (known-city-nodes) (known-city-edges)))
(defun handle-new-place (edge pos charging)
(let* ((node (assoc pos *city-nodes*))
(has-worm (and (member 'glow-worm node)
(not (member pos *visited-nodes*)))))
(pushnew pos *visited-nodes*)
(setf *player-pos* pos)
(draw-known-city)
(cond ((member 'cops edge) (princ "You ran into the cops, game over!"))
((member 'wumpus node) (if charging
(princ "You found the Wumpus!")
(princ "You ran into the Wumpus, game over!")))
(charging (princ "You watsed your last bullet, game over!"))
(has-worm (let ((new-pos (random-node)))
(princ "You ran into a Glow Worm Gang! You're now at ")
(princ new-pos)
(handle-new-place nil new-pos nil))))))
(defun handle-direction (pos charging)
(let ((edge (assoc pos
(cdr (assoc *player-pos* *city-edges*)))))
(if edge
(handle-new-place edge pos charging)
(princ "That location does not exist!"))))
(defun walk (pos)
(handle-direction pos nil))
(defun charge (pos)
(handle-direction pos t))
(defun new-game ()
(setf *city-edges* (make-city-edges))
(setf *city-nodes* (make-city-nodes *city-edges*))
(setf *player-pos* (find-empty-node))
(setf *visited-nodes* (list *player-pos*))
(draw-city)
(draw-known-city))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment