;;Wizard's Adventure ;Our game world. ;A house with a garden ;In the garden there is a well, chain?, and frog ;In the house there is a living room with a ladder that goes to an attic ;So, we have three different locations. Living room, attic, and garden ;; Look at Land Of Lisp book for graphics ;;We need to be able to: ;;Look around, walk to different locations ;;Pick up objects, perform actions on objects that have been picked up ;;We will be able to see three kinds of things from any loaction ;; Basic scenery ;; One or mor paths (edges) to other locations ;; Objects that can be picked up and manipulated ;; This will contain a list and discription of the locations in our game: (defparameter *nodes* '((living-room (you are in a the living-room. a wizard is snoring loudly on the couch.)) (garden (you are in a beautiful garden. there is a well in front of you.)) (attic (you are in the attic. there is a giant welding torch in the corner.)))) ;; Now we will want to create a function to deal with associating what we need ;; for example: ;; (assoc 'garden *nodes*) ;; >> (GARDEN (YOU ARE IN A BEAUTIFUL GARDEN. THERE IS A WELL IN FRONT OF YOU.)) ;; The car of the cdr seems appropraite to me here, but let's see. (defun describe-location (location nodes) (cadr (assoc location nodes))) ;yup, same idea I had, except less verbose ;; Time to create the edges to define our paths (defparameter *edges* '((living-room (garden west door) (attic upstairs ladder)) (garden (living-room east door)) (attic (living-room downstairs ladder)))) ;; Now to actually use this... (defun describe-path (edge) `(there is a ,(caddr edge) going ,(cadr edge) from here.)) ;; Note the backtick this time. This works like the single quote, but ;; if we put a comma before something like (caddr edge) it actually computes it ;; Instead of simply looking at it as a symbol ;; This is called quasiquoting which allows us to create chunks of data ;; That have small chunks of data in them ;; Okay, the above was just a simple test to play with backquoting, so... ;; here's for real ;; damn, just relaized the above has "path" and the below is "paths" ;; Guess they are both used (defun describe-paths (location edges) (apply #'append (mapcar #'describe-path (cdr (assoc location edges))))) ;; Wow, that's a few new things ;; Other languages would use some sort of for loop to run through the edges ;; then cram descriptions of each path together using a temp variable ;; The inner part basically does this (cdr (assoc 'living-room *edges*)) ;; >> ((GARDEN WEST DOOR) (ATTIC UPSTAIRS LADDER)) ;; Next we convert the edges to descriptions. ;; (mapcar #'describe-path '((GARDEN WEST DOOR) (ATTIC UPSTAIRS LADDER))) ;; >> ((THERE IS A DOOR GOING WEST FROM HERE.) ;; (THERE IS A LADDER GOING UPSTAIRS FROM HERE.)) ;; In effect, with mapcar here, we could add in any number of edges. ;; So first we grab the edges available from our location ;; Then we loop over them to grab descriptions ;; mapcar takes a another function and applies it to every member of a list ;; Example: ;; (mapcar #'sqrt '(1 2 3 4 5)) ;; >> (1 1.4142135 1.7320508 2 2.236068) ;; Or perhaps a less dumb example that uses the 1+ function that simply ;; adds 1 to a number (provided by me) ;; (mapcar #'1+ '(1 2 3 4 5)) ;; >> (2 3 4 5 6) ;; yet another example, provided by book ;; (mapcar #'car '((foo bar) (baz qux))) ;; >> (foo baz) ;; Back to the function ;; (append) takes multiple lists and combines them into a single list ;; So instead of: ;; ((THERE IS A DOOR GOING WEST FROM HERE.) ;; (THERE IS A LADDER GOING UPSTAIRS FROM HERE)) ;; We get: ;; (THERE IS A DOOR GOING WEST FROM HERE. THERE IS A LADDER GOING UPSTAIRS FROM HERE) ;; Book example: ;;(append '(mary had) '(a) '(little lamb)) ;; >> (MARY HAD A LITTLE LAMB) ;; The (append) function requires it to be given seperate lists, ;; Not one big list, so we need (apply) ;; (apply #'append '((mary had) (a) (little lamb))) ;; >> (MARY HAD A LITTLE LAMB) ;; don't quite get (apply), but it seems to allow us ;; to run (append) on each list and return the result ;; examples for study and clarity ;; We run (append) on a list of lists ;; (append '((Hi) (bye))) ;; >> ((HI) (BYE)) ;; We do the same thing, but add (apply) ;; (apply #'append '((Hi) (bye))) ;; >> (HI BYE) ;; So after all that, the function takes two parameters ;; The players current location, ;; as well as an alist of edges/paths for the game map ;; Since assoc returns both the key and the value ;; from the alist (association list, dictionary?) ;; we take the (cdr) of that so we only grab the value ;; Next we take (mapcar) to map the describe-path function ;; against each edge that we find ;; Finally, we concat the lists for describing all paths into one list ;; Now it's time for objects (defparameter *objects* '(whiskey bucket frog chain)) ;; and their locations (defparameter *object-locations* '((whiskey living-room) (bucket living-room) (chain garden) (frog garden))) ;; Now for the fun function to list visible objects (defun objects-at (loc objs obj-locs) (labels ((at-loc-p (obj) (eq (cadr (assoc obj obj-locs)) loc))) (remove-if-not #'at-loc-p objs))) ;; We create (at-loc-p) here because it's not needed anywhere else ;; We put a -p on it because that's lisp convention for functions ;; That return t or nil ;; (remove-if-not) removes all things from a list for which a passed-in function ;; (at-loc-p in this case) does not return true ;; Essentially, it returns a filtered list of objects consisting ;; of thsoe itmes form which at-loc-p is true ;; So really, our function takes our location, Our objects, and our object locations ;; It checks the objects against the object locations to see if they exist ;; in our current location ;; Now that we have a function that can find objects in a location, ;; we need to describe them (defun describe-objects (loc objs obj-loc) (labels ((describe-obj (obj) `(you see a ,obj on the floor.))) (apply #'append (mapcar #'describe-obj (objects-at loc objs obj-loc))))) ;; Not sure I'd think of anything like this on my own, but this ;; seems to describe every object it can find. ;; First we create a function (describe-obj) that is just there to ;; have a way to print out objects found on the floor ;; the main part consists of calling (objects-at) to find the objects ;; at the current location, mapping describe-obj across this list ;; of objects and finally appending the descriptions ;; into a single list ;; So... (objects-at) returns a list of found objects in the area ;; (describe-obj) is then mapped to each element of that list. ;; Or in more procedual words, the list that resulted from ;; running (objects-at) is passed element by element into the ;; (describe-obj) private function ;; Time to make our (look) function ;; And finally create a global variable state to define our current location (defparameter *location* 'living-room) (defun look () (append (describe-location *location* *nodes*) (describe-paths *location* *edges*) (describe-objects *location* *objects* *object-locations*))) ;; Time to walk (defun walk (direction) (let ((next (find direction (cdr (assoc *location* *edges*)) :key #'cadr))) (if next (progn (setf *location* (car next)) (look)) '(you cannot go that way.)))) ;; Note that the above two functions are not functional. ;; They deal in global variables ;; First we look up the available walking paths in *edges* table ;; using the current location ;; This is used by (find) to locate the path marked with the appropriate ;; direction ;; Note that find searches a list for and item, then returns that found item ;; The direction (such as west, upstairs, etc) will be the cadr of each path ;; so we tell find to match the direction against the cadr of all paths ;; in the list ;; We do this by passing a keyword parameter to find. ;;example: ;; (find 'y '((5 x) (3 y) (7 z)) :key #'cadr) ;; >> (3 y) ;; It finds the first item in a list that has the symbol y in the cadr location ;; the (cadr) location, in this case, ;; means it checks the second item of each list ;; A keyword parameter has two parts ;; The first is the name (:key in this case) ;; The second is the value (#'cadr in this case) ;; Anywya, once we have the correct path we then store the result ;; in the variable next ;; Then the if expression check to see if next has a value ;; if it does, then it adjusts the player's position ;; calls look, and retrieves the description for the new location ;; otherwise, it does something like "invalid direction" instead of a new look ;;Whelp, time to pick up those objects (defun pickup (object) (cond ((member object (objects-at *location* *objects* *object-locations*)) (push (list object 'body) *object-locations*) `(you are now carring the ,object)) (t '(you cannot get that.)))) ;; I almost get it, we check to see if the object we try to pickup ;; Is part of the visible objects at our location ;; If so we push it out of the list, and say we are carrying it ;; Otherwise, we can't get that ;; First we check to see if the object is on the floor in the curent location ;; We use (objects-at) to generate a list of objects in the current location ;; we then use member to check if the obj we specified is in that list ;; if it is, we use the push command to push command to push a new item onto ;; the *object-locations* list consisting of the time and its new location ;; The new location will just be "body" for the player's body ;; The push command simply adds a new item to the front of a list ;; variable's list ;; Oh, time for an inventory (defun inventory () (cons 'items- (objects-at 'body *objects* *object-locations*)))