5章 テキストゲームのエンジンを作る
5.1 魔法使いのアドベンチャー
- 周囲を見渡す
- 別の場所へ移動する
- オブジェクトを拾う
- 拾ったオブジェクトで何かする
以上4つの機能のうち3つを搭載したゲームエンジンをこの章では作る。この節では、*node*
をつくる。
連想リスト(alist)を使った機能
(defparameter *nodes* '((living-room (you are in the living-room. a wizard is snoring loudly on the couch.)) (garden (you are in a beautiful garden. there is a well in front of you.)) (attic (you are in the attic. there is a giant welding torch in the corner.))))
ここで文字列ではないのは、元となるデータ構造を、出力形式に縛られない形で最初から持っていて、自分のプログラミング言語の得意な点を活かしたコーディングができるようにするため。実際のプログラミングでもデータ構造が文字列オンリーといったシンプルな構造はありえない。
以下、定義したalistから情報を引っ張ってくるサンプル
[8]> (defun describe-location (location nodes) (cadr (assoc location nodes))) DESCRIBE-LOCATION [9]> (describe-location 'living-room *nodes*) (YOU ARE IN THE LIVING-ROOM. A WIZARD IS SNORING LOUDLY ON THE COUCH.) [10]> (describe-location 'test *nodes*) NIL [11]> (describe-location 'garden *nodes*) (YOU ARE IN A BEAUTIFUL GARDEN. THERE IS A WELL IN FRONT OF YOU.) [12]> (describe-location 'attic *nodes*) (YOU ARE IN THE ATTIC. THERE IS A GIANT WELDING TORCH IN THE CORNER.) [13]> (describe-location 'living-room *nodes*) (YOU ARE IN THE LIVING-ROOM. A WIZARD IS SNORING LOUDLY ON THE COUCH.)
5.2 通り道を描写する
- 通り道であるedgeの表現
- 準クオートで計算結果を埋め込んで返す
- データモードとコードモードを行ったり来たりしている
edgeの片割れを取り出す
[14]> (defparameter *edges* '((living-room (garden west door) (attic upstairs ladder)) (garden (living-room east door)) (attic (living-room downstairs ladder)))) *EDGES* [15]> (defun describe-path (edge) `(there is a ,(caddr edge) going ,(cadr edge) from here.)) DESCRIBE-PATH [16]> (describe-path '(garden west door)) (THERE IS A DOOR GOING WEST FROM HERE.) [17]> (describe-path '(attic upstairs ladder)) (THERE IS A LADDER GOING UPSTAIRS FROM HERE.) [18]> (describe-path '(living-room east door)) (THERE IS A DOOR GOING EAST FROM HERE.)
edgeを全部取り出す
[21]> (defun describe-paths (location edges) (apply #'append (mapcar #'describe-path (cdr (assoc location edges))))) DESCRIBE-PATHS [22]> (describe-paths 'living-room *edges*) (THERE IS A DOOR GOING WEST FROM HERE. THERE IS A LADDER GOING UPSTAIRS FROM HERE.)
mapcar
[23]> (mapcar #'describe-path '((GARDEN WEST DOOR) (ATTIC UPSTAIRS LADDER))) ((THERE IS A DOOR GOING WEST FROM HERE.) (THERE IS A LADDER GOING UPSTAIRS FROM HERE.))
apply
[24]> (apply #'append '((THERE IS A DOOR GOING WEST FROM HERE.) (THERE IS A LADDER GOING UPSTAIRS FROM HERE.))) (THERE IS A DOOR GOING WEST FROM HERE. THERE IS A LADDER GOING UPSTAIRS FROM HERE.)
5.3 特定の場所にあるオブジェクトを描写する
object(鎖とか蛇とか)をつくる
[26]> (defparameter *objects* '(whiskey bucket frog chain)) *OBJECTS*
オブジェクトの場所を定義する
[27]> (defparameter *object-locations* '((whiskey living-room) (bucket living-room) (chain garden) (frog garden))) *OBJECT-LOCATIONS*
特定の場所に何のオブジェクトがあるかを判定
[34]> (defun objects-at (loc objs obj-locs) (labels ((at-loc-p (obj) (eq (cadr (assoc obj obj-locs)) loc))) (remove-if-not #'at-loc-p objs))) OBJECTS-AT [35]> (objects-at 'living-room *objects* *object-locations*) (WHISKEY BUCKET) [36]> (objects-at 'garden *objects* *object-locations*) (FROG CHAIN) [37]> (objects-at 'attic *objects* *object-locations*) NIL
初期位置をliving-roomとして、全てを見渡す
[49]> (defparameter *location* 'living-room) *LOCATION* [50]> (defun look () (append (describe-location *location* *nodes*) (describe-paths *location* *edges*) (describe-objects *location* *objects* *object-locations*))) LOOK [51]> (look) (YOU ARE IN THE LIVING-ROOM. A WIZARD IS SNORING LOUDLY ON THE COUCH. THERE IS A DOOR GOING WEST FROM HERE. THERE IS A LADDER GOING UPSTAIRS FROM HERE. YOU SEE A WHISKEY ON THE FLOOR. YOU SEE A BUCKET ON THE FLOOR.)
5.5 ゲーム世界を動き回る
walkを実装して、色んな所を行き来しよう
[52]> (defun walk (direction) (let ((next (find direction (cdr (assoc *location* *edges*)) :key #'cadr))) (if next (progn (setf *location* (car next)) (look)) '(you cannot go that way.)))) WALK [53]> (walk 'west) (YOU ARE IN A BEAUTIFUL GARDEN. THERE IS A WELL IN FRONT OF YOU. THERE IS A DOOR GOING EAST FROM HERE. YOU SEE A FROG ON THE FLOOR. YOU SEE A CHAIN ON THE FLOOR.) [54]> (walk 'west) (YOU CANNOT GO THAT WAY.) [55]> (walk 'west) (YOU CANNOT GO THAT WAY.) [56]> (walk 'east) (YOU ARE IN THE LIVING-ROOM. A WIZARD IS SNORING LOUDLY ON THE COUCH. THERE IS A DOOR GOING WEST FROM HERE. THERE IS A LADDER GOING UPSTAIRS FROM HERE. YOU SEE A WHISKEY ON THE FLOOR. YOU SEE A BUCKET ON THE FLOOR.) [57]> (walk 'east) (YOU CANNOT GO THAT WAY.) [58]> (walk 'upstair) (YOU CANNOT GO THAT WAY.) [59]> (walk 'upstairs) (YOU ARE IN THE ATTIC. THERE IS A GIANT WELDING TORCH IN THE CORNER. THERE IS A LADDER GOING DOWNSTAIRS FROM HERE.) [60]> (walk 'upstairs) (YOU CANNOT GO THAT WAY.) [61]> (walk 'downstairs) (YOU ARE IN THE LIVING-ROOM. A WIZARD IS SNORING LOUDLY ON THE COUCH. THERE IS A DOOR GOING WEST FROM HERE. THERE IS A LADDER GOING UPSTAIRS FROM HERE. YOU SEE A WHISKEY ON THE FLOOR. YOU SEE A BUCKET ON THE FLOOR.) [62]> (walk 'east) (YOU CANNOT GO THAT WAY.) [63]> (walk 'west) (YOU ARE IN A BEAUTIFUL GARDEN. THERE IS A WELL IN FRONT OF YOU. THERE IS A DOOR GOING EAST FROM HERE. YOU SEE A FROG ON THE FLOOR. YOU SEE A CHAIN ON THE FLOOR.) [64]> (walk 'upstairs) (YOU CANNOT GO THAT WAY.)
5.6 オブジェクトを手に取る
pickup objectへpushする
[65]> (defun pickup (object) (cond ((member object (objects-at *location* *objects* *object-locations*)) (push (list object 'body) *object-locations*) `(you are now carrying the ,object)) (t '(you cannot get that.)))) PICKUP [66]> (walk 'east) (YOU ARE IN THE LIVING-ROOM. A WIZARD IS SNORING LOUDLY ON THE COUCH. THERE IS A DOOR GOING WEST FROM HERE. THERE IS A LADDER GOING UPSTAIRS FROM HERE. YOU SEE A WHISKEY ON THE FLOOR. YOU SEE A BUCKET ON THE FLOOR.) [67]> (pickup 'whiskey) (YOU ARE NOW CARRYING THE WHISKEY)
5.7 持っているものを調べる
bodyから何のobjectがあるかを調べる
[68]> (defun inventory () (cons 'items- (objects-at 'body *objects* *object-locations*))) INVENTORY [69]> (inventory) (ITEMS- WHISKEY) [70]> (pickup 'whiskey) (YOU CANNOT GET THAT.) [71]> (pickup 'bucket) (YOU ARE NOW CARRYING THE BUCKET) [72]> (inventory) (ITEMS- WHISKEY BUCKET)
今のゲームではモノを拾うだけ。何もしない。
5章まとめ
- ゲームの世界で表現する数学的なグラフ
- プレーヤーが行くことができる場所をノード
- 場所間を行き来する経路をエッジ
- このグラフは、変数 nodes に 連想リスト(alist)の形で持っておくことができる
- これによって、 ノード(場所)の名前からその場所の属性を引ける
- このゲームでは、属性として各ノード(場 所)の描写を格納しておいた
- assoc関数 により、キー(ここでは場所の名前)を使って alist からデータを引き出すことができる
- 準クオート を使えば、大きなデータの中に、その一部分を計算するためのコードを埋め込むことができる
- Lisp の関数には、他の関数を引数として受け取るものがある。これらは高階関数と呼ばれる
- mapcarは Common Lisp で最もよく使われる高階関数。
- alist 中の値を置き換えたければ、新しい要素をリストに pushするだけでいい。
- assocは最も新しい値だけを返すから。