モナドのまほう　第２話『ゲームループとキー入力ができました』

* [第一話　画像が表示できました](http://qiita.com/hiruberuto/items/5321d8cebce7b87851f6)　←前回

つづきです。

# 座標変換！:eyes:

前回は画像の読み込みと複数回の描画ができました。ここで**急にクォータービューっぽく表示してみたくなった**（思いつき）ので、そうしてみます！　`translate`とか`scale`とか`rotate`とかそういう座標変換の関数をそういう感じに呼びます！　アフィン変換について知らない人はゲームプログラミングの書籍なんかを読んでお勉強してみましょう！


```haskell
translate { translateX: 1280.0 * 0.5 , translateY: 720.0 * 0.5 } context
scale { scaleX: 1.0, scaleY: 0.6 } context
rotate (45.0 / 180.0 * pi) context
```

これらの関数について、詳しくは[Canvas API](https://developer.mozilla.org/ja/docs/Web/HTML/Canvas)をどうぞ。

# `save`/`restore`！:cop:

そして忘れてはいけないのが、コンテキストの`save`と`restore`。これを忘れるとゲームループが回るたびに座標変換が蓄積していってわけのわからないことになります。まあまだゲームループも実装していませんけど。もちろんPureScriptからでも問題なく`save`と`restore`を呼ぶことができます。

```haskell
save context
pure unit   -- do something
restore context
```

でも、これじゃうっかり`restore`を書き忘れるかも……。そんなときは`withContext`っていう関数が用意されているので、これを使えば自動的に`save`と`restore`をしてくれるので楽ちん！

```haskell
withContext context do
    pure unit -- do something
```

`save`のあとにこの一段インデントされた部分が実行され、それが終わると勝手に`restore`してくれます。それでは表示してみましょう。

![ss3.png](https://qiita-image-store.s3.amazonaws.com/0/64695/da2f674d-b893-2934-6acd-96be3ececa69.png)

きっったあああああああああ！:smiling_imp::smiling_imp::smiling_imp:　クォータービュー:small_orange_diamond:だ！　でも英語ではIsometric projectionっていうそうですよ！





# ゲームループとキー入力！:point_left:

静止画を表示しただけではゲームにはなりません。アニメーション、すなわち画面を時間にそって変化させることが必要ですが、そのためにはいわゆる**ゲームループ**が必要です。今はとくにゲームライブラリを使っていないので、ゲームループも自作する必要がありますが、この部分はJavaScriptで書きました（血涙）。イベントハンドラを仕掛けたりと多少DOMをいじらなくてはならないのですが、PureScriptの生DOMライブラリは死ぬほど面倒くさくて、はっきり言って使わないほうがよっぽどマシです。でもあんまりガチなUIフレームワークを使うほどではないので、代わりにJavaScriptでゲームループ関連のコードを書いて、それを**外部関数インターフェイス**を使ってPureScriptから呼び出すことにします。

JavaScript側は適当にこんな感じに。PureScriptから呼び出されるJavaScriptコードはCommonJSモジュールとして書きます。呼び出されたらすぐに内容を実行するのではなく、いったん引数のない関数オブジェクトを返しているのが特徴です。PureScript側からもう一度その関数オブジェクトを呼び出すことで本体が実行されます。

```js:JavaScript
exports.gameloop = function(callback){
    return function(){
        function next(){
            callback();
            count += 1;
            window.requestAnimationFrame(next);
        }
        var count = 0;
        window.addEventListener("keydown", function(e){
            if( ! keyTable.hasOwnProperty(e.keyCode)){
                keyTable[e.keyCode] = count;
            }
        });
        window.addEventListener("keyup", function(e){
            delete keyTable[e.keyCode];
        });
        next();
    };
};
```

PureScript側はこんな感じ。

```haskell:PureScript
foreign import gameloop :: forall eff . Eff (gameloop :: GameLoop, dom :: DOM | eff) Unit → Eff (dom :: DOM | eff) Unit
```

この関数`gameloop`はこんな感じで呼び出します。

```haskell
gameloop do
    withContext context do
        translate { translateX: 1280.0 * 0.5 , translateY: 720.0 * 0.5 } context
        scale { scaleX: 1.0, scaleY: pitch / 90.0 } context
        rotate (45.0 / 180.0 * pi) context
        translate { translateX: 36.0 * -2.0 , translateY: 36.0 * -2.0 } context

        for (0 .. 9) \y → do
            for (0 .. 9) \x → do
                drawImage context grass (36.0 * toNumber x) (36.0 * toNumber y)
        pure unit
```

こうすると、`gameloop`からひとつインデントされた部分が`requestAnimationFrame`のタイミングで繰り返し実行されます。これで何度も再描画はされているのですが、何も表示が変わっていないのでアニメーションしているように見えません……:sob::sob::sob:




# 変更可能な領域！:alien:

さて、PureScriptは**純粋関数型プログラミング言語**であり、**すべての式が純粋**です。噂には聞いたことがあるかもしれませんが、次のようにして変数を直接書き換えて状態を変更することができません。

```haskell
let counter = 0
gameloop do
    counter = counter + 1    -- こういうことはできない！
```

なんてこった……:frowning:　状態を変えられないとか、純粋関数型な言語じゃゲームにならないじゃないか……もう駄目だ……お終いだ……:cold_sweat:　いや！　まだだ！　まだ**`Ref`**がある！ 使い方は簡単で、`newRef`で変更可能な状態を作り、`readRef`で状態の読み取り、`writeRef`や`modifyRef`で状態の書き換えができます。

```haskell
ref ← newRef 0                -- 可変な領域を作成！　初期値は0！
gameloop do
    count ← readRef ref       -- 領域から値の読み取り！
    writeRef ref (count + 1)  -- 領域への値の書き込み
```

これで、ゲームループが回るたびにカウンターがひとつずつ増えていきます。状態を読み取る前に`readRef`を呼んで領域から現在の状態を取り出さなくてはならないのがちょっと面倒ですが、三匹の豚精神で乗り切ります。逆にいえば、DOMや外部のライブラリが持つ状態を除けば、**アプリケーション自身の状態といえるものはこの`ref`の状態だけ**であり、それ以外にはまったく状態を持たないのが保証されるので、アプリケーションの状態の管理がとても楽です。なんかゲームの状態がおかしくなったなあと思ったらこの`ref`の中だけ確認すればいいのです。

それでは、さっき`gameloop`関数と一緒に定義した`getKey`関数で、キーの状態を取得し、現在の状態に反映させます。それからその状態を使って画面を再描画します。試しに、変更されている状態を、座標変換の回転量に使いましょうか。実行させてみるとこうなります。

![ss4.png](https://qiita-image-store.s3.amazonaws.com/0/64695/ee9618f4-39b3-8157-59c2-535157fd4780.png)

あああああああ！　キー操作でくるくる回っているのに、静止画だから伝わらない！　詳しくは実際に動かしてみてください。







# 画像の読み込み(二回目)！:camera:

現状の方法だと、読み込む画像が増えるたびにネストがどんどん増えていってしまいます。

```haskell
main = do
    withImage "grass.png" \grass → do
        withImage "foo.png" \foo → do
            withImage "bar.png" \bar → do
                pure unit
```

これはキモいので、**非同期処理モナド**という謎の技術を使ってこれを平坦に書けるようにします(なお、PureScriptの非同期処理モナドについては、[こちら](http://qiita.com/hiruberuto/items/98598af2b0e9a6206ef3)で筆者も紹介しています)。これを使って`loadImage`という関数を定義すると、こんなふうに書けるようになります。

```haskell
main = launchAff do
    grass ← loadImage "grass.png"
    foo ← loadImage "foo.png"
    bar ← loadImage "bar.png"
    pure unit
```

よし！:sun_with_face:　これでネストが増えていかない！:cactus:　え？　なんでこういうふうに書けるのかって？　そういう難しいことをあんまり気にしちゃだめです！





# 全体像!:chicken:

今日のところはここまでにしておいてやる！:cop:　キャンバスを取得するところとかもちょっとリファクタリングして、こんなかんじです(再現)。

```haskell
main = launchAff do
    grass ← loadImage "grass.png"
    canvas ← getCanvas "canvas"
    liftEff $ runST do
        context ← getContext2D canvas
        stateRef ← newSTRef (0 :: Int)

        gameloop do
            state ← readSTRef stateRef
            let zeroOrOne key = maybe 0 (const 1) <$> getKey key
            rx ← (-) <$> zeroOrOne 68 <*> zeroOrOne 65
            writeSTRef stateRef (state + rx)

            clearRect context { x: 0.0, y: 0.0, w: 1280.0, h: 720.0 }
            withContext context do
                translate { translateX: 1280.0 * 0.5 , translateY: 720.0 * 0.5 } context
                scale { scaleX: 1.0, scaleY: 0.6 } context
                rotate (45.0 / 180.0 * pi) context
                for_ (0 .. (chunkSize - 1)) \y → do
                    for_ (0 .. (chunkSize - 1)) \x → do
                        drawImage context grass (36.0 * toNumber x) (36.0 * toNumber y)
```



# 次のお話:chicken:

* [第三話　オンラインゲームになりました](http://qiita.com/hiruberuto/items/5d3f61339e84d2715f71)


----