經過 Land of Lisp 中的超簡短字符遊戲例程學習 loop 和 format

經過 Land of Lisp 中的超簡短字符遊戲例程學習 Common Lisp 的 loop 和 format

介紹

Land of Lisp 第11章結束處有一個使用了 loopformat 的代碼超級簡短的小遊戲, 書中稱其爲single paper game, 意思是這段遊戲代碼能夠放在一頁紙上.app

本文主要介紹構成這段代碼的技術基礎: loopformat 的各類用法.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

遊戲操做

  • 方向鍵:
    • qwe: 左上,上,右上
    • a d: 左,右
    • zxc: 左下,下,右下
  • 其餘鍵:
    • t 瞬移到隨機位置,
    • l 離開遊戲

代碼解讀

這段遊戲代碼主要使用了 loop 宏和 format 函數的功能, 後面咱們會按功能塊逐行解讀這段代碼, 不過在此以前, 須要瞭解關於 loop 宏和 format 函數相關語法的一些知識儲備.orm

format 語法的知識儲備

先說 format 的語法結構, 就以上述代碼來講, 一般, 咱們會這樣調用 format遊戲

(format t "~%qwe/asd/zxc to move, (t)eleport, (l)eave:")

這裏, t 表示標準輸出, 雙引號內的 ~% 是一個控制字符串, 表示換行, 波浪號~所表明的就是控制字符串, 有些控制字符用來控制輸出的格式, 有些控制字符則做爲佔位符使用, 雙引號內其餘的文本內容表示直接輸出的字符串, 直接輸出便可.圖片

經常使用的一些控制字符串:

  • ~a
  • ~b 顯示二進制數, 例子以下:
[4]> (format t "十進制數字 12 的二進制形式爲 ~b." 12)
十進制數字 12 的二進制形式爲 1100.
NIL
[5]>
  • ~c 做爲字符的佔位符, 由後面參數提供, 會顯示一個小寫字符, 例子以下:
[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 的語法至關複雜, 好在咱們這裏只用到其中一一小部分, 在本文例程中用到的的 loop 形式有這麼幾種, 爲方便理解, 所有用實際例子代替解說, 本身多試幾遍應該就掌握了, 以下:

  • with 用於定義一個局部變量
[48]> (loop with x = (+ 1 2) repeat 5 do (print x))                                                                                                           

3 
3 
3 
3 
3 
NIL
[49]>
  • repeat 最簡單的循環
[29]> (defparameter o (loop repeat 10 collect 2))
O
[30]> o
(2 2 2 2 2 2 2 2 2 2)
[31]>
  • for 用於設置循環變量

(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]>
  • then 不知道該怎麼描述
[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]>
  • if 用於設置條件
[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]>
  • when 用於設置條件
[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]>
  • unless 用於設置條件
[77]> (loop for i below 10 unless (oddp i) do (print i))                                                                                                      

0 
2 
4 
6 
8 
NIL
[78]>
  • named 至關於一個標籤, 用於設置返回點
[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]>
  • return-from 返回到由 named 定義的標籤處

參見 named 的例程

  • collect 收集單個元素, 返回由單個元素組成的列表
[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]>
  • append 收集單個元素追加到列表中, 返回列表
[78]> (loop for i below 5 append (list 'z i))
(Z 0 Z 1 Z 2 Z 3 Z 4)
[79]>

突然發現理解了上面例子中的這幾種用法, 那段遊戲代碼也就理解了. :)

不過單色的字符有些單調, 咱們打算把它們修改成彩色字符, 好比, 機器人用一種顏色, 玩家控制的角色用一種顏色, 陷阱用一種顏色. 這部分修改改天再寫, 先上一張彩色字符的截圖: 彩色字符

--結束

相關文章
相關標籤/搜索