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

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

Common Lispを頑張る(24)

さあさあ、今日もやっていきます「Land of Lisp」。
前回はゲームの舞台の全ノードと全エッジを作成して、
図として出力するところまで行けたのでした。

部分的に図を出力する

複雑に絡み合ったノードからなる舞台のマップは感動的でしたが、
敵がいる場所も何もかもわかった状態で楽しい類のゲームではありません。
これまでに訪れた場所だけを表示するマップを作成したいところです。
訪れたノードを*visited-nodes*の記録してもらいます。

まずは、既知のノードからできるalistを作ります。

CL-USER> (defun known-city-nodes ()
           (mapcar (lambda (node)
                     (if (member node *visited-nodes*) ;対象が行ったことある場所なら
                         (let ((n (assoc node *congestion-city-nodes*))) ;nにnodeのalistを格納
                           (if (eql node *player-pos*)
                               (append n '(*)) ;プレイヤーの現在地なら*を付与
                               n)) ;そうでなきゃそのまま戻す
                         (list node '?))) ;行ったことある場所でなければ?をnodeに付与
                   (remove-duplicates ;引数のリストから重複を取り除く
                    (append *visited-nodes* 
                            (mapcan (lambda (node) ;mapcanは引数の関数の返すリストを全て繋ぎ合わせるらしい
                                      (mapcar #'car
                                              (cdr (assoc node
                                                          *congestion-city-edges*)))) ;訪れたことのあるノードから行ける
                                    *visited-nodes*)))))                               ;ノードのリストを作り、最初のmapcarに渡される。
KNOWN-CITY-NODES
CL-USER>

なんで今まで関数書きながらコメントしてこなかったんだろう…。
めっちゃ理解しやすい…。

さ、次は既知のノードから伸びているエッジのリストです。
ポイントとしては、実際にそのエッジを通っていないのにそこが
安全かどうかわかってはいけないということです。

CL-USER> (defun known-city-edges ()
           (mapcar (lambda (node) ;訪れたことのあるノードを処理に流し込む
                     (cons node (mapcar (lambda (x) ;処理に流し込まれたノードと内側のmapcar結果をcons
                                          (if (member (car x) *visited-nodes*) ;もし行ったことがあれば
                                              x                                ;そのままxを返して、
                                              (list (car x))))                 ;なかったら情報(cdr部分)を取っ払って返す
                                        (cdr (assoc node *congestion-city-edges*))))) ;内側のmapcar対象は、訪れたことあるノードから行けるノード(alist)
                   *visited-nodes*))
KNOWN-CITY-EDGES
CL-USER>

うむうむ。入れ子になっているmapcarも、もはや恐ろしいものではありません!

mapcan

先程出てきたmapcanの解説があるので見てみます。
コメントにしましたが、返されたリストを全て繋ぎ合わせたリストを返します。
当然、引数の関数は必ずリストを返さなければなりません。
入力リストの項目と出力リストの項目が必ずしも1対1だと限らない場合便利だそう。

例として、バーガーショップが出てきました。
メニューはバーガー、チーズバーガー、ダブルチーズバーガー
注文から必要なパティとチーズのリストを返す関数です。

CL-USER> (defun ingredients (order)
           (mapcan (lambda (burger)
                     (case burger
                       (single (list 'patty))
                       (double (list 'patty 'patty))
                       (double-cheese (list 'patty 'patty 'cheese))))
                   order))
INGREDIENTS
CL-USER> (ingredients '(single double-cheese double))
(PATTY PATTY PATTY CHEESE PATTY PATTY)
CL-USER>

ふむむ。一要素渡すたびにリストが返ってくるのを
全部まとめて一つにすると。appendみたいなもんですかね。

既知の部分だけの地図を描く

上で既知のノードとエッジだけを含むグラフを作る関数を作ったので、
それを図にする関数です。

CL-USER> (defun draw-known-city ()
           (ugraph->png "/Users/user/Desktop/Lisp/known-city" (known-city-nodes) (known-city-edges)))
DRAW-KNOWN-CITY
CL-USER>

ゲームを開始する時にこの関数も起動してもらいます。

CL-USER> (defun new-game ()
           (setf *congestion-city-edges* (make-city-edges))
           (setf *congestion-city-nodes* (make-city-nodes *congestion-city-edges*))
           (setf *player-pos* (find-empty-node))
           (setf *visited-nodes* (list *player-pos*))
           (draw-city)
           (draw-known-city))

NEW-GAME
CL-USER>

段々と完成してきました。Macの前でほくそ笑んでいます。

移動するための関数

ゲームを進めるための関数を作ります。
ノードを移動するものと、探し人がいると思ったら
それが当たりか判定するものが必要です。

CL-USER> (defun walk (pos)
           (handle-direction pos nil))
WALK
CL-USER> (defun charge (pos)
           (handle-direction pos t))
CHARGE
CL-USER>

上が移動するためのもの、下が判定してもらうものです。
殆ど変わりませんね。handle-directionに渡す第二引数だけです。

handle-directionを書いてみます。

CL-USER> (defun handle-direction (pos charging)
           (let ((edge (assoc pos ;edgeは現在地から通れるedgeのalistのリストを出して、posを含んでいれば行き先がedgeに入る。
                              (cdr (assoc *player-pos* *congestion-city-edges*)))))
             (if edge
                 (handle-new-place edge pos charging) ;
                 (princ "That location does not exist!"))))
HANDLE-DIRECTION
CL-USER>

じゃあ、handle-new-placeですね。

CL-USER> (defun handle-new-place (edge pos charging)
           (let* ((node (assoc pos *congestion-city-nodes*)) ;行き先のノードとそこの情報をnodeへ格納
                  (has-worm (and (member 'glow-worm node)              ;そこがヤバいノードで
                                 (not (member pos *visited-nodes*))))) ;かつ訪れたことがなければ
             (pushnew pos *visited-nodes*) ;着いたノードを訪問済みリストへ
             (setf *player-pos* pos)       ;現在地を更新
             (draw-known-city)             ;マップを再描画
             (cond ((member 'cops edge) (princ "You ran into the cops. Game over.")) ;ゲームオーバーエッジを通ってたら
                   ((member 'wumpus node) (if chargeing
                                              (princ "You found the Wumpus!")        ;探し人がいると覚悟して来たならクリア
                                              (princ "You ran into the Wumpus.")))   ;覚悟してなければゲームオーバー
                   (charging (princ "You wasted your last ballet. Game Over."))      ;外してもゲームオーバー
                   (has-worm (let ((new-pos (random-node)))                                ;ヤバいノードなら移動させられる
                               (princ "You ran into a Grow Worm Gang! You're now at ")     
                               (princ new-pos)
                               (handle-new-place nil new-pos nil))))))                     ;テレポートみたいなものなのでエッジは関係なし
HANDLE-NEW-PLACE
CL-USER>

できた!
早速プレイしてみます!

勉強が終わったのでゲーム!

CL-USER> (new-game)
NIL
CL-USER>

お、今回は無事に通ったようです。
f:id:programcat:20181003224938p:plain
マップも生成されました。
幸か不幸か進む道は一つです。12へ行ってみます。

CL-USER> (walk 12)
NIL
CL-USER>

f:id:programcat:20181003225146p:plain
ちなみにこんな感じでプレイしています。
f:id:programcat:20181003225706p:plain

退屈だと思うんで勝手にゲームクリアかゲームオーバーまでプレイします。

と思ったら、エラーがでました。

CL-USER> (charge 22)
; Evaluation aborted on #<SYSTEM::SIMPLE-UNBOUND-VARIABLE #x000000020035FCF1>.
CL-USER>

ぐむむ、あと少しでクリアなのに。
どうもchargeingとかいうアホな綴りをどこかで書いてるようです。
…handle-new-placeのまさにゲームクリア判定のところでタイポしてました。
修正して…。

CL-USER> (charge 22)
You found the Wumpus!
"You found the Wumpus!"
CL-USER>

よっしゃあ!ここ2日間の疲れも飛んでいくようです!

ちなみに最終的な探索マップがこちら。ヌルゲーでした。
f:id:programcat:20181003230902p:plain
ゲームオーバーエッジが機能しているかとか色々確かめたいことは
まだありますが、とりあえずここで締めます。
おやすみなさい!

追記;
とりあえずテレポートするノードが機能していませんでした。
どうやらmake-city-nodesの中でタイポしていて、
glow-wormをglow-wormsとしてリストに加えていました。
修正して色々試しましたところあとはちゃんと動いていそうです。

CL-USER> (walk 12)
You ran into a Grow Worm Gang! You're now at 26
NIL
CL-USER> (walk 10)
NIL
CL-USER> (walk 29)
NIL
CL-USER> (walk 12)
You ran into the cops. Game over.
"You ran into the cops. Game over."
CL-USER> (charge 29)
You ran into the cops. Game over.
"You ran into the cops. Game over."
CL-USER> (charge 10)
You wasted your last ballet. Game Over.
"You wasted your last ballet. Game Over."
CL-USER> 

これで健やかに眠れそうです。