Haskell弾幕
mkmさんのと〜ほ〜GCの掲示板*1で、弾幕スクリプトのあるべき形の模索が始まっております*2。
東方弾幕風はC風スクリプト、ほとんど使ったことは無いのですがBulletMLは動きの列挙のようです。
で、新たな形の模索というかネタの投下をしてみます。「関数型言語弾幕」。前から考えつつも私には無限リストを扱える関数型言語インタプリタなんて作るのは無理に決まってますのであきらめてたのですが、Hugsにserver APIというものがあるのを見つけましたので、Hugsをそのまま利用できそうですので書いてみる事にしました。
type Real = Double type ID = Int type Kind = Int type Point = (Real, Real) data Object = Object (ID, Kind, ObjectFunc) data NewObject = NewObject (Kind, ObjectFunc) type ObjectFunc = [(Bool, Point, [NewObject])] erase :: ID -> IO () erase id = do putStrLn ((show id) ++ " deleted!") update :: ID -> Point -> IO () update id (x, y) = do putStrLn ((show id) ++ ": " ++ (show x) ++ " , " ++ (show y)) execute :: [Object] -> [NewObject] -> ID -> IO () execute objects newObjects newId = let (nId, nobjs) = newToObjects (newId, newObjects) in let tobjs = nobjs ++ objects in if length tobjs == 0 then return () else do (nextObjs, newObjs) <- executeEvery tobjs execute nextObjs newObjs nId return () where newToObjects :: (ID, [NewObject]) -> (ID, [Object]) newToObjects (id, []) = (id, []) newToObjects (id, (n:ns)) = let (id2, ns2) = newToObjects (id + 1, ns) in let NewObject(kind, func) = n in (id2, Object(id, kind, func) : ns2) executeEvery :: [Object] -> IO ([Object], [NewObject]) executeEvery [] = return ([], []) executeEvery (o:os) = do Object (id, kind, func) <- return o ((erasing, p, shot) : func2) <- return func if erasing then do erase id; (nexts, shots) <- executeEvery os; return (nexts, shot ++ shots) else do update id p; (nexts, shots) <- executeEvery os; return (Object(id, kind, func2) : nexts, shot ++ shots) initial = [ NewObject(10, (sample (9, 10))), NewObject(10, (sample (5, 5)))] main :: IO() main = do execute [] initial 1 sample :: Point -> ObjectFunc sample (x, y) = let n = if y == 20 then [NewObject(11, (naname (x, y)))] else [] in (y > 30, (x, y), n) : sample (x, y + 1) naname :: Point -> ObjectFunc naname (x, y) = (y > 30, (x, y), []) : naname (x + 1, y + 1)
1: 9.0 , 10.0 2: 5.0 , 5.0 1: 9.0 , 11.0 2: 5.0 , 6.0 1: 9.0 , 12.0 2: 5.0 , 7.0 1: 9.0 , 13.0 2: 5.0 , 8.0 1: 9.0 , 14.0 2: 5.0 , 9.0 1: 9.0 , 15.0 2: 5.0 , 10.0 1: 9.0 , 16.0 2: 5.0 , 11.0 1: 9.0 , 17.0 2: 5.0 , 12.0 1: 9.0 , 18.0 2: 5.0 , 13.0 1: 9.0 , 19.0 2: 5.0 , 14.0 1: 9.0 , 20.0 2: 5.0 , 15.0 3: 9.0 , 20.0 1: 9.0 , 21.0 2: 5.0 , 16.0 3: 10.0 , 21.0 1: 9.0 , 22.0 2: 5.0 , 17.0 3: 11.0 , 22.0 1: 9.0 , 23.0 2: 5.0 , 18.0 3: 12.0 , 23.0 1: 9.0 , 24.0 2: 5.0 , 19.0 3: 13.0 , 24.0 1: 9.0 , 25.0 2: 5.0 , 20.0 4: 5.0 , 20.0 3: 14.0 , 25.0 1: 9.0 , 26.0 2: 5.0 , 21.0 4: 6.0 , 21.0 3: 15.0 , 26.0 1: 9.0 , 27.0 2: 5.0 , 22.0 4: 7.0 , 22.0 3: 16.0 , 27.0 1: 9.0 , 28.0 2: 5.0 , 23.0 4: 8.0 , 23.0 3: 17.0 , 28.0 1: 9.0 , 29.0 2: 5.0 , 24.0 4: 9.0 , 24.0 3: 18.0 , 29.0 1: 9.0 , 30.0 2: 5.0 , 25.0 4: 10.0 , 25.0 3: 19.0 , 30.0 1 deleted! 2: 5.0 , 26.0 4: 11.0 , 26.0 3 deleted! 2: 5.0 , 27.0 4: 12.0 , 27.0 2: 5.0 , 28.0 4: 13.0 , 28.0 2: 5.0 , 29.0 4: 14.0 , 29.0 2: 5.0 , 30.0 4: 15.0 , 30.0 2 deleted! 4 deleted!
eraseとupdateをFFIでC言語に置き換えて、で、ユーザーにはinitialとsample,nanameみたいなのだけ書かせて後の部分は自動で生成してひとつの.hsファイルを吐いて、runhugsに喰わせてGO!…という計画です。Objectが果たして弾か敵かはKindで区別。衝突判定とかはまだ書いてません。自機の管理はHaskellに乗せる意味が無いように思えますから恐らく衝突判定もFFIでやることになるでしょう。
Haskellの関数は無限リストを返せますので、(ここではsampleとnanameは同じ形をしていますが)シグネチャを揃えなくても、同じように利用することができます。言い替えれば、状態変数の数は再帰という形で閉じ込める事ができますので、東方弾幕風スクリプトのイベントドリブン方式の弱点、フレーム毎に処理を細切れにしないといけない、から解放されます。*3
逆にHaskellを用いる弱点としましては、他の弾や自機の情報は取得可能にするとしましても、グローバル変数に書き込めませんので、変なコトができなくなる、という点です。IOモナド付けますと無限リストの一部だけ取得ができなくなりますので*4、悩ましい*5ところです。
…いや、もちろん、GUIを作るつもりなんてこれっぽっちもありませんよ?
これは計画倒れを意図した一発ネタであることは宣言しておきます。