星田さんの記事に対するコメント
・・・継承・・最高やんm(_ _)m
う〜んとね。
結論から言うと「ゲームに於けるデータ設計」ってのは割にオブジェクト指向が役立つんだよ。
例えばRPGで、特にTRPGとかキャラシートとかあるでしょ?
んでモンスター図鑑的なモノがあって。
でも両者とも「パラメータ」類が色々設定されてるなら、何か共通のデータ型の「雛形」を設計しておいて、それを「継承」して作っていった方が「ラク」なんだよな。
原則、ゲームの場合、アクションとかパラメータとかデータ型に「色々と共通する」性質がある以上(例えば「飛行機」は「弾を撃つ」とか・笑)、一々バラバラに設計したくない、って言う背景が出てくると、やっぱ雛形、っつーか「テンプレ」を作っといてそれを全部「継承」した方が「作りやすい」ってのは事実なんだ。
まぁ、オブジェクト指向ってのを散々批判してるんだけど(笑)、結局、
「必要もねぇのに何でもかんでもオブジェクト指向で作ろう」
って言うカンジの「誘導」が意味がない、って事なんだよな。
例えばフツーにCLIのソフトを作る際に「何でもかんでもオブジェクト指向で作ろう」ってのは間違ってる、とは思う。
ゲームでもいつぞや見せた「ババ抜き」のように、「オブジェクト指向で全体を作りました」と言うカンジだと「構造化スパゲティコード」になる、って事も見た。
ただし、フツーのプログラミングの、特にデータ設計に「ちょっとオブジェクト指向を混ぜる」ってのは悪くないドコか効果的だし、ぶっちゃけRacketの構造体はそういうカンジでデザインされている。
そして、ゲームの場合、結構な比率でこういう「オブジェクト指向」的なアイディアが有用なケースが、フツーのプログラムより多いとは思う。
余談だけど、一般の誰もが「オブジェクト指向」なんぞ聞いた事がない70年代末から80年代にかけて。
ナムコなんかはマシン語/アセンブリ言語レベルのブログラムで「オブジェクト指向」的な事をやってたらしい。
ギャラクシアンが発祥かどうかは分からないんだけど、仮にそうだとしたら、1979年辺りでナムコのゲームプログラマはマシン語/アセンブリ言語レベルで「オブジェクト指向的な」発想をしてた、って事だ。
つまり、例えば自機とか敵キャラの「共通の性質」を括って定義しておいて、それを参照しつつ画像とか差異を「あるオブジェクトの定義部分」に埋め込みバラバラに動かす、とか(要は、OOPで言う「インスタンス」だ・笑)。
当時の職業ゲームプログラマって物凄かったんだな、と思うわ。ある意味マジで「時代と技術の最先端にいた」って事だよな。
と言うわけで、ゲームでの「データ設計」、特に似たようなオブジェクトが複数出てくる、って場合は、メソッド定義はさておき、「データ型」と言うガワに於いてはオブジェクト指向って考え方は「有用な場合が多い」って話だ。
とまぁ、そういう前フリを書いてはみたんだけど。
本題に入ろう。
さて、別種の構造体をリストに入れてそれをSortする問題。実は頭の中では「Caseと(variant x)を使えば楽勝だろう」と考えていた。
が! Sortの文法だとxが無いw
こういう風に書きたいのに!
いっつも言ってるけど、星田さんの発想は100%正しい。
極めて勘が優れてると思う。
いっつも「あと一歩」のトコにいるんだよな。
つまり、例えば構造体での設計がこんなカンジであるわけだろ?
(struct CHARACTER (Name Race Class Ali Lv Hp Ac Exp Money Move Arm Armor Item Str Int Wis Dex Con Chr) #:transparent)
(struct ENEMY (Name Hp Ac Exp Money Armor Item Str Int Wis Dex Con Chr) #:transparent)
まぁホントにこの通りかどうかは知らんが。
んで、構造体のインスタンスのリストがある、と。
`(
,(CHARACTER "tawa" "ELF" "MAGIC-USER" "" 1 6 10 0 90 6 '() '() '() 10 18 6 11 9 10)
,(CHARACTER "hosida" "HUMAN" "FIGHTER" "" 1 1 10 0 90 5 '() '() '() 17 10 12 8 15 14)
,(ENEMY "DEMON1" 10 5 10 5 '() '() 10 10 10 10 10 10)
,(ENEMY "DEMON2" 10 5 10 5 '() '() 10 10 10 10 10 10))
んで、このケースの場合だと「別々の構造体のインスタンス」のDEXスロットの値で、ソートしたいんだけど、どうすりゃエエんだ、と。
Sortの文法だとxが無いwこういう風に書きたいのに!
ってのはそういう事だよな。
血の叫びだ(笑)。
さて、一回Racketに於けるsortの文法を確認してみよう。
さてさて。
#:keyと言うキーワードパラメータが受け取る値はextract-keyだと書かれてて、それは比較指定の完全型であるラムダ式の一部である、って書かれてる。
(lambda (x y)
(less-than? (extract-key x) (extract-key y)))
つまり、#:keyに与えてるブツはクロージャだと言う事になる。
これは星田さんも認識してんだよ。
相手が構造体だけど、もしかしてCar Cadr...の形で参照できるなら、両方共ケツから3つ目ってのは共通してるので大丈夫かも?と思ったが駄目か・・。
つまり、#:keyに与えるのはクロージャじゃなきゃダメだ、って知っている。
ついでにこれだ。
Sortの文法だとxが無いw
うん、無い(笑)。でもxを使いたい。
要はxを与えるのにいきなりcaseを使ってるから間違った、ってだけだ。何故ならcaseはクロージャを生成しないから。
そう、xを使いつつクロージャを生成する、ってぇのなら単にそこはラムダ式で指定すりゃエエ、って事だ。
つまり、正解はこうだ、って事。
(sort `(
,(CHARACTER "tawa" "ELF" "MAGIC-USER" "" 1 6 10 0 90 6 '() '() '() 10 18 6 11 9 10)
,(CHARACTER "hosida" "HUMAN" "FIGHTER" "" 1 1 10 0 90 5 '() '() '() 17 10 12 8 15 14)
,(ENEMY "DEMON1" 10 5 10 5 '() '() 10 10 10 10 10 10)
,(ENEMY "DEMON2" 10 5 10 5 '() '() 10 10 10 10 10 10))
< #:key (lambda (x) ; ここはラムダ式で指定する
(case (variant x)
((CHARACTER) (CHARACTER-Dex x))
((ENEMY) (ENEMY-Dex x)))))
ね?「言われてみりゃその通りだ」でしょ(笑)?
「あと一歩だった」と(笑)。
悔しがっていいからね(笑)。その方が「二度と同じパターンではハマらない」から(笑)。