星田オステオパシー

Racketで何か 四人のキング12 メインEvalをFoldで動くようにする実験

 前回、CARDインスタンスの中にクロージャと引数のリストを用意して、それをFOLDで回すことで組み合わせ自由、個数無制限のイベント処理システムが作れるのでは無いだろうか?と思いついたワケですが・・。
 例えばSAでは
・戦闘を行うか?
・イベント発生に必要なアイテムを持っているか?
 ・持っていた場合運試しを行うか?
   ・運試し実行
    ・バトルへ
 ・運試し無しでバトルへ
という流れが発生します。入れ子になってる部分は引数で処理するとして(これも再考の余地ありで後述)

 CARDインスタンスにF-LISTとしてクロージャをこのように入れたい

 で、クロージャの数と同数の引数リストA-LISTを用意して初期値としてWORLDインスタンスを用意して、出力されたWORLDも引数として利用して・・というのをFOLDで回していくと

 とりあえず実際に書式に沿った各種関数を作っていくか・・。テストなので理解しやすいようにメッセージ結合方式でやってます。Selectの場合は受ければそのままWorldを次へ回し、受けない場合は次のPlayerでメインを進めるので・・脱出になるわけだな?

 次に今回の場合は所持アイテムをチェックしてイベント発生条件を満たしていれば、引数に入れてある別のクロージャや関数を呼び出して処理。持ってない場合はWorldをそのまま返す=スルー。
 ちょっと引っかかるのがここでして・・引数にクロージャや関数を忍ばせるのが良いのか、それともWORLDのスロットを1つ使ってスルーさせる処理を作るのが良いのか・・今回の場合で言うと
(,select ,satisfy-item(luck? luck) ,battle-read)
と、入れ子になってるのを
(,select ,satisfy-item ,luck? ,luck ,battle-read)
と言う風にとりあえず全部のクロージャをCARDにリストとして持たせて置いて、すべてを通過させるようにするか。多分、WORLDのスロットに判定用の何かを埋めれば可能だとは思うんだよなぁ。すべてのクロージャをブロックのようにつないで動かす・・という思想からするとこっちを選ぶべきか・・?

 とりあえず今回は入れ子容認で作ったのでこのままで・・。satisfy-item用の引数リストの1に'luck?が入れてあって、これをhash-refで読み込む仕組み

 呼び出されたluck?にはsatisfy-itemのW Aがそのまま引き継がれるので、それを使って処理。運試しをする場合は更にLuckを呼び出す

 
 Luckでも同じ引数を用いて成功判定。処理は更にLuck-resultに任せる。成功の場合はA-LISTの2のCAR部分、失敗の場合はCADR部分を渡す。
 (0 0 0) (-3 0) #f はそれぞれ(プレイヤーの技術点、体力点、運点)(敵の技術点、体力点)(強制移動ありか?)の引数になっている

 引数を元に数値を変更したPlayer Enemyのインスタンスを作って表示して新たなWORLDインスタンスを組み上げて返す、と。これが最終的なsatisfy-itemクロージャの返り値となって次の(今回は)Battle-readへと渡される、と。

 メッセージ部分は・・とりあえずこんな感じで?引数をFlattenでバラして何個目が0じゃないかで処理。複数の数値変動がある場合は破綻するが、多分無いんじゃないかな?

 今回不安なのはSatisfy-itemクロージャでイベント発生に必要なアイテムを持ってるかどうかをチェックする部分なので小テスト。この自作関数で・・

 シンボルの場合はオッケイ

 インスタンスでもオッケイ。良かった・・じゃあどうすっかなぁ・・手軽さで言えばインスタンスを直接リストするようにした方が(特に後に作る装備品からステータス変化をさせる部分では)多分楽だしなぁ・・

 という事で、とりあえずインスタンスのリストにしておくことに

 PLAYER-ITEMに必要なものを持たせて・・


 テスト用のEval部分を書く。おお・・たったこれだけで(理屈が合ってれば)動く(はずな)のか・・

 で、テスト!実はここがまた大変だった・・仕様にそって出来上がってるCARDが1つだけなので(だってテストだから)、各種関数が要求する引数やら関数を無効化しないといけなくてコードがコメントアウトの嵐!
 そしてCARDインスタンスの引数として直接クロージャやインスタンスを指定しているのでコードの置き場所もBattle-readが存在するメイン部分にCARDを持ってこないといけなかったりでグチャグチャに。もしかしてこういうのを防ぐためにハッシュテーブルに登録して'キーで呼び出すようにしてるのか!?と思ったけど後の祭りで・・。まあ、テストが無事終わったらそうしよう!まだ動くか分からないんだから・・

 で、テスト用WORLDインスタンス。実際はENEMIESは'()になってて、main-readの時に現在のCARDからENEMYを読み込んで次に渡す事になる、と。ではいよいよ・・実行!

 よっしゃ!実験成功! ま、結果だけ書いてますからスンナリ進んでるように見えますけど実際のトコロ大変だったんですけどね(^_^;)
 あと、本来は運試しの結果は技術点に影響するんですけど、技術点は表示しない設計になってるのでテスト用に体力点を変えてます。

 とにかくこれでFOLDによるクロージャ連鎖システムでちゃんと動くことが分かったので、この仕様に沿った各種イベント関数を書いていけばオッケイって事だな〜。感覚的には峠を超えた感じ。

 しかし懸念はクロージャ入れ子方式かクロージャオールブロック貫通方式か・・だな。ブランチ分けて一度作ってみるか・・カード作る前の今しか無いからなぁ〜
 

コメント一覧

hosidaosuteo
更にありがとうございます!前に括りだす話をしてもらってたのに活かせてませんでした〜
あと「剰余」。毎回全く思いつかないんですがCametanさんの解説では何度も出てくるのでコレはキーやなと。研究させていただきます!
cametan_42
> 毎回COND系の時には「なんかもっとスッキリ書けそうな気もするが・・」と気にはなってるんですが。

「気になる」なら多分そうなんですよ(笑)。
ただし、やっぱ第一義は「何だろうと取り敢えず動くこと」です。
細かいテクニックを応用するのはやっぱ一旦完成させてから、の方が良いでしょう。
「完成した後コードを整理する」事をリファクタリング、と言います。
ただ、C言語なんかでリファクタリングの重要性を学生なんかにあまり伝えないのは、結局「コンパイルして試す」ってのが面倒臭いから、ですよね。
リファクタリングでガンガンコードを改良出来る、ってのはインタプリタ採用言語処理系の強みです。

んで、リファクタリングなんかの改造をする際には元コードは消さない。元コードをコメントアウトする、なんつーのが良くあるテなんだけど・・・・・・。
一番良いのはgitで別ブランチを切っちゃう事です。開発用ブランチから別ブランチを生成してそこでコードを改良する。上手く動いたら開発用ブランチにマージする。
迷ったらガンガンブランチを分岐していって構いません。それがgitの強みなんで、「コード修正」ではブランチを切りまくり、マージしまくりましょう。

んで「条件分岐」の記事でも書いたけど、割に「色々と纏められる」ケースが多いんですよ。
例えば。

(cond ((= count 0) (values NAME "技術点" (car c-arg)))
((= count 1) (values NAME "体力点" (car c-arg)))
((= count 2) (values NAME "幸運点" (car c-arg)))
((= count 3) (values ENAME "技術点" (car c-arg)))
((= count 4) (values ENAME "体力点" (car c-arg)))
(else #f))

これ、else以外は全部(values ...)が返り値になってる。
と言うのなら「括りだす」ならここです。
んでパターン見てみると、

・countが2以下の時にはNAMEそれ以外だとENAME

ってのがまずは見つかる。
もう1つはトリッキーなんだけど、

・countの剰余が0の時は"技術点"、1の時は"体力点"、2の時は"幸運点"

と言うパターンがある。
そうすれば、

(values (if (< count 3)
NAME
ENAME) (case (modulo count 3)
((0) "技術点")
((1) "体力点")
((2) "幸運点")) (car c-arg))

と書ける。
でも多分 #f も欲しくて、それがパターン外なのね。
そういう場合はこうしちゃう。

(call/cc
(lambda (return)
(values (cond ((< count 3) NAME)
((< count 5) ENAME)
(else (return #f)))
(case (modulo count 3)
((0) "技術点")
((1) "体力点")
((2) "幸運点"))
(car c-arg))))

継続を使って脱出しちゃえばいい。
他にも関数luckは、条件分岐で変わってるのはcarかcadrか、ってトコだけが違う。
よってこう書けます。

(printf "運試しの結果は・・")
(luck-result W ((if (> (car (PLAYER-LUCKP c-player)) (dice))
car
cadr) (list-ref A 2)))

としてしまう。そうすればcarとcadrだけが条件によって「とっかえられる」。

ただ、こういうのは、コーディングが全部終わって「ゲームが動いて」からかな。
最適化とか、リファクタリングは取り敢えずプロトタイプが完成してから考えればいい話ではあるんで。
最初はまずは「動けばいい」でおおらかに考えてていいとは思います。
hosidaosuteo
@cametan_42 コメントありがとうございます!毎回COND系の時には「なんかもっとスッキリ書けそうな気もするが・・」と気にはなってるんですが。論理式は確かに短期記憶容量がパンパンになって追いきれないときがあるし・・実用の該当ページ読んでみます!CommonLispもせっかくなのでやってみたいんですよねぇ〜
cametan_42
リファクタリングの話だと、condはcaseにした方がスッキリするでしょうね。

(case count
 ((1) ...)
 ...)

みたいに書いていく。

satisfy-item?は、まぁ、好みなんだけど、僕なら

(or (null? card-item)
 (and (member? ...)
    (satisfy-item? ...)))

にするかなぁ。
まぁこの辺は好み。
Schemerは#tとか#fとか書くのが嫌いなんで(笑)、論理式で生成しようとするケースが多いと思うんだけど、一方、ピーター・ノーヴィック先生は「下手に縮めると意味が不明瞭になる」ってぇんで「実用Common Lisp」だと諌めています。
ただ、ANSI Common Lispだとwhenとか使っても値が返るんだけど、Scheme系言語だとそういう保証がないんで、どうしてもこの辺、冗長になるんで痛し痒しかなぁ。
名前:
コメント:

※文字化け等の原因になりますので顔文字の投稿はお控えください。

コメント利用規約に同意の上コメント投稿を行ってください。

 

※ブログ作成者から承認されるまでコメントは反映されません。

  • Xでシェアする
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

最近の「プログラミング」カテゴリーもっと見る

最近の記事
バックナンバー
人気記事