モナドのまほう　第３話『オンラインゲームになりました』

* [第二話](http://qiita.com/hiruberuto/items/5a2b613378d07906c5c5)　←前回


つづきです。

# :fire:Firebase:fire:で一気にオンラインゲームに！

**そろそろオンラインゲームっぽくしたくなった**(思いつき)ので、オンラインゲームっぽくしておきましょう。なおPureScriptのFirebaseのバインディングは他の人が書いたものがあったんですが、それが古くて動かなかったので、筆者が自分で書きました。つらい。それではFirebaseに接続！　もちろんコードは基本的にJavaScriptとほとんど同じ！　雰囲気を知ってほしいので、JavaScript版のコードとPureScript版のコードを並べて書いてみます！ JavaScriptならこう！

```haskell:JavaScript
var firebaseApp = firebase.initializeApp({
    apiKey: "AIzaSasdfasdfasfiTNu3gGRNgqRsadlR0",
    authDomain: "zombie-c5857.firebaseapp.com",
    databaseURL: "https://zombie-c5857.firebaseio.com/",
    storageBucket: "zombie-c5857.appspot.com",
    messagingSenderId: "135964071397"
});
```

PureScriptならこう！

```haskell:PureScript
firebaseApp ← initializeApp {
    apiKey: "AIzaSasdfasdfasfiTNu3gGRNgqRsadlR0",
    authDomain: "zombie-c5857.firebaseapp.com",
    databaseURL: "https://zombie-c5857.firebaseio.com/",
    storageBucket: "zombie-c5857.appspot.com",
    messagingSenderId: "135964071397"
}
```

いやっほおおおおう！:stuck_out_tongue_closed_eyes::stuck_out_tongue_closed_eyes::stuck_out_tongue_closed_eyes:　Firebaseきたああああああ！　接続ができたら`signInAnonymously`で適当に匿名サインイン！:metal:

```js:JavaScript
firebase.auth().signInAnonymously();
```

```haskell:PureScript
auth firebase >>= signInAnonymously
```

そして`onAuthStateChanged`を仕掛けておくと、サインインが成功したらコールバックで知らせてくれます。このとき`User`オブジェクトが手に入るので、適当にユーザIDとかを保存！:bank:

```js:JavaScript
firebase.auth().onAuthStateChanged(function(user){
    state.userID = user.uid;
    console.log("signed-in to firebase anonymously. Your id is " + uid user.uid)
})
```

```haskell:PureScript 
auth firebase >>= onAuthStateChanged \user → do
    modifySTRef stateRef _{ userID = uid user }
    log ("signed-in to firebase anonymously. Your id is " <> uid user)
```

自分のキャラの位置が変更されたら`set`でFirebaseのデータを更新！:pencil: このときに先ほど保存しておいたユーザIDをキーに使っています。`foo`っていうIDを持つキャラクターは`/users/foo/position`っていう位置に保存するっていう感じ。

```js:JavaScript
firebase.database().ref("/users/" + state.userID + "/position").set(state.player.position)
```

```haskell:PureScript
database firebase >>= ref ("/users/" <> state.userID <> "/position") >>= set (toForeign state.player.position)
```

あとは`on('value', callback)`を仕掛けて待ち構えます！:turtle:　値が更新されるとコールバックされて新しい値が降ってきますから、それを現在の状態に設定します。

```js:JavaScript
firebase.database().ref("/users").on("value", function(snap){
    state.players = snap.val();
});
```

```haskell:PureScript
database firebase >>= ref "/users" >>= onValue \snap → void do
    modifySTRef stateRef _{ players = unsafeFromForeign (val snap) }
```

ほら、JavaScriptもPureScriptもコードはあんまり変わらないでしょう？　『純粋関数型プログラミング言語では副作用の扱いが難しい』っていう噂は、純粋関数型をおとしめようとする闇の勢力:alien:の陰謀です！　さて、あとはFirebaseから送られた情報を元にそれぞれのキャラクターを描画します:pencil2:   あと、詳しくは説明しませんが、キー入力でキャラクターが動きまわるようにしました。ちなみのこの裏で更に座標変換のために線形代数ライブラリを自分で書いたりとなにげに苦労しています（血涙）。それでは、２窓で開いてみると、一方が歩きまわるとちゃんと他方のウィンドウのキャラクターも連携して動き回ることが確認できました！:feet::feet::feet::feet:

![ss5.png](https://qiita-image-store.s3.amazonaws.com/0/64695/fa6f0e29-0cbd-f86e-9129-86a6de6eeabd.png)

おおおおおおおオンラインゲームだ！:satisfied::satisfied::satisfied:　かがくのちからってすげー！　たったこれだけでオンラインゲームっぽく同期ができちゃうなんて:fire:Firebase:fire:最高だよアンタ！　多少の遅延は確認できますが、今回はFPSとか格ゲーじゃないのでそこまでシビアに考えなくていいと思います。

Firebaseのコンソールを開くと、キャラクターが動き回るのに合わせてデータベース内のデータが変化しているのがリアルタイムに確認できます。新たに追加された値は$\color{green}{緑色}$、変更された値は$\color{orange}{黄色}$、削除された値は$\color{red}{赤色}$で表示されます。

![firebase.png](https://qiita-image-store.s3.amazonaws.com/0/64695/b7721386-8854-82f9-4743-9ff688e38242.png)

このときコンソールで直接データを編集することもでき、例えば`position`の値をいじると対応するキャラクターの位置もリアルタイムで移動したりします。たのしい！:stuck_out_tongue_closed_eyes::stuck_out_tongue_closed_eyes::stuck_out_tongue_closed_eyes:









# シーンの切り替え！:arrows_clockwise:

タイトル画面がないとゲームっぽくないですよね！　そんなわけで、タイトル画面とロード中画面、プレイ画面を切り替えられるようにします。さて、現在どの画面になっているかの状態を表すのに、**代数的データ型**を使いましょう！

```haskell
data Scene = Title | Loading | Playing State
```

このデータ型では、

* `Title`つまりタイトル画面、
* `Loading`つまりサインイン待ち中
* `Playing State`つまりゲームプレイ中の画面でしかも`State`型の状態を持っている

という表現になっています。`Scene`型のデータは、`Title`か、`Loading`か、`Playing`のいずれかで、`Playing`の場合だけ`State`型のデータを内部に抱えているわけです。

なお、`Title`とか**名前付けが死ぬほど雑**ですが気にしない気にしない！　もし衝突したらちゃんとコンパイラがエラーで教えてくれますから。あとで気に入らなくなったら、その時に名前を変えればいい話です。名前を変更した場合も、変更すべきポイントをすべてコンパイラが教えてくれます:cop:

ゲーム開始時はタイトル画面なので、`newRef `を初期化するときには`Title`で初期化しましょう。

```haskell
stateRef ← newRef Title
```

そして、ゲームループ内では`case-of`文の条件分岐によって現在の状態にしたがって場合分けをします。

```haskell
gameloop do                              -- この内部がゲームループ
    scene ← readRef stateRef           -- 現在の状態を取得
    case scene of                        -- sceneの値に応じて場合分け
        Title -> do                      -- Titleの場合はここに来る
            pure unit -- do something
        Loading -> do                    -- Loadingの場合はここに来る
            pure unit -- do something
        Playing state -> do              -- Playingの場合はここに来る。しかも変数stateにはState型のデータが入っている
            pure unit -- do something
```

場合分けをしたときに、状態が`Playing`の場合の時だけ`State`のデータに触ることができるようになっています。なるべく余計な状態は持たないのが楽をするコツです。

そして、タイトル画面のときに`z`キーを押すと、現在の状態をプレイ中の状態へと変更します。

```haskell
Title -> do                                   -- Titleの場合は
    z <- getKey 90                            -- zキーの状態を取得
    when (z == Just 0) $ void do              -- zキーが押されてる場合の条件分岐
        auth firebase >>= signInAnonymously   -- firebase.auth().signInAnonymously()で匿名サインイン
        writeRef stateRef Loading           -- 現在の状態をLoadingに変える
    drawImage context titleImage 0.0 0.0
```

それから、`onAuthStateChanged`でコールバックされてサインインが成功した時に、`Loading`状態から`Playing`状態へと移行します。

```haskell
-- onAuthStateChangedでユーザ認証の状態が変更した時のコールバックを仕掛ける
auth firebase >>= onAuthStateChanged \userMaybe → case userMaybe of      -- caseで場合分け
    
    -- Nothingの場合はサインアウトした時
    Nothing -> void do
        writeRef stateRef Title    -- タイトル画面に戻る

    Just user -> do
        -- Playing状態へと変える
        writeRef stateRef $ Playing { userID: uid user, rotation: defaultRotation, player, players: empty } 

        -- 後片付け用にonDisconnectを仕掛ける
        database firebase >>= ref ("/users/" <> uid user) >>= onDisconnect >>= remove
```

これで、

1. 最初はタイトル画面
2. zキーが押される
3. サインインのリクエスト＆ローディング画面へ
4. サインイン成功
5. プレイ画面へ

という状態遷移が実現できました。あとはそれぞれの場合に応じて画面も描画します。



# OAuthを利用したサインイン！:key:

`Firebase`ではOAuthを利用したサインインの処理も死ぬほど簡単です！ たとえばTwitterでサインインしたかったら、`newTwitterAuthProvider`(JavaScriptでは`new Firebase.TwitterAuthProvider()`)で使いたい認証プロバイダを表すオブジェクトを作成し、`signInWithPopup`を呼びだすだけ！　成功すると`onAuthStateChanged`がコールバックされます！　

```haskell
provider ← newTwitterAuthProvider
auth firebase >>= signInWithPopup provider authObj onError onSuccess
```

うひょおおおおおおおおおお!:grin::grin::grin: コードとしては本当にこのくらい！　ちなみにこれを書いている時点で、匿名、Twitter:bird:、GitHub:octocat:、Google:crocodile:の４種類でログインできるようになっています。Firebaseでは普通にメールアドレスとパスワードで認証する方法も提供されているんですが、そんなの入力面倒くさくて誰も使いたがらないでしょうからゲームに実装する気はありません。Facebook:person_with_blond_hair::blue_book:は筆者が使っていないのでパス！


![login.png](https://qiita-image-store.s3.amazonaws.com/0/64695/be5f00d7-9185-930d-5f1f-ccf28608ca13.png)

これで面倒な登録作業なしでゲームを始められます！




# そういえばデプロイもFirebaseでやってます！:cloud:

Firebaseには静的コンテンツのホスティングの機能もあるので、デプロイにはこれを使ってます。コマンドラインツールのfirebase-toolsを適当にインストールして設定したら、あとは`deploy`コマンド一発！

```bash 
$ firebase deploy 
```

これだけで`public`ディレクトリに入ったプロジェクトがデプロイされます！　簡単すぎ！　自分の持っているドメインを接続することもできます。ウェブアプリケーションはもうこいつだけでいいんじゃないかな！:stuck_out_tongue::stuck_out_tongue::stuck_out_tongue:







# 次のお話:chicken:

* [第四話](http://qiita.com/hiruberuto/items/44f118b649367f010cb0)