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をFFIC言語に置き換えて、で、ユーザーにはinitialとsample,nanameみたいなのだけ書かせて後の部分は自動で生成してひとつの.hsファイルを吐いて、runhugsに喰わせてGO!…という計画です。Objectが果たして弾か敵かはKindで区別。衝突判定とかはまだ書いてません。自機の管理はHaskellに乗せる意味が無いように思えますから恐らく衝突判定もFFIでやることになるでしょう。

Haskellの関数は無限リストを返せますので、(ここではsampleとnanameは同じ形をしていますが)シグネチャを揃えなくても、同じように利用することができます。言い替えれば、状態変数の数は再帰という形で閉じ込める事ができますので、東方弾幕風スクリプトのイベントドリブン方式の弱点、フレーム毎に処理を細切れにしないといけない、から解放されます。*3

逆にHaskellを用いる弱点としましては、他の弾や自機の情報は取得可能にするとしましても、グローバル変数に書き込めませんので、変なコトができなくなる、という点です。IOモナド付けますと無限リストの一部だけ取得ができなくなりますので*4、悩ましい*5ところです。

…いや、もちろん、GUIを作るつもりなんてこれっぽっちもありませんよ?
これは計画倒れを意図した一発ネタであることは宣言しておきます。

*1:リンクは自粛

*2:大袈裟

*3:もっとも、弾幕風はマイクロスレッドを用意していますのでそれを使えば解決なんですけど

*4:勝手に参照させていただきますがhttp://d.hatena.ne.jp/tanakh/20040722#p1…私も最近ここを読ませていただいて初めて知りました

*5:純粋な弾幕シミュレータを作るなら問題なさそうですが、件のオセロみたいなものが作れなくなるのは重要な問題です