lisp:land-of-lisp:ch6
This commit is contained in:
parent
6215a98407
commit
fcce94e59f
68
lisp/land-of-lisp/ch6/notes.lisp
Normal file
68
lisp/land-of-lisp/ch6/notes.lisp
Normal file
@ -0,0 +1,68 @@
|
||||
(defun say-hello ()
|
||||
(print "Please type your name:")
|
||||
(let ((name (read)))
|
||||
(print "Nice to meet you, ")
|
||||
(print name)))
|
||||
|
||||
;; Type name in quotes in order for it to be a string and not a symbol
|
||||
;; When running the function
|
||||
|
||||
;; First we print a message asking users for a name
|
||||
;; Next we define a local variable called "name" which is set
|
||||
;; to the value returned by (read)
|
||||
|
||||
;; (read) will cause lisp to wait for the user to type in something
|
||||
;; Only after the user has typed something and pressed ENTER will
|
||||
;; the variable name be set to the result
|
||||
|
||||
;; Now that we have the user's name, we print a personalized greeting
|
||||
|
||||
;; The print command leaves in the quotes when printing to the screen.
|
||||
|
||||
;; It is recomended to use (print) or (read) if we can get away with it
|
||||
;; in order to save doing extra work. (I still like (format))
|
||||
|
||||
;; Basically, (read) and (print) simply take in and spit out things
|
||||
;; In a computer understood way. Hence the quotes.
|
||||
|
||||
;;Let's do something with numbers
|
||||
|
||||
(defun add-five ()
|
||||
(print "please enter a number:")
|
||||
(let ((num (read)))
|
||||
(print "When I add five I get")
|
||||
(print (+ num 5 ))))
|
||||
|
||||
;; We can use literal characters by putting #\ in front of it
|
||||
;; So we can do #\a to get "a"
|
||||
|
||||
;; There are also special characters used this way
|
||||
;; #\newline #\tab #\space .. Should be self-explaintory
|
||||
|
||||
;; To avoid getting quotes back from print and prin1
|
||||
;; we can use (princ)
|
||||
|
||||
(progn (princ "This sentence will be interrupted")
|
||||
(princ #\newline)
|
||||
(princ "By an annoying newline character."))
|
||||
;; >> This sentence will be interrupted
|
||||
;; >> by an annoying newline character.
|
||||
|
||||
;; This is posibly a better methond than bothering with (format)
|
||||
|
||||
;; Now let's create a say-hello function that doesn't suck
|
||||
(defun say-hello2 ()
|
||||
(princ "Please type your name:")
|
||||
(let ((name (read-line)))
|
||||
(princ "Nice to meet you, ")
|
||||
(princ name)))
|
||||
|
||||
;; This is similar to our original, but now it doesn't
|
||||
;; print quotes around the string.
|
||||
;; It just takes everything the user enters as one big string
|
||||
|
||||
;; back to the game
|
||||
;; So far we've been able to do everything in our game from the lisp repl
|
||||
;; this is awesome for prototyping, but it's time to make a real interface
|
||||
|
||||
;; (see the new wizard file)
|
103
lisp/land-of-lisp/ch6/wizard_adventure_add_repl-no-comments.lisp
Normal file
103
lisp/land-of-lisp/ch6/wizard_adventure_add_repl-no-comments.lisp
Normal file
@ -0,0 +1,103 @@
|
||||
(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.))))
|
||||
|
||||
(defun describe-location (location nodes)
|
||||
(cadr (assoc location nodes)))
|
||||
|
||||
(defparameter *edges* '((living-room (garden west door)
|
||||
(attic upstairs ladder))
|
||||
(garden (living-room east door))
|
||||
(attic (living-room downstairs ladder))))
|
||||
|
||||
(defun describe-path (edge)
|
||||
`(there is a ,(caddr edge) going ,(cadr edge) from here.))
|
||||
|
||||
(defun describe-paths (location edges)
|
||||
(apply #'append (mapcar #'describe-path (cdr (assoc location edges)))))
|
||||
|
||||
(defparameter *objects* '(whiskey bucket frog chain))
|
||||
|
||||
(defparameter *object-locations* '((whiskey living-room)
|
||||
(bucket living-room)
|
||||
(chain garden)
|
||||
(frog garden)))
|
||||
|
||||
(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)))
|
||||
|
||||
(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)))))
|
||||
|
||||
(defparameter *location* 'living-room)
|
||||
|
||||
(defun look ()
|
||||
(append (describe-location *location* *nodes*)
|
||||
(describe-paths *location* *edges*)
|
||||
(describe-objects *location* *objects* *object-locations*)))
|
||||
|
||||
(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.))))
|
||||
|
||||
(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.))))
|
||||
|
||||
(defun inventory ()
|
||||
(cons 'items- (objects-at 'body *objects* *object-locations*)))
|
||||
|
||||
(defun game-repl ()
|
||||
(let ((cmd (game-read)))
|
||||
(unless (eq (car cmd) 'quit)
|
||||
(game-print (game-eval cmd))
|
||||
(game-repl))))
|
||||
|
||||
(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))))))
|
||||
|
||||
(defparameter *allowed-commands* '(look walk pickup inventory))
|
||||
|
||||
(defun game-eval (sexp)
|
||||
(if (member (car sexp) *allowed-commands*)
|
||||
(eval sexp)
|
||||
'(i do not know that command.)))
|
||||
|
||||
(defun tweak-text (lst caps lit)
|
||||
(when lst
|
||||
(let ((item (car lst))
|
||||
(rest (cdr lst)))
|
||||
(cond ((eql item #\space) (cons item (tweak-text rest caps lit)))
|
||||
((member item '(#\! #\? #\.)) (cons item (tweak-text rest t lit)))
|
||||
((eql item #\") (tweak-text rest caps (not lit)))
|
||||
(lit (cons item (tweak-text rest nil lit)))
|
||||
(caps (cons (char-upcase item) (tweak-text rest nil lit)))
|
||||
(t (cons (char-downcase item) (tweak-text rest nil nil)))))))
|
||||
|
||||
(defun game-print (lst)
|
||||
(princ (coerce (tweak-text (coerce (string-trim "() "
|
||||
(prin1-to-string lst))
|
||||
'list)
|
||||
t
|
||||
nil)
|
||||
'string))
|
||||
(fresh-line))
|
233
lisp/land-of-lisp/ch6/wizard_adventure_add_repl.lisp
Normal file
233
lisp/land-of-lisp/ch6/wizard_adventure_add_repl.lisp
Normal file
@ -0,0 +1,233 @@
|
||||
(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.))))
|
||||
|
||||
(defun describe-location (location nodes)
|
||||
(cadr (assoc location nodes)))
|
||||
|
||||
(defparameter *edges* '((living-room (garden west door)
|
||||
(attic upstairs ladder))
|
||||
(garden (living-room east door))
|
||||
(attic (living-room downstairs ladder))))
|
||||
|
||||
(defun describe-path (edge)
|
||||
`(there is a ,(caddr edge) going ,(cadr edge) from here.))
|
||||
|
||||
(defun describe-paths (location edges)
|
||||
(apply #'append (mapcar #'describe-path (cdr (assoc location edges)))))
|
||||
|
||||
(defparameter *objects* '(whiskey bucket frog chain))
|
||||
|
||||
(defparameter *object-locations* '((whiskey living-room)
|
||||
(bucket living-room)
|
||||
(chain garden)
|
||||
(frog garden)))
|
||||
|
||||
(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)))
|
||||
|
||||
(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)))))
|
||||
|
||||
(defparameter *location* 'living-room)
|
||||
|
||||
(defun look ()
|
||||
(append (describe-location *location* *nodes*)
|
||||
(describe-paths *location* *edges*)
|
||||
(describe-objects *location* *objects* *object-locations*)))
|
||||
|
||||
(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.))))
|
||||
|
||||
(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.))))
|
||||
|
||||
(defun inventory ()
|
||||
(cons 'items- (objects-at 'body *objects* *object-locations*)))
|
||||
|
||||
;; Let's now define a repl
|
||||
|
||||
;;(defun game-repl ()
|
||||
;; (loop (print (eval (read)))))
|
||||
|
||||
;; Well, that's the definition of a repl isn't it?
|
||||
;; It reads, then evals, then prints, and loops... This is easy.
|
||||
|
||||
;; Time to redefine the repl to work proper for us. Also, why loop?
|
||||
|
||||
(defun game-repl ()
|
||||
(let ((cmd (game-read)))
|
||||
(unless (eq (car cmd) 'quit)
|
||||
(game-print (game-eval cmd))
|
||||
(game-repl))))
|
||||
|
||||
(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))))))
|
||||
|
||||
;; (read-from-string) works like (read) but lets us read a syntax expression
|
||||
;; or any other basic Lisp data type from a string instead
|
||||
;; of directly from the console
|
||||
|
||||
;; The string we use for this is a tweaked version of a string
|
||||
;; we get from read-line
|
||||
|
||||
;; We tweak it by adding quotes around it using (concatenate)
|
||||
;; which can be used for cating strings together as well as
|
||||
;; some parens
|
||||
|
||||
;; Then we define a local function called (quote-it) which we can use to
|
||||
;; (quote) any arguments the player has in a command
|
||||
;; (remember that the single quote is shorthand for (quote )
|
||||
|
||||
;; Let's test
|
||||
|
||||
;; (game-read)
|
||||
;; walk east
|
||||
;; >> (WALK 'EAST)
|
||||
|
||||
;; Looks like it adds the parens and quotes it
|
||||
|
||||
;; Note that our read loop isn't perfect. If a player threw
|
||||
;; in an extra paren, it would go to hell
|
||||
|
||||
(defparameter *allowed-commands* '(look walk pickup inventory))
|
||||
|
||||
(defun game-eval (sexp)
|
||||
(if (member (car sexp) *allowed-commands*)
|
||||
(eval sexp)
|
||||
'(i do not know that command.)))
|
||||
|
||||
;; The first line checks to see if the command the user gives is in our
|
||||
;; list of allowed commands.
|
||||
|
||||
;; If we didn't do this, it would be easy to call Lisp commands
|
||||
;; from our custom repl from beyond the scope of this game
|
||||
|
||||
;; Our eval command doesn't protect against hacking 100%, but it's a start
|
||||
|
||||
;; Our game-print function will convert our symbol-based writing
|
||||
;; into properly capitalized text
|
||||
;; by having this function available, we can store
|
||||
;; the text in our game engine in the most comfortable format possible
|
||||
;; lists of symbols
|
||||
|
||||
;; this makes it easier to manipulate text
|
||||
|
||||
(defun tweak-text (lst caps lit)
|
||||
(when lst
|
||||
(let ((item (car lst))
|
||||
(rest (cdr lst)))
|
||||
(cond ((eql item #\space) (cons item (tweak-text rest caps lit)))
|
||||
((member item '(#\! #\? #\.)) (cons item (tweak-text rest t lit)))
|
||||
((eql item #\") (tweak-text rest caps (not lit)))
|
||||
(lit (cons item (tweak-text rest nil lit)))
|
||||
(caps (cons (char-upcase item) (tweak-text rest nil lit)))
|
||||
(t (cons (char-downcase item) (tweak-text rest nil nil)))))))
|
||||
|
||||
(defun game-print (lst)
|
||||
(princ (coerce (tweak-text (coerce (string-trim "() "
|
||||
(prin1-to-string lst))
|
||||
'list)
|
||||
t
|
||||
nil)
|
||||
'string))
|
||||
(fresh-line))
|
||||
|
||||
;; So this is a bit more complex
|
||||
|
||||
;; The first important part of code xecuted is in game-pring
|
||||
;; it converts the symbol list (containing the text whose layout we want to fix
|
||||
;; into a string with prin1-to-string
|
||||
|
||||
;; The "to-string" part means this function doesn't show the result on the screen
|
||||
;; but just returns it as a string
|
||||
;; The "1" means it will stay on a single line
|
||||
|
||||
;; Next game-print converts the string to a list of characters with (coerce)
|
||||
;; By coercing our string into a list, we can reduce the bigger goal
|
||||
;; of the function into a list-processing problem
|
||||
|
||||
;; In this case we're creaing a list of chars making up the text we want to fix
|
||||
|
||||
;; No we send the data to the list-eater function (tweak-text)
|
||||
;; Some of the arguments used in (game-print) are printed on their own line
|
||||
;; for clarity
|
||||
|
||||
;; to make it easier to see which argments are meant for which commands by
|
||||
;; looking at indentation
|
||||
|
||||
;; for instance, the "t" and "nil" arguments belong to tweak-text
|
||||
|
||||
;; (tweak-text) looks at each character in the list and modifies it as needed
|
||||
;; At the top of this function we define item and rest
|
||||
;; which we get by chewing off an item from the front of the sentence
|
||||
;; we're tweaking
|
||||
|
||||
;; Then the tweak-text function uses a cond to check the character at the
|
||||
;; top of the list for different conditions
|
||||
|
||||
;; The first checks whether the character is a space character
|
||||
;; if so, leave it alone and go to the next char
|
||||
;; if the char is a period, question mark, or exclamation point
|
||||
;; we turn on the (cap) parameter for the rest of the string
|
||||
;; (by using the value "t" as an argument in the recursive call) to
|
||||
;; indicate that the next symbol is at the beginning of a sentence
|
||||
;; and needs to be a capital letter
|
||||
|
||||
;; We also track whether we've encountered a quotation mark
|
||||
;; we do this because, infrequently, a symbol list is not
|
||||
;; adequate for encoding English text
|
||||
|
||||
;; Examples include having a comma as commas are not allowed
|
||||
;; in standard common lisp symbols
|
||||
|
||||
;; or product names with non-standard capitalization
|
||||
|
||||
;; In these cases we can fall back on using text strings
|
||||
|
||||
;; Example:
|
||||
;; (game-print '(not only does this sentence have a "comma," it also mentions the "iPad."))
|
||||
;; >> Not only does this sentence have a comma, it also mentions the iPad.
|
||||
|
||||
;; We tell the function to treat the capitlization as shown literally by
|
||||
;; turning off the lit variable in the recursive call
|
||||
;; As long as this value is set, (tweak-text) prevents
|
||||
;; the capitalization rules from being reached
|
||||
|
||||
;; Next (tweak-text) checks to see whether the next char is supposed to be
|
||||
;; capitalized. If it is, we use the char-upcase function to change the
|
||||
;; current character to uppercase (if it isn't already) before processing
|
||||
;; the next item in the list
|
||||
|
||||
;; If none of the other conditons were met, we know that the current char
|
||||
;; should be lowercase and we can convert it using the char-downcase function
|
||||
|
||||
;; After tweak-text is finished correcting the text in the list,
|
||||
;; (game-print) coerces it back into a proper string and princs it
|
||||
|
||||
;; (fresh-line) makes sure the next item appearing on the screen will start
|
||||
;; on a fresh line
|
||||
|
||||
;; Note: (read) and (eval) should be mostly avoided for production code
|
||||
;; Someone will be able to think up a way to break into it
|
Loading…
x
Reference in New Issue
Block a user