ASP.NET coreで対戦型『殺しの七並べ』をつくる(2)の続きです。今回はASP.NET Core用のSignalRで使用するHubクラスを定義します。

Program.csの編集

まずASP.NET Coreで新しいプロジェクトを作成したときに自動生成されるProgram.csを編集します。

Program.cs

このあとKillSeven.GameHubを定義します。

Hubクラスの定義

以降は名前空間部分を省略して表記します。

フィールド変数

静的フィールド変数として以下を定義します。

接続のイベント処理

接続のイベントが発生したときにおこなわれる処理を示します。

辞書にContext.ConnectionIdとIClientProxyを登録します。そして接続に成功したユーザーに対して”SucceedConnectionToClient”イベントを送信して接続に成功したことを知らせます。

またはじめて実行されたときはタイマーの初期化をおこないます。接続されているユーザーが0から1になったときはタイマーをスタートさせます。

辞書に追加したり削除するときに非同期処理でforeach文を実行すると例外がおきるので、ここがクリティカルセクションになります。そこで排他ロックをかけます。

切断のイベント処理

切断のイベントが発生したときにおこなわれる処理を示します。

辞書からContext.ConnectionIdを削除します。通信が切断されたユーザーはこれまでゲームに参加していたプレイヤーかもしれません。そこでGame.RemovePlayerメソッドを実行します。もし現在ゲームに参加しているユーザーが離脱したのであればプレイヤーからも削除する処理が実行されます。そのあとプレイヤーの数を確認します。プレイヤーの数が0の場合(全員がNPCの場合)はすべての接続ユーザーに対して全員が試合放棄したことを通知します。

この場合もクリティカルセクションが存在するので、該当部分には排他ロックをかけます。

エントリー時の処理

ユーザーがエントリーボタンをクリックしたときの処理を示します。

プレイヤー名が設定されていない場合は”名無しさん”とします。Gameオブジェクトが存在しない場合は生成してイベントハンドラを追加します。

そのあとGame.AddPlayerメソッドを実行します。ゲーム参加の処理が正常におこなわれた場合は-1以外の値が返されます。この場合はそのユーザーに”SucceedEntryToClient”を送信します。-1が返されたときはゲームに参加する処理がおこなわれなかった場合(NPCが存在しなかった)なので”FailuredEntryToClient”を送信します。

Game.AddPlayerメソッド実行中にGame.Updateメソッドが実行されると例外が発生する可能性があるので、排他ロックをかけます。

ゲーム開始時の処理

ゲーム開始時の処理を示します。ゲームが開始されたら接続しているユーザー全員に”GameStartedToClient”を送信します。

プレイヤーが着手しようとしたときの処理

プレイヤーがカードを出そうとしたときにおこなわれる処理を示します。

Game.PlayerPutOutCardメソッドを実行するときにCardのリストに対して要素の追加と削除がおこなわれるため、ここがクリティカルセクションになります。プレイヤーがもっているカードが変化した場合はこれをユーザー全員に通知する必要があるのでSendDataメソッド(後述)でイベントを送信します。

プレイヤーがもっているカードが変化したときに、これをユーザー全員に通知する処理を示します。

通知する内容は全ユーザー共通のものと各プレイヤー独自のものがあります。ゲームに参加しているプレイヤーの名前、所持するカードや殺されたカードの枚数、順位などは全ユーザー共通です。しかしプレイヤーがもつカードはそのプレイヤー以外には送信しません。これらをjsonに変換してクライアントサイドに送信します。

全ユーザーに”CardsToClient”イベントとともに第一引数には共通のものと、第二引数には各プレイヤー独自のものを渡します。第三引数は手番であればtrue、そうでない場合はfalseです。

送信すべきデータを取得するとき、送信先を取得するときにクリティカルセクションがあるので、ここでも排他ロックを実行します。

着手が成立したときの処理

着手が成立したら効果音を再生します。自分が着手したときとそうでないときの効果音を変えたいため、着手したプレイヤーに対しては”PutCardToClient”イベントを、そうでないユーザーには”OthersPutCardToClient”を送信しています。

着手したプレイヤーに対応するIClientProxyやそれ以外の接続しているユーザー全員のIClientProxyの配列を生成しているときに_clientProxiesに要素が追加されたり削除されると例外が発生するため、排他ロックをかけています。

不正な着手に対する処理

ユーザーが着手できない場所にカードを置こうとした場合は、そのことを該当ユーザーに通知するために”DenyCardToClient”イベントを送信します。

カードが殺されたときの処理

カードが殺されたときは殺されたカードのなかに自分のカードが含まれていたかどうかで効果音を変えたいので、イベントハンドラの引数を調べて、送信先のユーザーのカードが含まれている場合は”KilledOwnToClient”イベント、そうでない場合は”KilledOthersToClient”イベントを送信します。

パスする場合の処理

ユーザーであるプレイヤーが出せるカードがない場合、制限時間以内にカードを出さなかった場合は自動的にパスとなります。この場合はOnPlayerPassedメソッドが呼び出されるので、そのユーザーに対して”PlayerPassedToClient”を送信します。

ゲームが終了したときの処理

ゲームが終了したらOnGameFinishedメソッドが呼び出されます。引数を調べればゲームの結果がわかるので、これをjsonに変換してすべての接続しているユーザーに”GameFinishedToClient”イベントとともに送信します。

定期的におこなわれる処理

1秒ごとにTimer_Elapsedメソッドが呼び出されます。このときはGame.Updateメソッドを呼び出して、そのあと前述のSendDataメソッドを呼び出してゲームの状態を全ユーザーに送信します。ただしGameオブジェクトが存在しない場合、すでにゲームが終了している場合はなにもしません。