bitterharvest’s diary

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

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

1.問題

最初からいきなり問題である。

問題:Netwireで次のゲームを実現しなさい。

仕様:ピンポン玉が空中に投げ出されたとする。この時、ピンポン玉が落ちないように、水平に移動するラケットで反射させなさい。また、左右には壁があるものとし、ピンポン玉は壁にぶつかった時、跳ね返るものとする。なお、ピンポン玉はニュートン力学に従って運動するものとし、壁、および、ラケットの反射係数は1としなさい。

このゲームは図に示すと以下のようになる。
f:id:bitterharvest:20150610175609p:plain

なお、Netwireを用いて実装されたゲームには、AsteroidsTetrisがある。

2.Netwire

Netwireのプロジェクトは2011年に始まる。Yampaを改良して、よりよいインタフェースと、既存のフレームワークにより洗練化することであった。そして、当初の目的は、ゲームサーバーと知能ネットワークロボットの強化であった。Netwireの名前もこの点に由来している。しかし、信号関数に時間の間隔を加えたことで、今では、Yampaとは別物である。

例によって、Netwireに関する記事はあまりないので、Getting Into NetwireAlmost a Netwire 5 Tutorialの記事を参考にしながら(といっても、前半は、Getting Into Netwireの記事に沿った説明となる)、最初に挙げた問題が解けるようになるまで説明する(なお、上記の問題は、修士課程で教えている今年度の講義での課題でもある)。

3.振舞い

コンピュータでのゲームやシミュレーションでは様々な登場人物が登場する。これらの登場人物の「振舞い」をどのように表現したらよいのかが、重要である。Haskellでは、何かを表現しようとするときは、そのための型を導入するのが常套手段である。そこで、Behaviorという型を作ることにする。

3.1 具体例

具体的な定義から始めて、だんだんに、一般化する。最初に、散歩に関する振舞いを考え、t秒後にどれだけ進んだかを表すために、振舞いの新しい型を定義する。時間と進んだ距離がDouble型であったとすると、次のように型を定義することが可能である。

newtype Behavior = Behavior {stepBehavior :: Double -> Double}

上記の定義で、stepBehavior(代数的データ型であることに注意してほしい)の型シグネチャは次のようになる。なお、左のBehaviorは型コンストラクタ、右のBehaviorは値コンストラクタである。ここでは同一の名前を用いたが、もちろん異なっていてもかまわない。

*Main> :t stepBehavior
stepBehavior :: Behavior -> Double -> Double

次にこの型を利用して具体的な振舞いを求める。1秒につき1.5mの速度で歩いているときの位置の振舞いは次のようになる(位置を求めているのではなく振舞いを求めていることに注意してほしい)。

positionBehavior :: Behavior
positionBehavior = Behavior {stepBehavior = \ t -> 1.5 * t}

これを用いると、t秒後の現在位置は次のようになる。

currentPosition :: Double -> Double
currentPosition t = stepBehavior positionBehavior t

例えば、5.0秒後の位置は

*Main> currentPosition 5.0
7.5

と求めることができる。

次にある時間にある作業を行うという行為に関わる振舞いを考えることにする。行為を文字列で表すことにすると、この時の振舞いの型は次のように定義できる。

newtype Behavior' = Behavior' {stepBehavior' :: Double -> String}

そこで、行為の振舞いは次のようになる。

actionBehavior' :: Behavior'
actionBehavior' = Behavior' {stepBehavior' = \t -> if t == 1.0 then "Eating" else if t== 3.0 then "Afternoon tee" else "Nothing"}

次に、時間tでの作業は次のようになる。

currentAction :: Double -> String
currentAction t = stepBehavior' actionBehavior' t

実行結果は次のようになる。

*Main> currentAction 3.0
"Afternoon tee"

3.2 一般化

散歩と行為という振舞いに対して、別々の型を用意したが、全ての振舞いについて、それぞれ、型を用意するのではとても大変である。そこで、散歩と行為に対する別々の振舞いを一つの振舞いにまとめることを考える。二つの振舞いの中での相違点は、片方が距離を表すためにDouble型を、他方が作業を表すためにString型を用いたことである。そこで、この両方を表せるようにするために、この部分は型変数を用いることとし、振舞いを次のように定義する。

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

そして、散歩と行為の振舞いは型変数を具体的な型で、即ち、DoubleとStringで、表して次のようになる。

positionBehavior :: Behavior Double
positionBehavior = Behavior {stepBehavior = \ t -> 1.5 * t}

actionBehavior :: Behavior String
actionBehavior = Behavior {stepBehavior = \ t -> if t == 1.0 then "Eating" else if t== 3.0 then "Afternoon tee" else "Nothing"}

次に、時間tでの位置と作業は次のようになる。

currentPosition :: Double -> Double
currentPosition t = stepBehavior positionBehavior t

currentAction :: Double -> String
currentAction t = stepBehavior actionBehavior t

実行結果は次のようになる。

*Main> currentPosition 3.0
4.5
*Main> currentAction 1.0
"Eating"


ところで、行為に関する振舞いは、Double型ではなく、何時というように整数で指定したいとする。このとき、
時間tのところも型変数にすれば、この要求はかなえられるので、次のようにする。

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

位置の振舞いと行為の振舞いは時間の型を具体的に指定して次のようになる。。

positionBehavior :: Behavior Double Double
positionBehavior = Behavior {stepBehavior = \ t -> 1.5 * t}

actionBehavior :: Behavior Int String
actionBehavior = Behavior {stepBehavior = \ t -> if t == 1 then "Eating" else if t== 3 then "Afternoon tee" else "Nothing"}

次に、時間tでの位置と作業は次のようになる。

currentPosition :: Double -> Double
currentPosition t = stepBehavior positionBehavior t

currentAction :: Int -> String
currentAction t = stepBehavior actionBehavior t

実行する。

*Main> currentPosition 4.0
6.0
*Main> currentAction 3
"Afternoon tee"

ちなみに、stepBehaviorの型シグネチャは次のようになっている。

*Main> :t stepBehavior
stepBehavior :: Behavior t a -> t -> a

以上で、振舞いの一般的な型を得ることができた。次の記事では、イベントについて記述する。