在 Land of Lisp
第11章結束處有一個使用了 loop
和 format
的代碼超級簡短的小遊戲, 書中稱其爲single paper game
, 意思是這段遊戲代碼能夠放在一頁紙上.app
本文主要介紹構成這段代碼的技術基礎: loop
和 format
的各類用法.less
這裏是代碼dom
(defun robots () (loop named main with directions = '((q . -65) (w . -64) (e . -63) (a . -1) (d . 1) (z . 63) (x . 64) (c . 65)) for pos = 544 then (progn (format t "~%qwe/asd/zxc to move, (t)eleport, (l)eave:") (force-output) (let* ((c (read)) (d (assoc c directions))) (cond (d (+ pos (cdr d))) ((eq 't c) (random 1024)) ((eq 'l c) (return-from main 'bye)) (t pos)))) for monsters = (loop repeat 10 collect (random 1024)) then (loop for mpos in monsters collect (if (> (count mpos monsters) 1) mpos (cdar (sort (loop for (k . d) in directions for new-mpos = (+ mpos d) collect (cons (+ (abs (- (mod new-mpos 64) (mod pos 64))) (abs (- (ash new-mpos -6) (ash pos -6)))) new-mpos)) '< :key #'car)))) when (loop for mpos in monsters always (> (count mpos monsters) 1)) return 'player-wins do (format t "~%|~{~<|~%|~,65:;~A~>~}|" (loop for p below 1024 collect (cond ((member p monsters) (cond ((= p pos) (return-from main 'player-loses)) ((> (count p monsters) 1) #\#) (t #\A))) ((= p pos) #\@) (t #\ ))))))
原書中該頁截圖以下: 函數
把上述代碼保存爲一個名爲 c11-robots.lisp
的文件, 而後啓動 CLISP
, 在 REPL
中加載該段文件, 加載成功後執行函數 (robots)
, 操做過程以下:oop
[1]> (load "c11-robots.lisp") ;; Loading file c11-robots.lisp ... ;; Loaded file c11-robots.lisp T [2]> (robots) | A | | | | A | | A | | A | | | | A | | | | A @ A | | A | | | | | | A | | | | A | | | qwe/asd/zxc to move, (t)eleport, (l)eave:
遊戲截圖:學習
圖中 @
表示玩家控制的角色, A
表示機器人, 隨機出現的 #
表示陷阱, 不論玩家仍是機器人誰碰到都會死, 機器人會一直追逐玩家, 一旦被追上就失敗, 玩家能夠引誘機器人去碰陷阱code
這段遊戲代碼主要使用了 loop
宏和 format
函數的功能, 後面咱們會按功能塊逐行解讀這段代碼, 不過在此以前, 須要瞭解關於 loop
宏和 format
函數相關語法的一些知識儲備.orm
先說 format
的語法結構, 就以上述代碼來講, 一般, 咱們會這樣調用 format
遊戲
(format t "~%qwe/asd/zxc to move, (t)eleport, (l)eave:")
這裏, t
表示標準輸出, 雙引號內的 ~%
是一個控制字符串, 表示換行, 波浪號~
所表明的就是控制字符串, 有些控制字符用來控制輸出的格式, 有些控制字符則做爲佔位符使用, 雙引號內其餘的文本內容表示直接輸出的字符串, 直接輸出便可.圖片
經常使用的一些控制字符串:
[4]> (format t "十進制數字 12 的二進制形式爲 ~b." 12) 十進制數字 12 的二進制形式爲 1100. NIL [5]>
[18]> (format t "輸出字符 ~c, 它的具體值由後面的參數決定." #\a) 輸出字符 a, 它的具體值由後面的參數決定. NIL [19]>
format 的嵌套語法, 按照 Land of Lisp
做者所說, 這個語法很瘋狂, 它以一種很是簡潔的形式表現出比較複雜的形式, 由於它具有一種循環結構能夠解析處理嵌套的列表. ~{
和 ~}
控制序列加一個列表, 會依次循環輸出列表中的元素, 這裏咱們先定義一個用 loop
生成的數字列表, 而後再用 format
的 "~{
~a
~}"
(1 2 3 4)
形式來循環輸出數字列表中的數字:
[6]> (defparameter *numbers* (loop repeat 10 collect (random 100))) *NUMBERS* [7]> *numbers* (37 27 8 47 42 13 32 86 7 73) [8]> (format t "~{ I see a number: ~a! ~}" *numbers*) I see a number: 37! I see a number: 27! I see a number: 8! I see a number: 47! I see a number: 42! I see a number: 13! I see a number: 32! I see a number: 86! I see a number: 7! I see a number: 73! NIL [9]>
增長一個換行控制序列 ~%
, 注意, 由於要換行的是每次輸出的內容, 因此 ~%
要寫在 ~{
和 ~}
內, 以下:
[9]> (format t "~{ I see a number: ~a!~% ~}" *numbers*) I see a number: 37! I see a number: 27! I see a number: 8! I see a number: 47! I see a number: 42! I see a number: 13! I see a number: 32! I see a number: 86! I see a number: 7! I see a number: 73! NIL [10]>
接下來咱們見識一下什麼是瘋狂的 format
, 用一行代碼輸出格式規整的數字表:
[12]> (format t "|~{~<|~%|~,33:;~2d ~>~}|" (loop for x below 100 collect x)) | 0 1 2 3 4 5 6 7 8 9 | |10 11 12 13 14 15 16 17 18 19 | |20 21 22 23 24 25 26 27 28 29 | |30 31 32 33 34 35 36 37 38 39 | |40 41 42 43 44 45 46 47 48 49 | |50 51 52 53 54 55 56 57 58 59 | |60 61 62 63 64 65 66 67 68 69 | |70 71 72 73 74 75 76 77 78 79 | |80 81 82 83 84 85 86 87 88 89 | |90 91 92 93 94 95 96 97 98 99 | NIL [13]>
咱們但願能在表格上方跟下方增長分割線條, 相似這樣的: ------------
, 那麼咱們單獨試驗一下:
[18]> (format t "~{~a~}~%" (loop repeat 32 collect #\-)) -------------------------------- NIL [19]>
很好, 符合預期, 那就跟前面的表格語句合併, 先試驗上方, 以下:
[17]> (format t "~{~a~}~%|~{~<|~%|~,33:;~2d ~>~}|" (loop repeat 32 collect #\-) (loop for x below 100 collect x)) -------------------------------- | 0 1 2 3 4 5 6 7 8 9 | |10 11 12 13 14 15 16 17 18 19 | |20 21 22 23 24 25 26 27 28 29 | |30 31 32 33 34 35 36 37 38 39 | |40 41 42 43 44 45 46 47 48 49 | |50 51 52 53 54 55 56 57 58 59 | |60 61 62 63 64 65 66 67 68 69 | |70 71 72 73 74 75 76 77 78 79 | |80 81 82 83 84 85 86 87 88 89 | |90 91 92 93 94 95 96 97 98 99 | NIL [18]>
再把下方的加進去:
[20]> (format t "~{~a~}~%|~{~<|~%|~,33:;~2d ~>~}|~%~{~a~}~%" (loop repeat 32 collect #\-) (loop for x below 100 collect x) (loop repeat 32 collect #\-)) -------------------------------- | 0 1 2 3 4 5 6 7 8 9 | |10 11 12 13 14 15 16 17 18 19 | |20 21 22 23 24 25 26 27 28 29 | |30 31 32 33 34 35 36 37 38 39 | |40 41 42 43 44 45 46 47 48 49 | |50 51 52 53 54 55 56 57 58 59 | |60 61 62 63 64 65 66 67 68 69 | |70 71 72 73 74 75 76 77 78 79 | |80 81 82 83 84 85 86 87 88 89 | |90 91 92 93 94 95 96 97 98 99 | -------------------------------- NIL [21]>
控制好換行 ~%
的位置, 就能夠輸出上述的表格.
實際上這幾個例子已經用到了部分 loop
, 接下來咱們探討一下 loop
的一些用法.
loop
的語法至關複雜, 好在咱們這裏只用到其中一一小部分, 在本文例程中用到的的 loop
形式有這麼幾種, 爲方便理解, 所有用實際例子代替解說, 本身多試幾遍應該就掌握了, 以下:
[48]> (loop with x = (+ 1 2) repeat 5 do (print x)) 3 3 3 3 3 NIL [49]>
[29]> (defparameter o (loop repeat 10 collect 2)) O [30]> o (2 2 2 2 2 2 2 2 2 2) [31]>
(for x below 10 ...)
[37]> (defparameter o (loop for x below 10 collect x)) O [38]> o (0 1 2 3 4 5 6 7 8 9) [39]>
(for x from 0 do ...)
[51]> (loop for i from 0 do (print i) when (= i 5) return 'oK) 0 1 2 3 4 5 OK [52]>
(for x in list ...)
[73]> (loop for i in '(100 25 35) sum i) 160 [74]>
[43]> (loop repeat 10 for x = 10.0 then (/ x 2) collect x) (10.0 5.0 2.5 1.25 0.625 0.3125 0.15625 0.078125 0.0390625 0.01953125) [44]>
[44]> (loop for i below 10 if (oddp i) do (print i) (print "OK")) 1 "OK" 3 "OK" 5 "OK" 7 "OK" 9 "OK" NIL [45]>
[45]> (loop for i below 10 when (oddp i) do (print i) (print "OK")) 1 "OK" 3 "OK" 5 "OK" 7 "OK" 9 "OK" NIL [46]>
[77]> (loop for i below 10 unless (oddp i) do (print i)) 0 2 4 6 8 NIL [78]>
[72]> (loop named outer for i below 10 do (progn (print "outer") (loop named inner for x below i do (print "**inner") when (= x 2) do (return-from outer 'kicked-out-of-all-the-way)))) "outer" "outer" "**inner" "outer" "**inner" "**inner" "outer" "**inner" "**inner" "**inner" KICKED-OUT-OF-ALL-THE-WAY [73]>
參見 named
的例程
[79]> (loop for i below 5 collect (list i)) ((0) (1) (2) (3) (4)) [80]> (loop for i below 5 collect i) (0 1 2 3 4) [81]>
[78]> (loop for i below 5 append (list 'z i)) (Z 0 Z 1 Z 2 Z 3 Z 4) [79]>
突然發現理解了上面例子中的這幾種用法, 那段遊戲代碼也就理解了. :)
不過單色的字符有些單調, 咱們打算把它們修改成彩色字符, 好比, 機器人用一種顏色, 玩家控制的角色用一種顏色, 陷阱用一種顏色. 這部分修改改天再寫, 先上一張彩色字符的截圖:
--結束