A Text Adventure Game by CommonLISP
June 1, 2014
Common Lisp
Today, I read the book Land of Lisp. It is interesting to write a text adventure game.
First, there are some basic and useful functions.
##some useful functions
1. mapcar
This function takes another function and a list, and then applies this function to every member of a list.This is an example:
```lisp
(mapcar #'sqrt '(1 2 3 4 5))
```
It will output a list contains the square root of each element. Besides, the code above is the same as:
```lisp
(mapcar (function car) '(1 2 3 4 5))
```
Furthermore, there is a namespace problem here. For example:
```lisp
(let ((car "liuxueyang"))
(mapcar #'car '((foo bar) (fo ba))))
```
It will output (foo ba) as expected. Here are two 'car's. Because we prepended the second 'car' with #', there is no confusion about which car we are talking about.
Common Lisp tracks function names differently from variable names. It has multiple namespaces, including one for variables and one for functions. However, in Scheme, there is only namespace for functions and variables.
append
this function can join several lists into one big list:
(append '(liu) '(xue) '(yang))
It will output:
(liu xue yang)
apply
Sometimes, I will confuse this function with the
mapcar
. You pass theapply
a function and a list of objects, and it pretends that the items in the list are separate objects and passes them to the given function as such.(apply #'append '((are) (you) (sad)))
This will output:
(are you sad)
assoc
(defparameter *letter-list* '((a (This is letter a)) (b (This is letter b)) (a (This is the second letter a)))) (assoc 'a *letter-list*)
This wil output:
(a (This is letter a))
Notice that it will ONLY returns the first item it finds in a list. This is very useful in future. (such as when we push two objects into one list, we will only get the newer one as if we replace the old one with the newer one.)
push
the
push
command simply adds a new item to the front of a list variable’s list. For example:(defparameter *foo* '(1 2 3)) (push 73 *foo*)
It will output:
(73 1 2 3)
find
this function searches a list for an item, then return that found item. For example:
(find 1 '(4 5 1))
It will output:
1
Besides, in Common Lisp, many functions have built-in freatures that can be accessed by passing in special parameters at the end of the function call. For example:
(find 'y '((1 x) (2 y) (3 n)) :key #'cadr)
It will output:
(2 Y)
##main program
This is the complete program.
; this is a text adventory game.
; by Conrad Barski, M.D.
; describe these locations of the game
(defparameter *nodes*
'((living-room (you are in 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.))))
;describe each location and the paths of each of them
(defparameter *edges* '((living-room (garden west door)
(attic upstair ladder))
(garden (living-room east door))
(attic (living-room downstairs ladder))))
; a function to describe current location
(defun describe-location (location nodes)
(cadr (assoc location nodes)))
; describe the path of a location
(defun describe-path (edge)
`(there is a ,(caddr edge) goting ,(cadr edge) from here.))
; describe multipal paths of a location
(defun describe-paths (location edges)
(apply #'append (mapcar #'describe-path (cdr (assoc location edges)))))
; describe objects
(defparameter *objects* '(whiskey bucket frog chain))
; describe locations of objects
(defparameter *object-locations*
'((whiskey living-room)
(bucket living-room)
(chain garden)
(frog garden)))
; list the object visible from a given location
(defun objects-at (loc objs obj-locs)
(labels ((at-loc-p (obj) (eq loc (cadr (assoc obj obj-locs)))))
(remove-if-not #'at-loc-p objs)))
; describe objects visible at a given location
(defun describe-objects (loc objs obj-locs)
(labels ((describe-obj (obj)
`(there is a ,obj on the floor.)))
(apply #'append (mapcar #'describe-obj (objects-at loc objs obj-locs)))))
; define current location
(defparameter *location* 'living-room)
; start to look around
(defun look ()
(append (describe-location *location* *nodes*)
(describe-paths *location* *edges*)
(describe-objects *location* *objects* *object-locations*)))
; this function takes a direction and let me walk there
(defun walk (direction)
(let ((next (find direction (cdr (assoc *location* *edges*)) :key #'cadr)))
(if next
(progn (setf *location* (car next))
(look))
'(you can not go that way.))))
; pick up a object
(defun pickup (object)
(cond ((member object (objects-at *location* *objects* *object-locations*))
(push (list object 'body) *object-locations*)
`(you are now carrying the ,object))
(t '(you cannot get that.))))
;see an inventory of objects I carry
(defun inventory ()
(cons 'items- (objects-at 'body *objects* *object-locations*)))
; this function add parentheses and quotes to what we input
(defun game-read ()
(let ((cmd (read-from-string (concatenate 'string "(" (read-line) ")"))))
(flet ((quote-it (x)
(list 'quote x)))
(cons (car cmd) (mapcar #'quote-it (cdr cmd))))))
; define allowed commands
(defparameter *allowed-commands* '(look walk pickup inventory))
;call some function
(defun game-eval (sexp)
(if (member (car sexp) *allowed-commands*)
(eval sexp)
'(I do not know that command.)))
##play the game
- function
look
describe where are you, the paths you can go and the objects around you. - function
walk
take a direction with a single quote as an argument. It will allows you go somewhere and print some error if there is no way in the direction. - function
pickup
will allow you to pick up some object. - function
inventory
will list those objects you have.
This is an example:
(look)
(YOU ARE IN THE LIVING-ROOM. A WIZARD IS SNORING LOUDLY ON THE COUCH. THERE IS A DOOR GOTING WEST FROM HERE. THERE IS A LADDER GOTING UPSTAIR FROM HERE. THERE IS A WHISKEY ON THE FLOOR. THERE IS A BUCKET ON THE FLOOR.)
(walk 'west)
(YOU ARE IN A BEAUTIFUL GARDEN. THERE IS A WELL IN FRONT OF YOU. THERE IS A DOOR GOTING EAST FROM HERE. THERE IS A FROG ON THE FLOOR. THERE IS A CHAIN ON THE FLOOR.)
(pickup 'frog)
(YOU ARE NOW CARRYING THE FROG)
(inventory)
(ITEMS- FROG)
I will try to add a custom text game interface which will make the game play more seamless for the player.
##Add a Custom Interface to Our Game
###Some Functions about print and read
print
This function uses to print something. For Example:
(print '3') (print 'foo') (print '"foo") (print '#\a)
It will output:
3 FOO “FOO” ‘#’\A (does not include single quote.)
Note that this function will place a space character at the end of the printed value and start a new line before print another value.
princ
This function does less than
print
. It will print value as it was. For example:(progn (prin1 "This") (prin1 "is") (prin1 "a") (prin1 "test"))
It will output:
“This”“is”“a”“test”
read
read something from input. eg:
(defun add-one () (print "Enter a value") (let ((num (read))) (print "When I add one I get") (print (+ num 1))))
When we run this function:
(add-one) “Enter a value” 3 “When I add one I get” 4
read-line
This command captures and returns all the text entered until the ENTER key is pessed, without any fuss.
eval
This command can execute some data as if it were a piece of code. eg:
(defparameter *foo* '(+ 4 5)) (eval *foo*)
It will output:
9
concatenate
It can be used for concatenating strings together. eg:
(concatenate 'string "(" (read-ling) ")")
To run it:
liu “(liu)”
read-from-string
It let us read a syntax expression from a string instead of from the console. eg:
(let ((cmd (read-from-string "(liu)"))) (princ cmd))
To run it:
(LIU)
I add game-read and game-eval functions to the original code.