`(kakko ,man)

Find a guide into tomorrow by taking lessons from the past

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 を出すため、うざったいから止めてます。

今日はこんな感じ。