その1続き

昨日の続き。
継続を用いて並列的な処理の記述を行うということであった。
今回はその具体的方法について述べる。


この方法の基本的なアイデアHaskellのPreludeにあるinteractである。
このページでもたびたび取り上げているので詳しくはそちらを見てもらうとして、
わかる方はもうこれだけでわかってしまうのでは無いかと思う。
interactで遅延する入力に対して出力を定義すると、
その出力も遅延するのでそれを用いてインタラクティブ
プログラムが作れるのであるが、それと同じでゲームでも
遅延する入力に対してそのリアクションが定義できれば
出力も遅延ストリームにすることが出来るのである。
(このページの最初のころに書いた数当てゲームを見てもらえると。)


interactにて遅延する入力は標準入力からの入力文字列である。
リアルタイム入力にて必要になるのは各フレームのキー状態になるので、
入力としてキー状態のリストを引っ張ってこればよい。
イメージとしては、秒間たとえば60回の割合でぴょこぴょこ伸びるリストが
入力であるような感じであろうか。。


とりあえず、interact…というかgetContentsのSDL版とでも言うもの
を実装してみる。

getKeys :: IO [ [SDLKey] ]
getKeys = do
  pumpEvents
  ks  <- getKeyState
  delay 100 -- 休み時間は適当
  kss <- getKeys
  return $ ks:kss

メッセージをポンプして、キー状態取得して、少し休んで
再帰するような感じである。
ただし、これはこのままではうまく行かない。
前に遅延IOで書いたが(こんなのばっかりやな…)、
これでは遅延してくれないのである。
そこで、これの定義は(胡散臭いものを用い)次のようにする必要がある。

getKeys = unsafeInterleaveIO $ do
  pumpEvents
  ks  <- getKeyState
  delay 100
  kss <- getKeys
  return $ ks:kss

これで、遅延するキー状態リストを取得することが出来る。
あとは、[ [SDLKey] ] -> [なにか]な関数を定義すれば望みの処理を書くことが出来るのであるが、
[SDLKey]をそのまま使うのはちょっと面倒なので、

data KeyState =
  Pushed | Pushing | Released | Releasing
  deriving (Eq,Show)

mkKeyProc (bef,cur) k
  | not bp && not cp = Releasing
  | not bp &&     cp = Pushed
  |     bp && not cp = Released
  |     bp &&     cp = Pushing
  where
    bp = k `elem` bef
    cp = k `elem` cur

このようなものをつくり、

main = do
  ...
  getKeys >>= process
  ...

process dat = processReaction $ game funcs where
  funcs = map mkKeyProc $ zip ([]:dat) dat

processReaction :: [なにか] -> IO ()
 ...

game :: [SDLKey -> KeyState] -> [なにか]
 ...

このようにすれば押した瞬間などもわかり幾分わかりやすい。
gameがゲームの定義となるが、どのような感じになるのか適当に作ってみた。

game = entryPoint

entryPoint (s:ss)
  | s SDLK_z == Pushed = nextPoint ss
  | otherwise = "I am in the entry point.":entryPoint ss

nextPoint (s:ss)
  | s SDLK_x == Pushed = entryPoint ss
  | s SDLK_a == Pushed = ["The end."]
  | otherwise = "Here is next point":nextPoint ss

全くゲームではないが…
上コードにて[なにか]は[String]としているが、
(processReaction = mapM_ putStrLn とでもしておく)
別に何でもいいので、[IO()]とかとすれば何でも出来るであろう。
(入力は上のままでは出来ないので、他に考える必要があるけど)
最初entryPointにいて、zボタンが押されるとnextPointに移行、
nextPoint中にてxが押されるとentryPointに移行、
aが押されるとリストが終わる。
(ここでリストを終わらせる=プログラムが終わるとなることが
個人的にちょっと面白かった)
上コードはプログラムの並列性については何も記述していないが
(前回のyield()に相当するものとか)
遅延評価されるので、一フレームごとに必要な計算のみがなされて、
実際には毎フレーム実行が中断されるように振舞うのである。


もちろん、これをこのまま一般的なゲームの実装として使うのは
まだやりにくいと思うので、その助けになるような描画システムを
考えることにする。

(続く…)