Node.jsとWebSocketでゲームアプリを作ってみた

##はじめに
双方向通信を学ぶ為にNode.js+WebSocketでトランプのスピードゲームアプリを作成してみたので、ポイントとなる点をまとめてみました。

##アーキテクチャ解説

###Node.js
通常クライアントサイドで動くJavaScriptをサーバーサイドで稼働させるためのJavaScript環境。
メモリ使用量が小さく、[ノンブロッキングI/O](https://techacademy.jp/magazine/16410)による非同期処理で大量のアクセスを捌く処理に向いている。
また、Socket.ioというwebsocketのライブラリが用意されているので手軽に[双方向通信](https://wa3.i-3-i.info/word1928.html)の実装が行える。

###WebSocket
Http通信と異なり、サーバ⇒クライアントへのプッシュ通信も可能とする通信プロトコル。
今回はサーバーサイドをNode.jsで作るのでNode.jsのライブラリである**socket.io**を使用して実現します。
詳しくは[こちらの記事](https://qiita.com/k_g/items/91a20b02af276ce85e08)に纏めています。


##アプリ概要
作るアプリは、トランプのスピードゲーム。
やったことのある人も多いと思いますが、プレイヤー二人が順不同にカードを出し合うゲーム性が双方向通信を活かすのにいい題材と思い採用しました。

###構成
極力シンプルに作りたいので画面は２つ。
ユーザ名を入力して対戦相手を待つ画面と、プレイ画面だけにします。
流れとしては、
1.ユーザーは名前を入力してログイン
2.一人目の場合は待機状態とする。
3.二人目のユーザがログインした場合、一人目のユーザと二人目のユーザを対戦画面に遷移させる。
4.スピードゲーム開始。
という感じにしていきたいと思います。

##下準備
アプリを開発していくのに、まずは必要なものをインストールしていきます。

###Nodeインストール
[こちら](https://nodejs.org/ja/)の公式サイトからダウンロードしてインストール。

###package.json作成
作業ディレクトリに移動し、以下コマンドでpackage.jsonを作成。

```DOS
npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (test) speed
version: (1.0.0)
description: Sample Application
entry point: (index.js) sever.js
test command:
git repository:
keywords: Node.js,socket.io
author: k_g
license: (ISC)
About to write to <作業ディレクトリ>package.json:

{
  "name": "speed",
  "version": "1.0.0",
  "description": "Sample Application",
  "main": "sever.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "Node.js",
    "socket.io"
  ],
  "author": "k_g",
  "license": "ISC"
}


Is this ok? (yes)
```
これでpackage.jsonが出来上がります。
package.jsonとはプロジェクトのpackageを管理するもので、mavenで言うところの[pom.xml](https://waman.hatenablog.com/entry/20070718/1184796928_1)の役割を果たすファイルです。

###socket.ioインストール
npmにパッケージが用意されているので、以下のコマンドでインストール。

```DOS
npm install socket.io --save
```
ちなみに、"--save"オプションはpackage.jsonに書き込むためのオプションです。

###Expressインストール
Node.jsを使うのでexpressもインストールしておきます。
socket.io同様にnpmにパッケージが用意されているので以下コマンドでインストール。

```DOS
npm install express --save
```
expressとは、Webアプリを構築するために必要な機能が詰まったNode.jsのフレームワークです。

##開発
これで準備は完了しましたので、開発に入っていきます。
ソース全ては解説しきれないのでサーバー側・クライアント側の要点をまとめていきます。

###ログイン処理
まずは、server.jsでserver生成処理を記載します。

```javascript:server.js
var express = require('express');
var io = require('socket.io')();

// expressアプリケーション生成
var app = express.createServer();

//serverを生成
var server = app.listen(8080, "127.0.0.1", function() {
		console.log('%s: Node server started on %s:%d ...',Date(Date.now()));
		});

// socket.ioを設定
io.attach(server);
```
これでサーバ側の準備は整いました。

続いて、クライアント側の実装をしていきます。

```html:speed.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Client</title>
  <!-- client apiを読み込む -->
  <script src="/socket.io/socket.io.js"></script>
</head>
<body>
  <script>
    // 通信先のサーバを指定する
    var socket = io('http://localhost');
    
    //ログインボタン押下イベント
    function login(){
        var form = document.forms.mainForm;
        socket.emit('login',form.userName.value);

        // 対戦開始イベント
        socket.on("battle", function(data){
        // 対戦開始時処理（省略）
    }
  </script>
～～～中略～～～
  <input type="text" name="userName"></input>
  <button id="login" onClick="login()">ログイン</button>
</form>
</body>
</html>
```
\#説明のため簡略化しています
クライアント側では、まず初めに**"io(<サーバ>)"**という処理を呼び出しています。
この処理を行うことによって、サーバ側のnode.jsとコネクションを確立しています。そして確立したコネクションを"socket"として保持しており、この"socket"を使用してサーバ側処理を呼び出したり、受け取ったりしていきます。
処理の書き方ですが、サーバ側処理を呼び出す際には**socket.emit**を使います。上記のように記載することで、先ほどのサーバ側で記載した"login"という処理を"userName"を引数に呼び出すことが出来ます。
逆に、サーバ側から呼び出される処理は**socket.on**を使って定義します。ここでfunctionを定義しておくと、サーバ側から"emit"処理でクライアント側の処理を呼び出すことが出来ます。

続いて、サーバ側の処理を記載していきます。

```javascript:server.js
// ロジック処理
var logic = require('./SpeedLogic.js');

// 待機中フラグ
var waitPlayer = "0";

// 一時部屋ID
var tmpRoomId;

//接続確立時の処理
io.sockets.on('connection', function(socket) {
    //接続切断処理
    socket.on('disconnect', function() {
        console.log("disconnect");
    });

    //ログイン時処理
    socket.on('login', function(userName) {
        // 待機中プレイヤーが居る場合
        if(waitPlayer == "1"){
          // 待機中の部屋IDにjoin
          socket.join(tmpRoomId);

          // ユーザ名リストを作成
          var nameList = new Array();

          // 待機中ユーザ名と新規ユーザ名を追加
          nameList.push(tmpName);
          nameList.push(name);
            
          // Dtoを生成
          var speedDto = logic.createSpeedDto(tmpRoomId,nameList);

          waitPlayer = "0";

          // 対戦処理を呼び出し
          io.to(tmpRoomId).emit('battle',speedDto);
        
        // 待機中プレイヤーが居ない場合
       } else {
          // 待機プレイヤーに1を設定
          waitPlayer = "1";

          // 部屋IDを生成
          tmpRoomId = logic.createRoomId();
          socket.join(tmpRoomId);

          // ユーザ名を一時変数に追加
          tmpName = name;
        }
    });
});
```
では順番に見ていきましょう。
文字列とfunctionを引数に**"io.sockets.on～"**という処理が記載されています。これがsocket.ioでのメソッドの宣言の仕方となります。
第一引数には文字列が入っていますが、これはクライアント側からサーバ側処理を呼び出す際の名称になります。クライアント側から引数の名称で呼び出されると、第二引数のfunctionが呼び出されるようになっています。
ここでは、接続時に"connection"が呼びされ、その中に"login"のメソッドを定義しています。

####部屋分けとは？
サーバ側で行っている、**部屋分け**について解説します。
行っている処理としては、
1.待機中ユーザが居ない場合
　1-1.待機中ユーザフラグを立てる。
　**1-2.部屋IDを振って、部屋に入る(join)。**
2.待機中ユーザが居る場合
　2-1.待機中ユーザフラグを削除する。
　**2-2.部屋IDを使って部屋に入る(join)。**
　2-3.部屋IDに紐づく通信のクライアント側対戦開始処理を呼び出す。

簡単に説明するとこんな感じになっています。
ここで、1-2と2-2で行っている**部屋分け処理**について解説します。
サーバ側から"emit"で処理を呼び出す際は、そのまま呼び出すと確立している通信**全て**のクライアント処理が呼ばれます。
ここでは、ログイン⇒対戦相手が居る⇒対戦開始、という流れにしたいのですがそのまま呼び出すとすべてのユーザの対戦開始処理が呼ばれてしまいます。
そこで、socket.ioの部屋分けという機能を使います。使い方は簡単で、部屋IDを適当に決めたら"socket.join(部屋ID)"処理を使って部屋に入れます。そしてクライアント側処理を呼び出す際に"io.to(部屋ID).emit(処理ID)"と呼べば、同じ部屋IDに入っている通信のクライアント側処理を呼び出す事が出来ます。

###対戦処理
続いて、対戦処理を作っていきます。
※ソースは長すぎたので割愛

やることとしては
①カードを台札に置いた際の処理
②片方のプレイヤーの手札が無くなったときの処理
の二つです。これもサーバ側・クライアント側で必要な処理を作り、クライアント⇒サーバ⇒クライアントの順に処理が動くのですが、ポイントはサーバ側からクライアント側に処理結果を戻すときに**プレイヤー二人とも同じ結果が戻る**という点です。

処理の流れとしては、どちらかがカードを台札に置いた場合、サーバで処理を行ってクライアントに結果を戻すのですが、どちらのプレイヤーにも処理を戻す必要があります。（画面に結果を反映するため）
その際に、戻り値はどちらのプレイヤーにも同じ値が戻ってくることになるので、クライアント側は自分がカードを置いた場合と相手がカードを置いた場合のどちらも同じ処理で画面を描画しなければいけません。

ここが普通のアプリと大きく異なる点で、クライアント側は自分がサーバを呼び出した時と相手がサーバを呼び出した時のどちらの場合でも正しく動くようにしなければいけないのです。

###完成
以上の点に注意しつつ、サーバ側とクライアント側の実装を行いました。
画面はこんな感じになりました。
![speed.jpg.png](https://qiita-image-store.s3.amazonaws.com/0/222268/e8d0b04e-3ade-4b5b-c527-523efa93d545.png)

###まとめ
node.jsとsocket.ioを使って実装する際のポイントに絞って解説していきましたが、作り方自体は非常にシンプルで、簡単なチャットアプリのようなものであればすぐに作れてしまうのに驚きました。
むしろ苦労したのは今までのアプリと違い、サーバ・クライアントが1:1ではなく1:多であることを意識した処理にしなければいけない所で、いろいろと工夫しなければいけませんでした。

とはいえアプリを作る事自体は簡単ですので、興味のある方はぜひトライしてみてください。