ASP.NET Core版 対戦型のマインスイーパーをつくる(1)の続きです。

と書くべきところを名前空間を省略して

と書くことにします。

接続時の処理

接続されたときはReceiveConnectedを送信して、接続に成功したことをクライアントサイドに伝えます。また新たなユーザーが接続したとき、接続しているのはそのユーザーだけだったというときは、Game.Initメソッドを呼び出して地雷が埋め込まれているマップを生成しなおします。

そのあとマップの状態とプレイヤーの名前とスコアを文字列に変換してクライアントサイドに送信します。

送信するプレイヤー名とスコアをカンマ区切りの文字列にして取得する処理を示します。

プレイヤー名をスコアが高い順に表示させたいのでソートしています。またクライアントサイドではhtmlにそのまま埋め込むのでエスケープ処理をおこなっています。

切断時の処理

切断時の処理を示します。

プレイ中のユーザーが離脱またはなんらかの理由で通信が切れてしまった場合、Game.PlayersからPlayerオブジェクトを取り除かなければなりません。また接続しているクライアントが格納されているClientProxyMapからも削除します。そのあと全ユーザーにプレイヤーがひとりいなくなったことを通知するためにReceiveScoresを送信します。

ゲームに参加するときの処理

ユーザーがゲームに参加するときの処理を示します。

Game.Playersのなかに第一引数をキーとする値が見つかった場合は、現在プレイ中なので二重に参加する処理がおこなわれないようにしています。

それ以外のときはPlayerオブジェクトを生成してGame.Playersのなかに格納しています。そしてたしかにそのユーザーがゲームに参加したことを伝えるためにReceiveGameStartedを送信します(これは参加したユーザーのみ)。そのあと全ユーザーにゲームに参加しているプレイヤー名とスコアの表示を更新するためにReceiveScoresを送信します。

セルを開く処理

プレイヤーがセルを開こうとしたときの処理を示します。

まずゲームに参加していないユーザーがクリックした場合は無視します。またクライアントサイドのcanvas内であってもセルが存在しない部分をクリックした場合も無視します。

ゲーム中にプレイヤーが自分の名前を変更しているかもしれないので、そのつど引数を受け取ってPlayer.Nameにセットしています。もし名前欄が空欄の場合はPlayerクラス内で「名無しさん」という名前がつけられます(前のページ参照)。

開こうとしている場所に自分で立てた旗がある場合はクリアします。そしてまだ開けられていないセルで地雷が埋まっていないセルであればプレイヤーのスコアを加算してGame.IsMapOpen[row, col]をtrueにします。そのあとそのクライアントにReceiveOpenを送信します。

地雷が埋まっているセルを開こうとした場合は一発ゲームオーバーです。Game.Playersから該当するPlayerオブジェクトを取り除き、そのユーザーにはReceiveGameOverを送信します。

いずれの場合も全ユーザーのディスプレイの描画内容を変更しなければなりません。そこで全ユーザーに残りのセルの数、全プレイヤーの名前とスコアを文字列に変換して送信します(ReceiveUnopenedCellCountとReceiveScores)。

セルの状態も送信しなければなりませんが、プレイヤーによって旗を立てている場所が違うので注意が必要です。Game.GetStringFromMapメソッドにPlayerを渡して送信用の適切なデータを取得します。

またプレイヤーがセルを開いた結果、残りのセル数が0になる場合があります。この場合はステージクリアとなり、3秒後に新しいマップが生成され表示されます。

ステージクリア時の処理

セルを開いた結果、Game.GetUnopenedCellCount() == 0になった場合はステージクリアです。この場合は3秒後にTimer_Elapsedを実行するタイマーをセットします。

イベントハンドラTimer_Elapsedが実行されたら第一引数として渡されたタイマーを停止してDisposeします。

そのあと新しいマップをつくります。さらに全ユーザーが立てた旗をクリアします。そして全ユーザーにマップの状態と残りのセル数を伝えるためにReceiveDrawとReceiveUnopenedCellCountを送信します。

旗を立てる処理

旗を立てるときの処理を示します。

この場合もセルを開くときと同様、ゲームに参加していないユーザーがクリックした場合は無視します。またクライアントサイドのcanvas内であってもセルが存在しない部分をクリックした場合も無視します。

ゲーム中にプレイヤーが自分の名前を変更しているかもしれないので、そのつど引数を受け取ってPlayer.Nameにセットしています。そのあとPlayer.Flagsを調べて、その場所に旗が立っていない場合は旗を立て、すでに立っている場合は取り除きします。そしてReceiveDrawを送信します。

新しく立てられた旗を描画する必要があるのはそのプレイヤーだけなので送信するのはそのユーザーだけです。

スコアランキングへの登録

まずスコアを格納するクラスを定義します。

次にゲームオーバーになったときにスコアランキングに登録する処理を示します。

スコアランキングのデータが書き込まれているテキストファイルを読み出し、Hiscoreのリストを生成します。そのあとゲームオーバーになったプレイヤーでHiscoreオブジェクトを生成してリストの最後に追加します。そのあとスコアが大きい順にソートして先頭から30件取り出してファイルに保存します。

スコアランキングを表示させる

スコアランキングを表示する処理を示します。

まずゲームを表示するページと同じディレクトリにhi-score.cshtmlをつくります。

内容はタイトルとスコアランキングの情報が書かれているテキストファイルのパスが違うだけで、AspNetCore.SignalRにおける処理 ASP.NET Core版 ボンバーマンのような対戦型ゲームをつくる(3)の終わりのほうに書かれているものとほとんど同じです。

Pages\Minesweeper\hi-score.cshtml