268 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			Common Lisp
		
	
	
	
	
	
			
		
		
	
	
			268 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			Common Lisp
		
	
	
	
	
	
| ;;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*)))
 |