星田さんの記事に対するコメント。
ザーッと見てみたカンジ、いくつかポイントがある。
蠅と戦ってんじゃねーよ、ってのは置いておいて(笑)。
まず。
1. 破壊的変更は値を返さない
どうやら値をイジった後に型が失われてる模様(enemy型なのにvoidになってる)
いや、原因は、Scheme系の言語に於いては、破壊的変更をした場合、その「作業」は値を返さないトコにある。
例えば、だぜ。
aを2と設定する。3とaを「破壊的変更」して3にして、加算した結果に6を望んだとしてもエラーになるわけだ。
何故なら (set! a 3) と言う破壊的変更は返り値を持たない。つまり (+ 3 ...) の...の部分には3が入らないわけ。この「値が何もない」のを #<void> と表現してる。
ちなみに、このテのやり方はANSI Common Lispなら問題はないんだけど。
ANSI Common Lispは破壊的変更だろうと何だろうと返り値があるんで、こういう記述法も受け入れてくれるんだけど、残念ながらScheme系言語はそうじゃない。「破壊的変更」をした時、その時点ではその「変更した値」を使う事が出来ないんだ。
これはScheme系言語上の全「破壊的変更」作業に共通してる。
つまり、ザーッと見る限り、構造体を「再構成」する際に、set-enemy-hp!と言う命令が、enemy-hpの値を破壊的変更してるんだけど、その時点では返り値がない為、エラーが起きる、ってワケだ。
一つの方策として、その部分を
(begin (set-enemy-hp! ...) 必要な返り値)
とするのが一つのテ。
尤も、そもそもRacketを含むScheme系言語の場合、破壊的変更はなるたけ避けた方がスマートだろう、とは思う。
つまり、構造体を「分解」して「再構成」する方がScheme/Racket「らしい」プログラムにはなるだろう。
2. match-letを使いこなせ
OCamlで散々っぱらmatch系構文を使ってきた。多分そろそろ慣れてきたと思う。
ここでは構造体分解で、一々letで書くのが面倒臭い、と言う悟りを開いたと言う前提で、ガンガンmatch-letを使って構造体を分解する事を薦める。
例えば構造体定義がこうあって。
(struct enemy2 (ac hp page image))
データがこうだとする。
(define usiabuimage (enemy2 4 5 18 'foo))
この時、仮に、何らかの関数内でusiabuimageと言うデータを分解したい場合、こう書いていけば構造体の各スロットに簡単にアクセス出来るわけだ。
(match-let (((enemy2 ac hp page image) ushiabuimage))処理...)
例えば、ナウシカがushiabuimageに攻撃したとして、問題はushiabuimageのhpの増減をどう「構造体の再構成」として書くか、そこに詰まってたわけじゃん?
と言う事は、心臓部としては次のような作業を書けば良い、って事になる。
(match-let (((enemy2 ac hp page image) usiabuimage))
(enemy2 ac (- hp 2) page image)) ;; ここで構造体のコンストラクタでデータを再生成
match-letはかなり便利なんで、これでとにかく要素を分解、そしてそこで使ったラベルを使って構造体を再生成していく。
同様の論法は環境データとして作ったenvにも応用出来るんで、例えば
(match-let (((ナウシカ 敵) env))(match-let (((enemy2 ac hp page image) 敵))(env ナウシカ (enemy2 ac (- hp 2) page image))))
みたいに、2段階で分解して、envを再構成する、ってのが一つのテになると思う。
あるいは、それこそmatchそのものを使って分解しても良い。
(define (battle-go env)
(match env ;; matchでenvの中身を構造体の要素構成へとバラバラにする
((env (hero hac hhp) (enemy2 eac ehp page image))
(let ((damage (- (+ (saikoro) eac)
(+ (saikoro) hac))))
(display 処理...)
(env (hero hac 処理...) (enemy2 eac 処理...)))))) ;; 構造体再構成
あるいは、多分コレ自体でループを狙ってるのかもしれないけど。
(define (battle-go env)
(match env ;; matchでenvの中身を構造体の構成要素へとバラバラにする
((env (hero hac hhp) (enemy2 eac ehp page image))
(let ((damage (- (+ (saikoro) eac)
(+ (saikoro) hac))))
(display 処理...)
(何らかの終了条件)
(battle-go (env (hero hac 処理...) (enemy2 eac 処理...))))))) ;; 構造体再構成
ただし、個人的な意見では、このbattle-go自体は戦闘に於けるevalだと思ってる。
従って、
3. 最初の段階で関数に表示やループを組み込まない方が良い
単純に、こういう状態なら環境に組み込まれた構造体で定義したプレイヤー/敵のヒットポイントがこう変わる(新しく定義された環境構造体が返る)、と一回で終わる関数にしておいて、表示関数や繰り返しは外部にあった方が修正は用意だ、と言う事。
逆に全部詰め込むと、修正が容易じゃないんで困ったちゃん、なんだよね。実際そうだったでしょ(笑)。
まずは表示とループは切り離しておいて、「目的の構造体を生成して返す」関数にする事を優先すべきだと思います。
マル。