Haskellプログラマはゲームプログラミングに何を見たか?(実例編)
ええ……、2日ほど遅延評価の迷宮をさまよっていた。
Haskellは勉強すればするほどどんどん深みにはまっていくような気がする。
当初のもくろみは今回もそんなに外れてはいなかったが、
まだまだ工夫の余地がたくさんある。たくさん有りすぎて大変だ。
Haskellでコードを書いているとき結構迷いが生じるのだが(私だけだろうか…)、
実装するための方法がいっぱいあってどれを選択すれば良いのかわからない。
一般的なノウハウは無いのだろうか、はたまた例のごとく私が知らないだけなのか。
というわけで(どういうわけでだ…)、
習作として以前作ったテトリスを全編遅延ストリームで記述してみた。
正直いろいろうまくない点はあったが、収穫も色々有った。
まず概要は三日前?の通りで。
main = do ... sur <- setVideoMode 640 480 32 [HWSURFACE,ANYFORMAT] imgs <- mapM loadBMP images seed <- newStdGen et <- elapseTime 60 getKeys et >>= process sur (seed,imgs) entryPoint ...
getKeysは遅延したキー状態のリスト、
processはそれを処理したものをentryPointに渡しつつ、
そのレスポンスを解釈する。
レスポンスは前回考察したコンビネータライブラリを用いて構築する。
process sur imgs entry dat = processResult sur $ entry imgs funcs where
funcs = map mkKeyProc $ zip (:dat) dat
processResult :: Surface -> [Scr] -> IO ()
processResult _ = return ()
processResult bg (sc:scs) = do
quit <- processEvents
ks <- getKeyState
unless (quit || pushExit ks) $ do
fillRect bg Nothing 0x000000
drawScr bg sc
flipSurface bg
processResult bg scs
processEvents = do
e <- pollEvent
case e of Nothing -> return False
Just QuitEvent -> return True
_ -> processEvents
pushExit ks =
SDLK_ESCAPE `elem` ks ||
all (`elem` ks) [SDLK_LALT,SDLK_F4] ||
all (`elem` ks) [SDLK_RALT,SDLK_F4]
entryPointがゲームの処理になる。
entryPoint (seed,imgs@[...]) fs = ...
前回のScrとかの実装は次のように行った。
---------------------------------------------- -- graphics combinator library type Scr = ScrState -> IO () type ScrState = (Surface,Point,Double) drawScr :: Surface -> Scr -> IO () drawScr bg sc = execScr sc (bg,pt 0 0,1.0) execScr :: Scr -> ScrState -> IO () execScr sc st = sc st ----- emptyScr :: Scr emptyScr _ = return () image :: Surface -> (Maybe Rect) -> Scr image sur rc (bg,pos,alp) = do setAlpha sur [SRCALPHA] (boundEnum (0,255) $ ceiling $ alp*255) blitSurface sur rc bg pos return () where boundEnum (mini,maxi) = toEnum . max mini . min maxi img :: Surface -> Scr img sur = image sur Nothing subImg :: Surface -> Rect -> Scr subImg sur rc = image sur (Just rc) trans :: Point -> Scr -> Scr trans (Point dx dy) scr (bg,Point x y,alp) = scr (bg,Point (x+dx) (y+dy),alp) alpha :: Double -> Scr -> Scr alpha a scr (bg,pos,alp) = scr (bg,pos,alp*a) infixr 4 <$$> (<$$>) :: Scr -> Scr -> Scr scr1 <$$> scr2 = \s -> do execScr scr1 s execScr scr2 s scrs :: [Scr] -> Scr scrs = foldl (<$$>) emptyScr
なんだか重なり合うイメージに対してα値を設定したときに
変な透け具合になるような気がするのだが、まぁ、よし…。
いよいよ遅延評価によるコルーチン使用の隠蔽の実例を挙げてみる。
-- タイトルスクリーン opening = loop $ cycle pat where loop (al:als) (s:ss) = (trans posp (alpha al push) <$$> trans post tl) : case s GKRotate of Pushed -> game ss -- ゲームへ _ -> loop als ss pat = [0,1/60 .. 1] ++ replicate 1 30 ++ [1,1-1/60 .. 0] -- 明滅パターン posp = pt 220 300 -- push z key 位置 post = pt 20 10 -- タイトル 位置 tl = (タイトルロゴ画像) push = (push z keyと書かれた画像)
これはタイトルスクリーン(画面の上半分ぐらいにロゴが表示されて、
下半分中央にpush z keyとかが明滅するような感じの)なのだが、
全体的にいい感じに書けたと思う。特にパターンの記述がすっきりと。
この時点でとりあえず問題に感じたのは
ゲーム処理部分からIOによる入力が行えないこと。
そのため今回は使用する画像などすべてを最初に読み込むようにした。
しかしまぁ、そのうちシーンレベルでの抽象化を行う予定なので
それで何とかなるだろう。
一応、作ったものをアップしてみる。
http://fxp.infoseek.ne.jp/haskell/HSDL/sample/hasdlis2.zip
ソースは切り貼りしまくったので汚いけど…