人生は勉強ブログ

https://github.com/dooooooooinggggg

Common Lispの基礎的な文法3

あくまで自分用のメモ。

今後の実装の時に、簡単に振り返るための記事。

進めている本は、Land Of Lisp

Land of Lisp

Land of Lisp

前回の記事の続き。

blog.ishikawa.tech

今回は、5章のテキストゲームの外界との接点を作る。

print系

今までのプログラムでは、外界との接点がなかった。

ただ処理するだけで、その結果を評価したりで値っぽいものを見えていたが、例えば、clisp file_name.lispみたいな感じでやった時に、

何もprintされなかった。これは、入出力のコードを今まで書いてなかったからで、この章ではそれらを学ぶとともに、gameに最適なユーザーインターフェースを実装していく。

print

(print "foo")

;; "foo"
;; "foo"

1個目が実際に表示した値。

2個目は、replが評価した結果。

prin1

(prin1 "foo")
;; これは、改行なし

prin1というコマンドは、行末に改行がない。

Golangでいうと、

fmt.Println -> print

fmt.Printf -> prin1

みたいな感じ。実際のプログラムでは、prin1の方が用いられることが多い。

(defun say-hello ()
    (print "Please type your name:")
    (let ((name (read)))
        (prin1 "Nice to meet you ")
        (print name)))

(say-hello)

;; [6]> (say-hello)

;; "Please type your name:" bob
;; "Nice to meet you "
;; BOB
;; BOB
;; [7]> (say-hello)

;; "Please type your name:" "bob"
;; "Nice to meet you "
;; "bob"
;; "bob"

(defun add-five()
    (print "please enter a number:")
    (let ((num (read)))
        (print "When I add five I get")
        (print (+ num 5))))

(add-five)


(defun say-hello2 ()
    (princ "Please type your name:")
    (let ((name (read-line)))
        (princ "Nice to meet you ")
        (princ name)))

(say-hello2)

挙動に関しては、実際に動かして見ないとわからないと思うので、もし読者の方がいたら、コピペしてやってみることをお勧めする。

ゲーム上のユーザーインターフェース

eval

(defparameter *foo* (+ 1 2))

(eval *foo*)

すごく便利らしい。まだ使い方はわからないが、後述で、それらしき発見があった。

実装

game-repl

自分だけのREPLを作ってみる。 ゲームに専用のインターフェースを追加。

(defun game-repl()
    (loop (print (eval (read)))))

(game-repl)

;; 下のバージョンは、ただの無限ループではない。
;; よくわからないが、ユーザー入力がそのままコードになるということかな

(defun game-repl2()
    (let((cmd (game-read)))
        (unless (eq (car cmd) 'quit)
            (game-print(game-eval cmds))
            (game-repl2))))

にしても、こんなに再帰がすぐ出てくるのはいいな。

game-read

ここで読んでいるgame-readは、標準のreadにある不都合な二つの点を直すこと。

  1. 括弧をつけないとコマンドを入力できない。これは困るので、read-lineで読んだものに、こっち側で、()をつける

  2. クォートをつけるのがめんどくさい

(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の入力とする文字列は、rread-lineで得たものにちょっと加工したデータ。

game-eval

;; コマンドの制限
(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.)))

この実装はそんなに難しいものではない。

game-print

要件は、「いい感じにプリントしたい。」

(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 lit)))))))

(defun game-print (lst)
    (princ (coerce (tweak-text (coerce (string-trim "() "
                        (prin1-to-string lst))
                    'list)
                t
                nil)
            'strings))
    (fresh-line))

この実装は少々複雑。

これは、実際に動かしてみるとわかるが、引数として渡ってきた文字列を、いい感じ(タブルクォーテーションなしで、大文字にすべきとこは大文字、そうでないものは小文字、のように。)

次は、6.5章、lambda.

blog.ishikawa.tech