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

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

Common Lispを頑張る(28)

今日は技術書典に行ってきました。
Lispの本は見つけられませんでしたが、面白そうな本やTwitterで一方的に知っている人を見て
テンションがあがっています。

それはともかく「Land of Lisp」やっていきます。
作成中のゲームの敵モンスター達に関する関数を定義していきます。

共通部分

色んな種類のモンスターが登場するゲームになる予定のようですが、
それらのモンスターにも共通部分があります。何より体力がなくなったら死ぬとか。
まずは体力という概念を持つジェネリックなモンスターを作成する関数です。

CL-USER> (defstruct monster (health (randval 10)))
STYLE-WARNING:
   Previously compiled call to COMMON-LISP-USER::MONSTER-HEALTH could not be
   inlined because the structure definition for COMMON-LISP-USER::MONSTER was
   not yet seen. To avoid this warning, DEFSTRUCT should precede references to
   the affected functions, or they must be declared locally notinline at each
   call site.
MONSTER
CL-USER>

むむむ、警告が出ました。monster-healthが含まれている関数が不味い気がします。
先立って構造体を作っていかなくちゃいけなかったんでしょうか。(英語が苦手で良くわからん)
とりあえず続けます。後々怒られたらmonster-healthを使っている関数を宣言し直します。

攻撃のダメージを受けた時にモンスターの体力を減らす関数も必要です。

CL-USER> (defmethod monster-hit (m x)
           (decf (monster-health m) x)
           (if (monster-dead m)
               (progn (princ "You killed the ")
                      (princ (type-of m))
                      (princ "! "))
               (progn (princ "You hit the ")
                      (princ (type-of m))
                      (princ ", knocking off ")
                      (princ x)
                      (princ " health points!"))))
#<STANDARD-METHOD COMMON-LISP-USER::MONSTER-HIT (T T) {1004E6C1E3}>
CL-USER>

defmethodを使うことで、モンスターに特有のメッセージを付け加えることができますね。
type-ofは、型を調べることのできる関数だそうです。

CL-USER> (type-of 7)
(INTEGER 0 4611686018427387903)
CL-USER> (type-of 'a)
SYMBOL
CL-USER> (type-of "aaa")
(SIMPLE-ARRAY CHARACTER (3))
CL-USER> (type-of (make-monster))
MONSTER
CL-USER>

参考書の例より返ってくる情報が多いです。しかも多値じゃなくてリストで返ってきています。
今はmake-monsterをしてもMONSTERが返ってくるだけですが、種類によって変わるとのこと。
処理系のせいでしょうか、参考書と挙動が違うので要注意ですね。

あとはmonster-showとmonster-attackを定義しておきます。

CL-USER> (defmethod monster-show (m)
           (princ "A fierce ")
           (princ (type-of m)))
#<STANDARD-METHOD COMMON-LISP-USER::MONSTER-SHOW (T) {1004F7EDD3}>
CL-USER> (defmethod monster-attack (m))
#<STANDARD-METHOD COMMON-LISP-USER::MONSTER-ATTACK (T) {1004FDD403}>
CL-USER>

monster-attackは何もしないメソッドです。モンスターの種類ごとに攻撃は違うので、
ジェネリックな攻撃を定義しても意味が無いからだそう。
単にそういうメソッドがあることを示すためだけの定義ということです。

ジェネリックなモンスターの定義はこれで完了、ここから具体的なモンスターを作っていきます。

モンスターの具体的な定義

まずはゲームのタイトルにもなっているオークを作ります。
オークは攻撃力はありますが、単細胞でありそんなにほっといても害がないそう。
ただし攻撃力にばらつきがあって、凄い強いやつもいるのでそれには注意。
オークの定義にはdefstructのまだ見ていない機能を使うみたいです。
それは、monsterで定義された全てのスロットをorcの定義に取り込むことです。
おお、オブジェクト指向っぽいですね。

CL-USER> (defstruct (orc (:include monster)) ;monster構造体の定義を取り込んでいる
           (club-level (randval 8)))  ;orcに特有の特徴(棍棒の攻撃力)を定義
ORC
CL-USER> (push #'make-orc *monster-builders*) ;モンスターを定義する関数を配列に格納
(#<FUNCTION MAKE-ORC>)
CL-USER>

さらに、オークに特殊化したmonster-showとmonster-attackを作成します。

CL-USER> (defmethod monster-show ((m orc))
           (princ "A wicked orc with a level ") ;オークは攻撃力にばらつきがあるので
           (princ (orc-club-level m))           ;攻撃力を表示する。
           (princ " club"))
#<STANDARD-METHOD COMMON-LISP-USER::MONSTER-SHOW (ORC) {10054A4523}>
CL-USER> (defmethod monster-attack ((m orc))
           (let ((x (randval (orc-club-level m))))
             (princ "An orc swings his club at you and knocks off ")
             (princ x)
             (princ " of your health points. ")
             (decf *player-health* x)))
#<STANDARD-METHOD COMMON-LISP-USER::MONSTER-ATTACK (ORC) {1005738E13}>
CL-USER>

次はヒドラです。頭がたくさんある伝説上の怪物ですね。
頭を切り落とすことで反撃ができます。手強いことに1ラウンドごとに新しい頭を生やすそう。

CL-USER> (defstruct (hydra (:include monster))) 
HYDRA
CL-USER> (push #'make-hydra *monster-builders*)
(#<FUNCTION MAKE-HYDRA> #<FUNCTION MAKE-ORC>)
CL-USER> (defmethod monster-hit ((m hydra) x)
           (decf (monster-health m) x)
           (if (monster-dead m)
               (princ "The corpse of the fully decapitated and decapacitated hydra falls to the floor!")
           (progn (princ "You lop off ")
                  (princ x)
                  (princ " of the hydra's heads! "))))
#<STANDARD-METHOD COMMON-LISP-USER::MONSTER-HIT (HYDRA T) {1002AA3313}>
CL-USER> (defmethod monster-attack ((m hydra))
           (let ((x (randval (ash (monster-health m) -1)))) ;頭の数に応じたダメージ
             (princ "A hydra attacks you with ")
             (princ x)
             (princ " of its heads! It also grows back one more head! ")
             (incf (monster-health m)) ;頭の数(体力)を増やす
             (decf *player-health* x)))
#<STANDARD-METHOD COMMON-LISP-USER::MONSTER-ATTACK (HYDRA) {1002D6C9F3}>
CL-USER>

厄介な敵ですね。頭10個だったりしたら恐ろしいです。

次に作るのはスライムです。
こいつは特殊能力を持っていて、足にくっついて他のモンスターの攻撃を避けられなくするか、
粘性の液体を顔に吹きかけてくるそう。ゲーム上どういう攻撃になるのでしょうか。

CL-USER> (defstruct (slime-mold (:include monster))
           (sliminess (randval 5))) ;スライム特有のパラメータ、ベタベタ度
SLIME-MOLD
CL-USER> (push #'make-slime-mold *monster-builders*)
(#<FUNCTION MAKE-SLIME-MOLD> #<FUNCTION MAKE-HYDRA> #<FUNCTION MAKE-ORC>)
CL-USER> (defmethod monster-show ((m slime-mold))
           (princ "A slime mold with a sliminess of ")
           (princ (slime-mold-sliminess m))) ;ベタベタ度を追加で表示
#<STANDARD-METHOD COMMON-LISP-USER::MONSTER-SHOW (SLIME-MOLD) {100323CEE3}>
CL-USER> (defmethod monster-attack ((m slime-mold))
           (let ((x (randval (slime-mold-sliminess m))))
             (princ "A slime mold wrap around your legs and decreases your agility by ")
             (princ x)
             (princ "! ")
             (decf *player-agility* x)
             (when (zerop (random 2))
               (princ "It also squirts in your face, talking away a health point! ")
               (decf *player-health*))))
#<STANDARD-METHOD COMMON-LISP-USER::MONSTER-ATTACK (SLIME-MOLD) {100361BE83}>
CL-USER>

イヤラシイ敵です。ベタベタ攻撃だけでなく、二分の一の確率で1ダメージを与えてきます。

最後はブリガンドという狡猾なモンスターです。山賊とかいう意味みたいですね。

CL-USER> (defstruct (brigand (:include monster)))
BRIGAND
CL-USER> (push #'make-brigand *monster-builders*)
(#<FUNCTION MAKE-BRIGAND> #<FUNCTION MAKE-SLIME-MOLD> #<FUNCTION MAKE-HYDRA>
 #<FUNCTION MAKE-ORC>)
CL-USER> (defmethod monster-attack ((m brigand))
           (let ((x (max *player-health* *player-agility* *player-strength*)))
             (cond ((= x *player-health*)
                    (princ "A brigand hits you with his slingshot, talking off 2 health points! ")
                    (decf *player-health* 2))
                   ((= x *player-agility*)
                    (princ "A brigand catches your leg with his whip, talking off 2 agility points! ")
                    (decf *player-agility* 2))
                   ((= x *player-strength*)
                    (princ "A brigand cuts your arm with his whip, talking off 2 strength points! ")
                    (decf *player-strength* 2)))))

#<STANDARD-METHOD COMMON-LISP-USER::MONSTER-ATTACK (BRIGAND) {100442F803}>
CL-USER>

一番高いパラメーターを2ずつ削ってくる、これまたイヤラシイ敵です。
さっさと倒したいですね。

ゲーム開始

ゲームを始めるには、orc-battle関数を呼べばよいです。定義したのが凄い昔のような気がします。

CL-USER> (orc-battle)
You are a valiant knight with a health of 30, an agility of 30, and a strength of 30
Your foes:
   1. (Health=6) A fierce BRIGAND
   2. (Health=2) A fierce BRIGAND
   3. (Health=8) A wicked orc with a level 3 club
   4. (Health=1) A fierce BRIGAND
   5. (Health=5) A fierce HYDRA
   6. (Health=8) A fierce BRIGAND
   7. (Health=9) A fierce BRIGAND
   8. (Health=10) A fierce BRIGAND
   9. (Health=6) A slime mold with a sliminess of 5
   10. (Health=9) A wicked orc with a level 7 club
   11. (Health=2) A fierce BRIGAND
   12. (Health=7) A fierce BRIGAND
Attack style: [s]tab [d]ouble swing [r]oundhouse:

おお、無事に始まりました。…妙にブリガンドが多いですね。
攻撃力の高いオークが怖いので、とっとと倒しておきます。

Attack style: [s]tab [d]ouble swing [r]oundhouse:s

Monster #:10
You killed the ORC! 
Your foes:
   1. (Health=6) A fierce BRIGAND
   2. (Health=2) A fierce BRIGAND
   3. (Health=8) A wicked orc with a level 3 club
   4. (Health=1) A fierce BRIGAND
   5. (Health=5) A fierce HYDRA
   6. (Health=8) A fierce BRIGAND
   7. (Health=9) A fierce BRIGAND
   8. (Health=10) A fierce BRIGAND
   9. (Health=6) A slime mold with a sliminess of 5
   10. **dead**
   11. (Health=2) A fierce BRIGAND
   12. (Health=7) A fierce BRIGAND
Attack style: [s]tab [d]ouble swing [r]oundhouse:

お次は2回連続攻撃を試してみます。

Your double swing has a strength of 1
Monster #:4
You killed the BRIGAND! 
Monster #:11
You hit the BRIGAND, knocking off 1 health points!
Your foes:
   1. (Health=6) A fierce BRIGAND
   2. (Health=2) A fierce BRIGAND
   3. (Health=8) A wicked orc with a level 3 club
   4. **dead**
   5. (Health=5) A fierce HYDRA
   6. (Health=8) A fierce BRIGAND
   7. (Health=9) A fierce BRIGAND
   8. (Health=10) A fierce BRIGAND
   9. (Health=6) A slime mold with a sliminess of 5
   10. **dead**
   11. (Health=1) A fierce BRIGAND
   12. (Health=7) A fierce BRIGAND
Attack style: [s]tab [d]ouble swing [r]oundhouse:

さて、なぎ払いを行います。

Attack style: [s]tab [d]ouble swing [r]oundhouse:r
You hit the BRIGAND, knocking off 1 health points!You hit the ORC, knocking off 1 health points!
A brigand hits you with his slingshot, talking off 2 health points! A brigand catches your leg with his whip, talking off 2 agility points! An orc swings his club at you and knocks off 3 of your health points. A hydra attacks you with 2 of its heads! It also grows back one more head! A brigand cuts your arm with his whip, talking off 2 strength points! A brigand catches your leg with his whip, talking off 2 agility points! A brigand cuts your arm with his whip, talking off 2 strength points! A slime mold wrap around your legs and decreases your agility by 2! It also squirts in your face, talking away a health point! A brigand cuts your arm with his whip, talking off 2 strength points! A brigand catches your leg with his whip, talking off 2 agility points! 
You are a valiant knight with a health of 22, an agility of 22, and a strength of 24
Your foes:
   1. (Health=5) A fierce BRIGAND
   2. (Health=2) A fierce BRIGAND
   3. (Health=7) A wicked orc with a level 3 club
   4. **dead**
   5. (Health=6) A fierce HYDRA
   6. (Health=8) A fierce BRIGAND
   7. (Health=9) A fierce BRIGAND
   8. (Health=10) A fierce BRIGAND
   9. (Health=6) A slime mold with a sliminess of 5
   10. **dead**
   11. (Health=1) A fierce BRIGAND
   12. (Health=7) A fierce BRIGAND
Attack style: [s]tab [d]ouble swing [r]oundhouse:

一斉攻撃を食らってしまいました。なるほど。…。

Attack style: [s]tab [d]ouble swing [r]oundhouse:d
Your double swing has a strength of 1
Monster #:1
You killed the BRIGAND! 
Monster #:6
You hit the BRIGAND, knocking off 1 health points!
An orc swings his club at you and knocks off 1 of your health points. A brigand cuts your arm with his whip, talking off 2 strength points! A brigand cuts your arm with his whip, talking off 2 strength points! A brigand hits you with his slingshot, talking off 2 health points! A slime mold wrap around your legs and decreases your agility by 4! It also squirts in your face, talking away a health point! A brigand cuts your arm with his whip, talking off 2 strength points! You have been killed. Game over.
NIL
CL-USER>

こいつはなかなか難しいゲームですね…!

ちょっと敵のターンがごちゃごちゃしていて見づらかったり、
そもそも日本語化したかったり改善したいところがいっぱいあります。
(あとvsオークと言ったらやっぱり女騎士ですよね。僕は詳しいんだ)

色々いじくりながら構造体の理解を深めたいと思います。それではまた平日頑張っていきましょう。