Lisp Game Programming 再履修 <その2>
いつも行くガソリンスタンドのバイトの兄ちゃん(工学部3年)と話しをしたら、「今、Pythonの勉強してるんすよ!」って。
「ふーん。俺、Lisp・・・・」と言いかけてやめた。
ライブラリが充実していて、一押しだそうな。
随分遠くに来ちゃった感があるな。(遠い目)と、「たそがれ感満載」でスタンドを後にした際、頭の中ではこの音楽が流れていた。
https://www.youtube.com/watch?v=g7l67VKDL8w
今時の若い人は知らないだろうなぁ。いい歌手だったなぁ。(また遠い目)
注)よくカラオケで歌いました。(爆)
いかんいかん。気を取り直して行ってみよう。少数派には少数派の美学があらぁな!
Lispbuilder-sdl を利用したゲームを作成する時のゲームフレームやイメージデータの読み込みの方法は過去にブログに掲載している。
ゲームフレーム ⇒
http://tomekame0126.hatenablog.com/entry/2014/06/26/222706
イメージデータの読み込み ⇒
http://tomekame0126.hatenablog.com/entry/2014/06/28/024155
なので、今回はこのプログラムの特徴的な関数を見てみよう。
;; step1 <Exchange Char For Element> ;; ----------------------------------------------------------------------------------------------- (defun Char->element (char) (ecase char (#\space 'empty) (#\# 'wall) (#\- 'dirt) (#\o 'rock) (#\* 'diamond) (#\@ 'entry) (#\& 'exit)))
このコードは、読み込んだマップデータのキャラクタを実際のイメージデータの名前に合わせるためのもので、lisp では文字を扱う場合 #\ を文字前につける。(当然です)
注)¥はバックスラッシュで表示されるはず
http://www.fireproject.jp/feature/common-lisp/data-structure/string.html
順番は前後するが、次はこれ、マップのロード。
;; step1 <Map Load> ;; ----------------------------------------------------------------------------------------------- (defun Load-map (level) (let ((filename (format nil "C:\\work\\Levels\\level~2,'0d.txt" level))) ; filename <- level01~03.txt (with-open-file (stream filename) ; fileopen level01~03.txt (loop for line = (read-line stream nil nil) ; as -> for ; read 1 line until (null line) ; if true loop stop collect line into lines ; lines <- line finally (return (Parse-map lines)))))) ; goto Parse-map()
マップ(level01.txt level02.txt level03.txt)のどれかを level で指定 ( or 1 2 3) し、マップファイルをOPEN。
そして、1行ずつ読み込み(行の最後のnullまで)、1行ずつlinesに集めていく。
イケてるのは loop の使い方。
collect で集めた lines をそのまま return で Parse-map 関数に投げるなんざ、「 Lisp ってのはこう書くのさ! 」ってコードが語っている。職人だね。
loop ⇒
http://smpl.seesaa.net/article/29800843.html
with-open-file ⇒
http://d.hatena.ne.jp/hiro_nemu/20090425/1240667723
最後は、lines にまとめたデータを Parse-map 関数に returnして、この関数から脱出。(脱獄ではない!)
注); as -> for とあるのは、もともとは for の部分が as として書かれていたため、いつも使っている for にしただけ
そして、読み込んだ lines の構文解析。
;; step1 <Map Parse> ;; ----------------------------------------------------------------------------------------------- (defun Parse-map (lines) (let* ((width (length (first lines))) ; width <- length of 1 line (height (length lines)) ; height <- number of lines (data (make-array (list height width))) ; data <- (height width) (entry nil) (exit nil)) ; (diamond-count 0)) (loop for y below height ; decf height for line in lines ; line <- lines do (loop for x below width ; decf width for element = (Char->element (aref line x)) ; as -> for ; exchange char for element do (setf (aref data y x) element) ; data <- element when (eql element 'entry) do (setf entry (cons x y)) ; set entry position (x,y) (setf (aref data y x) 'empty) ; data <- empty when (eql element 'exit) do (setf exit (cons x y)))) ; set exit position (x,y) ; when (eql element 'diamond) ; do (incf diamond-count))) ; diamond +1 (make-instance 'level ; level instance :width width :height height :data data :entry-position entry :exit-position exit))) ; :diamond-count diamond-count)))
lines リストの1行目の length(40) を width とし、リスト全体の length(22) を height に設定。
data は 22 × 40 の配列として設定。
entry(スタート)と exit(ゴール)には nil (初期値) を設定しておく。
ループでは増分の方向を負として、22 から 0 に向かって y を減らしつつ、lines から1 line 読み込む。
読み込んだら、40 から 0 に向かって x を減らしつつ、element に line の x 番目を読み込む。
element を data(22×40)にセットし、element が 'entry のときは nil に設定していた entry にその位置(コンス)をセット。
また 'exit であれば exit にその位置(コンス)をセットする。
最後は、level のインスタンス。
width(横幅 : 40)、height(高さ : 22) data(配列:'empty etc)、entry(入口位置:コンス)、exit(出口位置:コンス)の情報をもつインスタンスを生成。
あっ、diamond-count は今の段階ではコメントアウトしないと SBCL が warning を出すため、うざったいから止めてます。
今日はこんな感じ。