前回までに作成したオンラインゲームにランキング機能をつけます。

⇒ 動作確認はこちらから

ランキング機能をつける

ひとりでプレイしているところをYouTubeで公開して、「他の人がプレイしてくれないと正しく動いているのか検証できません。我こそは…という人がいたらお願いします」と概要欄に書いていたらさっそくアクセスしてくれた人がいました。ありがとうございました。

公開してからわかった問題点

実際に複数人(2人だけだけど)でやってみて思ったことは、

プレイしている人数がわかるようにする
ランキング機能をつける

このような機能をつけたほうがよさそうです。ただプレイしている人数がわかるようにしてしまうと閑古鳥が鳴いていることもわかってしまうという困った問題もおきます。

まあそれはいいとして今回はランキング機能を追加します。

プレーヤーに名前を登録してもらう

まずはプレーヤーに名前を登録してもらいましょう。

ボタンがクリックされたらstart関数が実行されます。これまではページにアクセスしたらすぐにサーバーにclient_to_server_firstが送られ、すぐにゲームに参戦することになっていましたが、今回はstart関数が実行されないとゲームに参加することはできません。

client_to_server_firstイベントではフォームに入力した自分の名前もサーバーにおくります。これでランキングに自分の名前が表示されるようになります。

またゲームに参加したらボタンは非表示にします。何回もボタンをおされると同じIDをもつプレーヤーが大量生産され、しかも実際に操作できるのはひとつだけという問題点があるです。ゲームオーバーになったあと再度ゲームをする場合はF5キーを押してください。

snake-game/snake-game.js

SnakeGame_Playerクラスにプレーヤーの名前を保存する

ランキングではゲーム中のプレーヤー限定のランキングとゲームオーバーになったプレーヤーも含めたランキングの2種類をつくります。

サーバー側ではすでにゲームオーバーになったものも含む上位者を格納する配列を用意します。

app.js

SnakeGame_Playerクラスにプレーヤーの名前を保存する関数を追加します。

app.js

そしてclient_to_server_firstイベントでクライアントからデータ(フォームに入力された名前)をうけとったらSnakeGame_PlayerクラスのSetPlayerName関数を呼び出して名前をつけます。

app.js

ランキングの変動に対応させる

ランキングが変更されるとすればプレーヤーの誰かが得点したときです。これに該当するのがCheckEatFood関数内で餌を食べたと判定されたときです。

餌を食べた場合はこれまでのプレーヤーの上位者の配列に自分を追加します。このときすでに自分が登録されているかもしれないので追加するまえに自分を取り除きます。自分を追加したら得点(自分自身の長さ)でソートし、上位10名だけ取り出します。これで上位者10名を知ることができます。

app.js

ランキングの変動をクライアント側に伝える

ランキングを表示させるにはクライアントにランキング情報を伝えないといけないのでSnakeGame_Dataクラスにそのためのメンバを追加します。

app.js

あとはserver_to_client_objectsイベントで上位者10名をクライアントに送信すればクライアント側でうまくやってくれます。

app.js

クライアント側でランキング情報の表示する

クライアント側でserver_to_client_objectsイベントによってデータを受信したときに新しく作成したDrawRankig関数でランキング情報を表示させます。

DrawRankig関数に渡す引数は、第一引数は現在プレイ中のプレーヤー全部、第二引数はプレイ中ではないものも含めた上位10名です。ただしランキングがつねに画面右側に表示されるのはうっとうしいかもしれないのでチェックボックスで表示と非表示を変えることができるようにします。

snake-game/snake-game.js

ランキング情報は現在プレイ中の上位7名、ゲームオーバーになった人も含めて上位10名を表示させます。

DrawRankig関数ですが、以下のようなものになります。

snake-game/snake-game.js

あとプレーヤーの描画をするときに名前も表示させることにしました。

ランキング情報をデータベースに保存する

これで一応、ランキングを表示させることができるのですが、メンテナンスでサーバーを停止した場合、現在プレイ中でないものを含む上位10名の情報は消えてしまいます。そこで消えてしまわないようにSQLiteに保存してサーバーが起動したら読み込んでデータが消えないようにします。

やるならSnakeGame_PlayerクラスのなかでCheckEatFood関数を実行した結果、highRankigPlayersの内容が変更されたときでしょうか? ただこれは頻繁におきるので、もっとデータベースにデータを保存する回数を減らしたいと考えたくなります。そこで誰かがゲームオーバーになったときにhighRankigPlayersの内容をSQLiteに保存することにします。

プレイヤーが死亡したら死亡したプレーヤーがいた位置に餌を配置する関数がSnakeGame_Playerクラスのなかにあるのですが、この関数の最後にhighRankigPlayersの内容をSQLiteに保存すればデータベースの更新作業の回数を減らせます。

app.js

上位プレーヤーのデータを読み出す

最初にアプリケーションが開始されたらSQLiteに保存されている上位プレーヤーのデータを読み出して配列 highRankigPlayersに読み込む処理を示します。

まずNode Package Managerでsqlite3をインストールします。

app.js

追加したLoadHiscorePlayers関数を示します。最初にアプリケーションを開始したときにはテーブルがつくられていないので作ります。テーブル名はtable_highscoresにします。次にデータが保存されていたら読み出します。このときdb.serialize(() => ・・・})とやらないとテーブルが作成されるまえにデータの読み込みが開始されるというエラーになります。db.serialize(() => ・・・})のなかでテーブルを作成した後にデータを読み込みの処理がおこなわれるようにします。もっともテーブル作成処理がおこなわれたときはデータは存在しませんが・・・。

db.all(“select * from table_highscores”, callback)のなかでデータの読み出しと読み出したデータのソート、上位10名のプレーヤーを 配列 highRankigPlayersに格納する処理をしています。これらはコールバック関数のなかでやらないとうまくいきません。

app.js

ランキング情報を保存するタイミング

次にプレーヤーが死亡したときに配列highRankigPlayersに格納されているデータをSQLiteに保存する処理を考えます。

最初にhighscores.dbに格納されているデータを削除して、そのあと配列highRankigPlayers内に格納されている上位プレーヤーのデータを保存します。

app.js