ルーム型オンラインゲームの作り方
La-keibaのAgentとバックエンドの通信にはpythonのwebsocketを使うことになっている。なぜpythonかというと、AgentはpythonのLightGBMを使ってトレーニングされた勝ち馬予測モデルを使っているから。となると、python以外に使える物はなく、仕方なくpythonを使うことにした。あと、プロセス間の通信にはhttp通信を使ってもいいんだけど、httpは一回きりの通信だし、モジュール間で双方向通信が必要なことが後々生じることもあるだろうと思いWebsocketを使うことにした。大(websocket)は小(htt)を兼ねるともいうし。(しかし、websocketの方がhttpよりオーバヘッドが大きいのも確か) websocketでも普通にJSONが扱える。
pythonを使ったwebsocket通信をするために、このサイトで勉強したのだが、これがかなり優秀だった。今まで手探りでやっていた、websocketを使ったルームベースのアプリ(ルーム型チャット等)のフレームに対する最適解を提示してくれるものとなった。このルームベースアプリケーションのフレームは様々なアプリに適応できる。ぱっと思いつくものでいうと、将棋とか、一対一で行われるゲームはそうだし、一対一ではなくとも、ルームという概念が存在するゲーム(ないしはアプリ)にはこれが適応可能である。BFだって通信に使っているプロトコルはおそらく独自の高速プロトコルだけど、全体で見た時のフレームはこれと同じだと思う。まあ、ルームを一つの物理サーバに複数立てるのではなく、1サーバに対し1ルームという感じではあると思うが。
チュートリアルでは、まるばつゲームの拡張版であるconnect4というゲームを扱った。ポイントだと思う点を以下にまとめる。
ゲームそのものを管理するゲームエンジンの存在
オブジェクト指向の真骨頂がここで使えるわけです。「ゲーム」をオブジェクトとみなして、ゲームとインタラクションする手段として、ゲームオブジェクトがメソッドを提供する。メソッドはコントローラみたいなものだね。内部の状態もゲームオブジェクトは保持する。
ルームオブジェクト
そして、特定のゲームオブジェクトと、そのゲームをコントロールする権利を持つwebsocketをまとめて保持するオブジェクト(以下、ルームオブジェクト)を作り、このルームオブジェクトにidをつける。c++でもそうだったけど、websocketオブジェクトは、sendメソッドを介していつでもフロントエンドにメッセージを送れる仕様になっている。つまり、フロントエンド間(2ユーザ間)でメッセージをやり取りするには、ルームオブジェクトでそのルームに所属するwebsocketを論理的に管理し、到着したメッセージをルームに所属するwebsocketオブジェクトに返せればよい、ということになる。
JSONでメッセージのやり取り
jsonメッセージの一番最初には、メッセージの種類を表す、typeを 持ってきます。そのあとは、typeに即したメッセージのやり取りをするわけですね。これがアプリケーション層でのメッセージプロトコルですね。 前に実装した独自の通信プロトコルでもそうだったけど、やはり、やり取りするメッセージの一番最初にはメッセージのタイプを持ってくるのが普通みたいですね。
タイプには例えば、initや、join、watch,playがある。initは最初にやりとりするメッセージのことでしょう。joinは、そのルームへのjoin。watchは、そのルームの観戦。playはプレイヤーの動きが入っています。typeでplayが来たら、内容に合わせてゲームオブジェクトにメッセージを送ります。 pythonのwebsocketには、
websocket.wait_closed()
という、そのフロントエンドから送られてくる通信はreceiveしないけど、サーバ側からは送れる、っていう便利なメソッドがあります。これを使うことで、そのwebsocketセッションを傍観者にすることが可能です。観戦だけできるようにするってことですね。 c++にはおそらくこれがないんだけど、websocketを継承して、新しいmywebsocketクラスを作り、その中でis_player変数とかを作ることで、傍観者かそうでないか、を見分けることもできますね。これはなかなかに便利だと思います。
フロントエンド(javascript)の処理
javascriptはすでにイベントドリブンな仕様になっている。よって、イベントを登録するだけでよい。メッセージを受信した時の動作を規定する関数と、メッセージを送信した時の動作を規定する関数を作る。関数の引数にはwebsocketオブジェクトを渡しておく。その中で、websocket.addEventlistner()をする。
便利だと思った機能
便利だと思ったメソッドは、replayメソッドだね。gameオブジェクトでは、過去のムーブをすべてレコードしてある。そのレコードをひとつづつ送れば、すべて再現できるようになっている。 これは、例えば途中から入ってきた観客にこれを使って得られるメッセージを送る、といった用途で使える。
websocketとhttpの使い分けについて
Websocketとhttpどっちがいいとかはなくて、それぞれ適切な用途があります。 httpは一回きりのステートレスな通信をするためのプロトコル。しかし、webアプリではhttpで通信をするけど、誰が通信してきているかを管理したいことがある。そこで、Cookieを使ってセッションを管理する。 http+Cookie = ステートフルになる。なのでhttpのセッション管理が必要なのは、フロントエンドからアプリを使いたいユーザだけ。 そして、httpはバックエンドのモジュール間の通信にも普通に使える。リクエストにJSONをのっけてpostしたり、url自体にpostする内容をのっけたりすることもできる。(RESTful)。モジュール間での通信がセッションを必要としない一度きりのもであればhttpを使えばいいし、そうでない場合はwebsocketを使えばいい。ブラウザでのゲームはステートを保持しないといけない、かつサーバからブラウザに通信が来ることがあるからhttpでは対応できなくて、Websockeを使わないといけない。