プログラミングを頑張る日記

プログラミングを勉強して、ハッカーになろう

Common Lispを頑張る(11)

今日は早く帰れました。
今日もCommon Lispの勉強をします。

昨日のリベンジ

昨日は下記の関数が無限ループしてしまったのでやる気を無くして寝ました。

(defun tweak-text (lst)
	   (let ((item (car lst))
		 (rest (cdr lst)))
	     (cond ((eql item #\IDEOGRAPHIC_FULL_STOP)
		    (cons item #\newline))
		   (t (tweak-text rest)))))

改めて見るとどうしてなのかは一目瞭然ですね。
処理を終了する箇所が全くありません。
昨日のことがあるので、ちょっとビビりながら改修します。

CL-USER> (defun tweak-text (lst)
	   (let ((item (car lst))
		 (rest (cdr lst)))
	     (when lst
	       (cond ((eql item #\IDEOGRAPHIC_FULL_STOP)
		    (cons item #\newline))
		   (t (tweak-text rest))))))
TWEAK-TEXT
CL-USER> (princ (tweak-text '(ど う だ 。)))
NIL
NIL
CL-USER>

どうだ…?最終的にwhenによる判定でのnilが返ってきてますが…。
ああ、これじゃダメか。consで改行を付け足したitemとrestを繋がないと、
あとwhenでの判定の位置も変ですね。

CL-USER> (defun tweak-text (lst)
	   (when lst
	     (let ((item (car lst))
		   (rest (cdr lst)))
	       (cond ((eql item #\IDEOGRAPHIC_FULL_STOP)
		      (cons item #\newline)
		      (cons item (tweak-text rest)))
		     (t (cons item (tweak-text rest)))))))
WARNING: redefining COMMON-LISP-USER::TWEAK-TEXT in DEFUN
TWEAK-TEXT
CL-USER> (princ (tweak-text '(ど う だ 。)))
(ど う だ 。)
(ど う だ 。)
CL-USER>

むむむ…。通ってなさそうですね…改行ルートを。

CL-USER> (defun tweak-text (lst)
	   (when lst
	     (let ((item (car lst))
		   (rest (cdr lst)))
	       (cond ((eql item #\IDEOGRAPHIC_FULL_STOP)
		      (cons item #\newline) (princ "ここは?")
		      (cons item (tweak-text rest)))
		     (t (cons item (tweak-text rest)) (princ "通った。"))))))
WARNING: redefining COMMON-LISP-USER::TWEAK-TEXT in DEFUN
TWEAK-TEXT
CL-USER> (tweak-text '(ど う だ 。))
通った。通った。通った。通った。
"通った。"
CL-USER> 

文字数分処理は行われていて…ただし改行ルートを通っていません。
うん?characterで渡してないからかな?
ちゃんとgame-print(予定)に組み込んで実行してみます。
…あー!consしたlstをrestにconsしてないからだ!
多分そうですよね。修正して試してみます。characterじゃないってのも
もちろんありそうだけど、character渡しても#\newlineが出なかったので気づきました。

CL-USER> (defun tweak-text (lst)
	   (when lst
	     (let ((item (car lst))
		   (rest (cdr lst)))
	       (cond ((eql item #\IDEOGRAPHIC_FULL_STOP)
		      (cons (cons item #\newline) (tweak-text rest)))
		     (t (cons item (tweak-text rest)))))))
WARNING: redefining COMMON-LISP-USER::TWEAK-TEXT in DEFUN
TWEAK-TEXT
CL-USER> (princ (coerce (tweak-text (coerce (string-trim "()" (prin1-to-string '(こっちは。どうだ。))) 'list)) 'string))
; Evaluation aborted on #<TYPE-ERROR expected-type: CHARACTER
             datum: (#\IDEOGRAPHIC_FULL_STOP . #\Newline)>.
CL-USER> (defun tweak-text (lst)
	   (when lst
	     (let ((item (car lst))
		   (rest (cdr lst)))
	       (cond ((eql item #\IDEOGRAPHIC_FULL_STOP)
		      (cons (cons item '(#\newline)) (tweak-text rest)))
		     (t (cons item (tweak-text rest)))))))
WARNING: redefining COMMON-LISP-USER::TWEAK-TEXT in DEFUN
TWEAK-TEXT
CL-USER> (princ (coerce (tweak-text (coerce (string-trim "()" (prin1-to-string '(こっちは。どうだ。))) 'list)) 'string))
; Evaluation aborted on #<TYPE-ERROR expected-type: CHARACTER datum: (#\IDEOGRAPHIC_FULL_STOP #\Newline)>.
CL-USER>

だめか…。最初はコンスセルが来たから怒られてその後はリストが来て怒られてますかね。

CL-USER> (cons 'a '(b c d))
(A B C D)
CL-USER> (cons '(a b) '(c d))
((A B) C D)
CL-USER>

car部分をリストにしてconsするとこうなっちゃうんですね…。
おっと、じゃあcdr部分に#\newlineをくっつければ良いのかな。
句点を見つけるたびに無駄な一周をすることになりますが、試してみます。

CL-USER> (defun tweak-text (lst)
	   (when lst
	     (let ((item (car lst))
		   (rest (cdr lst)))
	       (cond ((eql item #\IDEOGRAPHIC_FULL_STOP)
		      (cons item (tweak-text (cons '#\newline rest))))
		     (t (cons item (tweak-text rest)))))))
WARNING: redefining COMMON-LISP-USER::TWEAK-TEXT in DEFUN
TWEAK-TEXT
CL-USER> (princ (coerce (tweak-text (coerce (string-trim "()" (prin1-to-string '(こっちは。どうだ。))) 'list)) 'string))
こっちは。
どうだ。
"こっちは。
どうだ。
"
CL-USER>

できた…。よし、game-printを作ります。

CL-USER> (defun game-print (lst)
	   (princ
	    (coerce
   	     (tweak-text
	      (coerce
	       (string-trim "()"
			    (prin1-to-string lst))
	       'list))
	     'string)))
WARNING: redefining COMMON-LISP-USER::GAME-PRINT in DEFUN
GAME-PRINT
CL-USER> (game-print '(完成。ここまでに一時間半かかりました。長かった…。))
完成。
ここまでに一時間半かかりました。
長かった...。
"完成。
ここまでに一時間半かかりました。
長かった...。
"
CL-USER>

うおおお!できました!ポンコツのぐだぐだを晒し続けましたが
なんとかできました!
まあ、このぐだぐだこそがリアルタイムでやってることの特徴ということで。
いつかスラスラ書けるようになったら、見てくださっている人達と一緒に
感慨深くなりたいものです。なってください、頑張ります。

専用インターフェース

さて、これまでの関数たちをすべて組み合わせます。
本当ならずっとslimeを起動しているのでもうgame-replを起動できる算段でしたが、
昨日の無限ループで一回死んだのでおさらいがてら全部の関数を載せます。
あ、ゲーム部分は流石に一人でおさらいします。ただでさえグダグダやりすぎたので。

CL-USER> (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))))))
GAME-READ
CL-USER> (defparameter *allowed-command* '(look walk pickup inventory))
*ALLOWED-COMMAND*
CL-USER> (defun game-eval (sexp)
	   (if (member (car sexp) *allowed-command*)
	       (eval sexp)
	       '(そのコマンドは存在しません。)))
GAME-EVAL
CL-USER> (defun game-repl ()
	   (let ((cmd (game-read)))
	     (unless (eq (car cmd) 'quit)
	       (game-print (game-eval cmd))
	       (game-repl))))
GAME-REPL
CL-USER>

読みづらいブログだなあ!と怒りながら過去記事を漁りました。
さて…使ってみます!

CL-USER> (game-repl)
look
あなたははじまりの村にいる。
 どこからか牛の鳴き声が聞こえる。
 東 には 街道 があり、 活気ある街 へと続いている。
 この場所には 牛のミルク
 があるかもしれない。
walk 東
あなたは活気ある街にいる。
 大通りには露店が並んでいて、あちこちで交渉が行われている。
 西 には 街道 があり、 はじまりの村 へと続いている。
 街の中心
 には 橋 があり、 王様の城 へと続いている。
 この場所には 旅人の服 があるかもしれない。
 この場所には 栗毛の馬 があるかもしれない。
pickup 旅人の服
あなたは 旅人の服 を手に入れた!
そのコマンドは存在しません。
inventory
持ち物... 旅人の服
そのコマンドは存在しません。
quit
NIL
CL-USER>

ちゃんと動いてはくれましたが、問題点がいくつか。
・謎の改行が健在であること(例:牛のミルク、街の中心のあと)。
・最後が句点で終わっていないと改行されない。

謎の改行についてはさっぱりわかりませんが、
句点が無いと改行されない問題は簡単にどうにかできそうです。
game-printが最後にfresh-lineを呼べばいいだけですが…。
それだと句点があるときに改行が1個多くなってしまいそうですね。
tweak-textでrestがnilなら改行しても同様かな…。
良い解決策が浮かばないので、fresh-lineを足すだけにします。

CL-USER> (defun game-print (lst)
	   (princ
	    (coerce
   	     (tweak-text
	      (coerce
	       (string-trim "()"
			    (prin1-to-string lst))
	       'list))
	     'string))
	   (fresh-line))
WARNING: redefining COMMON-LISP-USER::GAME-PRINT in DEFUN
GAME-PRINT
CL-USER> (game-repl)
inventory
持ち物... 旅人の服
pickup 旅人の服
それはここには無いようだ。
walk 西
あなたははじまりの村にいる。
 どこからか牛の鳴き声が聞こえる。
 東 には 街道 があり、 活気ある街 へと続いている。
 この場所には 牛のミルク
 があるかもしれない。
pickup 牛のミルク
あなたは 牛のミルク を手に入れた!

お!句点の時に恐れていた改行がありませんでした。
そういえばfresh-lineはそこが文頭じゃない時に改行、みたいな解説が
以前にあった気がします。気の利いた関数でした。

今日はここまでです。
Lisperへの道は遠いことを改めて自覚した日でした。おやすみなさい。