bitterharvest’s diary

A Bitter Harvestは小説の題名。作者は豪州のPeter Yeldham。苦闘の末に勝ちえた偏見からの解放は命との引換になったという悲しい物語

Netwireでゲームを作成する(2)

4.イベント

イベントも振舞いと同じように型で表す。即ち、tという時間にeというイベントが発生したとき、

newtype Event t a = Event { getEvent :: (t,a) }

というデータ型で表す。

これは、振舞いを定義したBehaviorの型と比較するとよく似ていることが分かる。

newtype Behavior t a = Behavior { stepBehavior :: t -> a }

なお、getEventの型シグネチャは次のようになっている。

*Main> :t getEvent
getEvent :: Event t a -> (t, a)

例えば、6時にアラームが鳴るようなイベントは次のように表すことができる。

alarmEvent = Event {getEvent = (6,"Alarm")}

また、12時に新幹線が通るようなイベントは次のように表すことができる。

trainEvent = Event {getEvent = (12,"Rapid train")}

4.1 キーボードからの入力

パソコンを使っている我々にとって、最も慣れ親しんでいるイベントの一つはキーボードからの入力である。パソコンでゲームを作成するときもキーボードは重要な役割を担っている。画面上のあるものを動かしたり、弾丸を発射させる時に用いる。ここでは、キーボードからの入力について説明する。今、四角い箱があったとし、キーボードからの入力でこの箱を左右に動かすこととする。

キーのAを押し下げると左に、また、Dを押し下げると右に動くこととする。そこで、キーに対して次のようにデータ型を定義する。

data Input  =  A  | D  | Quit    deriving (Eq,Read,Show)

キーボードの入力をイベントとしてポーリングするために、次の関数を用意する。

pollEvents :: IO [Input]
pollEvents = fmap treatLine getLine
  where
    treatLine = concatMap (map fst . reads) . words

ここで、concatMapは、リストの各要素に対して関数fを適応したものをconcatする。concatは複数のリストのリストを一つのリストにする。例えば次のようになる。

Prelude> concat [[1,2,3],[4,5]]
[1,2,3,4,5]
Prelude> concat ["I ", "would ", "like ", "to ", "invite ", " him ", "to ", "the ", "party", "."]
"I would like to invite  him to the party."

また、concatMapの例を示す。

Prelude> putStr $ concatMap (++"\n") ["I", "like", "Haskell"]
I
like
Haskell

上記のプログラムは、最初に、各要素に(++"\n")を施すと["I\n", "like\n", "Haskell\n"]になる。これにconcatを適応すると、"I\nlike\nHaskell\n"となり、上記のような出力を得る。

それでは、pollEventsを実行する。適当にキーを入力すると次のような結果が得られる。

*Main> pollEvents
A
[A]
*Main> pollEvents
S
[]
*Main> pollEvents
D
[D]
*Main> pollEvents
AB
[]
*Main> pollEvents
A D E R T
[A,D]
*Main> pollEvents
A R D T A U
[A,D,A]
*Main> pollEvents
A T H Quit ADE DD
[A,Quit]

4.2 四角い箱を定義する

次の四角い箱を用意する。これも新しい型を導入して行う。

newtype Box = Box { _boxPosition :: V3 Double } deriving (Eq,Read,Show)

また、フィールド名_boxPositionを関数boxPositionにするために次の処理を行う。

makeLenses ''AppBox

なお、これらを使用するためには、TemplateHaskellを使えるようにするばかりでなく、以下のモジュールをインポートしておく必要がある。

{-# LANGUAGE TemplateHaskell #-}

import Control.Lens
import Linear.V3

さらにBoxの状態を次の型により定義する。

data AppSt = AppSt { _appBox :: Box} deriving (Eq,Read,Show)

また、フィールド名を関数にする。

makeLenses ''AppBox

4.3 箱をキーからのイベントにより動かす

そこで、キーのA,Dが押された時は、それぞれ左右に0.1だけ移動させる関数は、以下のようになる(ここではこんな感じになると思って欲しい。後で詳しい説明をする)。

updateAppSt :: AppSt -> Input -> Maybe AppSt
updateAppSt appst input = case input of
  A -> Just $ appst & appBox . boxPosition . _x -~ 0.1
  D -> Just $ appst & appBox . boxPosition . _x +~ 0.1
  Quit -> Nothing

ここまでのプログラムをまとめると次のようになる。

{-# LANGUAGE TemplateHaskell #-}

import Control.Lens
import Linear.V3

data Input  =  A  | D  | Quit    deriving (Eq,Read,Show)

pollEvents :: IO [Input]
pollEvents = fmap treatLine getLine
  where
    treatLine = concatMap (map fst . reads) . words

newtype Box = Box { _boxPosition :: V3 Double } deriving (Eq, Read, Show)

makeLenses ''Box

data AppSt = AppSt { _appBox :: Box} deriving (Eq,Read,Show)

makeLenses ''AppSt

updateAppSt :: AppSt -> Input -> Maybe AppSt
updateAppSt appst input = case input of
  A -> Just $ appst & appBox . boxPosition . _x -~ 0.1
  D -> Just $ appst & appBox . boxPosition . _x +~ 0.1
  Quit -> Nothing