ASP.NET Core版 対人対戦できるぷよぷよをつくる(4)の続きです。他の人のプレイを観戦できる機能を追加します。
これまでは対戦しているユーザーだけにゲームに関するデータを送信していました。これを観戦したいユーザーにも送信すればよいということになります。では、そのように作りかえましょう。
まず観戦希望者用のページを作成します。
Contents
cshtmlファイル
新しくcshtmlファイルをPagesフォルダの配下に作成し、以下のように記述します。
Pages\PuyoMatch\games.cshtml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
@page @{ Layout = ""; string baseurl = "https://lets-csharp.com/samples/2204/aspnetcore-app-zero"; // baseurlはアプリとして公開したいurl。ドメイントップで公開するのであれば空文字列でよい } <!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>鳩でもわかるオンライン対戦型「ぴよぴよ」Ver 1.0 - 鳩でもわかるASP.NET Core</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/6.0.1/signalr.js"></script> <style> body { background-color: #000; color: #fff; font-family: "MS ゴシック"; } #container { width: 700px; } .display-none { display: none; } </style> </head> <body> <div id="container"> <div class="display-none"> @for (int i = 0; i < 5; i++) { @for (int k = 0; k < 20; k++) { if (k < 10) { <img src="@(baseurl)/puyo-match/puyo-images/@(i+1)-0@(k).png" alt="" id="type@(i+1)-0@(k)" /> } else { <img src="@(baseurl)/puyo-match/puyo-images/@(i+1)-@(k).png" alt="" id="type@(i+1)-@(k)" /> } } } <img src="@baseurl/puyo-match/puyo-images/wall.png" alt="" id="wall" /> </div> <!-- /.display-none --> <div style="margin-left:60px; color:#ff0000" id="errer"> <p>エラー:通信が切れました。</p> </div> <div style="margin-left:60px"> <p id="notify"></p> </div> <canvas id="can"></canvas> <br> <div style="margin-left:60px;"> <input type="checkbox" id="sound-checkbox"><label for="sound-checkbox">音を出す</label> <p id="notify1"><span id="game-count"></span> の対戦がおこなわれています。</p> <div id="games"> </div> <div id="stop-watching"> <input type="button" value="観戦をやめる" onclick="StopWatching()"><br> </div> <p id="conect-result"></p> </div> </div><!-- / #container --> <script> let connection = new signalR.HubConnectionBuilder().withUrl("@baseurl/PuyoMatchHub").build(); let base_url = '@baseurl'; </script> <script src="@baseurl/puyo-match/puyo-match.js"></script> </body> </html> |
名前空間は省略します。
1 2 3 4 5 6 |
namespace PuyoMatch { public class PuyoMatchHub : Hub { } } |
1 2 3 |
public class PuyoMatchHub : Hub { } |
対戦リストを表示させる
対戦リストを表示させるためにはクライアントサイドに現在おこなわれているすべての対戦のリストを送信しなければなりません。
そのためにこれまでに使っていたOnConnectedAsyncメソッドに1行追加します。最後に自作メソッド SendGamesToClientを呼び出しているだけです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
public class PuyoMatchHub : Hub { public override async Task OnConnectedAsync() { await base.OnConnectedAsync(); ClientProxyMap.Add(Context.ConnectionId, Clients.Caller); if (IsFirst) { IsFirst = false; TimerForUpdate.Interval = 1000 / 30; TimerForUpdate.Elapsed += TimerForUpdate_Elapsed; TimerForGC.Interval = 4000; TimerForGC.Elapsed += TimerForGC_Elapsed; TimerForGC.Start(); } if (ClientProxyMap.Count == 1) TimerForUpdate.Start(); await Clients.Caller.SendAsync("SuccessfulConnectionToClient", "接続成功", Context.ConnectionId, Field.ROW_MAX, Field.COL_MAX); await Clients.Caller.SendAsync("EndUpdatedToClient"); await Clients.Caller.SendAsync("PlayerCountChangedToClient", WaitingPlayers.Count); await Clients.Caller.SendAsync("GameCountChangedToClient", Games.Count); SendGamesToClient(); // これを追加した。それ以外は既存のものと同じ } } |
SendGamesToClientメソッドは観戦用のページに現在おこなわれている対戦のプレイヤー名とこれを観戦するためのボタンを表示するための文字列を送信するためのものです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class PuyoMatchHub : Hub { public async void SendGamesToClient() { List<string> vs = new List<string>(); foreach (PuyoMatchGame game in Games) { string str = $"{game.EscapedPlayerNames[0]} VS {game.EscapedPlayerNames[1]}"; string param = $"{game.ConnectionIds[0]} {game.ConnectionIds[1]}"; str += $" <input type=\"button\" onclick=\"WatchGame('{param}')\" value=\"観戦する\">"; vs.Add(str); } await Clients.Caller.SendAsync("SendGamesToClient", String.Join("<br>", vs.ToArray())); } } |
クライアントサイドの処理
次にクライアントサイドの処理ですが、観戦用のページにアクセスしたら現在対戦中のリストをページ内に表示させます。対戦がひとつも行なわれていない場合はそのように表示させます。また観戦をしていないのに[観戦をやめる]ボタンが表示されているのはおかしいので、この最初はボタンを非表示にしておきます。
wwwroot\puyo-match\puyo-match.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
connection.on("SendGamesToClient", function (str) { isPlaying = false; let games = document.getElementById('games'); if (games != null) { if (str != '') games.innerHTML = str; else games.innerHTML = '対戦はありません。'; } let stopWatching = document.getElementById('stop-watching'); if (stopWatching != null) stopWatching.style.display = 'none'; }); |
実際に他人の対戦を観戦する
[観戦する]ボタンがクリックされると引数として両対戦者のIDを引数にWatchGame関数が呼び出されます。これを頼りにサーバーサイドでは該当するゲームオブジェクトを探します。
wwwroot\puyo-match\puyo-match.js
1 2 3 |
function WatchGame(param) { connection.invoke("GetGameToServer", param); } |
観戦者リストに追加する処理
サーバーサイドでは”GetGameToServer”を受信すると以下の処理が行なわれます。
引数で渡されたふたつのIDがPuyoMatchGame.ConnectionIdsプロパティに格納されているオブジェクトを探します。見つかった場合はPuyoMatchGame.AddWatcherメソッドでユーザーを観戦者リストに追加します。そして観戦者として正常に登録されたことを伝えるために”SuccessfulWatchGameToClient”を送信します。
そのあと各データをクライアントサイドに送信します。SendMatchStringメソッドで対戦情報(両プレイヤー名、現在何戦目かなど)を送信します。また次回、次々回に落下するぷよの情報も送信します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class PuyoMatchHub : Hub { public void GetGameToServer(string param) { string[] ids = param.Split(' '); PuyoMatchGame? game = Games.FirstOrDefault(_ => _.ConnectionIds[0] == ids[0] && _.ConnectionIds[1] == ids[1]); if(game == null) game = Games.FirstOrDefault(_ => _.ConnectionIds[0] == ids[1] && _.ConnectionIds[1] == ids[0]); if (game == null) return; game.AddWatcher(Context.ConnectionId); await Clients.Caller.SendAsync("SuccessfulWatchGameToClient"); // "SendNextToClient"の送信は // クライアントサイドで"SuccessfulWatchGameToClient"の受信が終わってからでないと // 不具合が発生するので await している int gameCount = game.WinCounts[0] + game.WinCounts[1] + 1; _ = SendMatchString(Context.ConnectionId, game.EscapedPlayerNames[0], game.EscapedPlayerNames[1], gameCount); _ = ClientProxyMap[Context.ConnectionId].SendAsync("SendNextToClient", true, game.NextPuyoTexts[0, 0], game.NextPuyoTexts[0, 1]); _ = ClientProxyMap[Context.ConnectionId].SendAsync("SendNextToClient", false, game.NextPuyoTexts[1, 0], game.NextPuyoTexts[1, 1]); } } |
これは観戦者として登録されているユーザーの接続IDのリストを返すメソッドです。また観戦者として追加・削除するためのメソッドもあわせて示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
namespace PuyoMatch { public class PuyoMatchGame { List<string> _watchers = new List<string>(); public List<string> GetWatchersId() { return _watchers; } public void AddWatcher(string id) { _watchers.Add(id); } public bool RemoveWatcher(string id) { return _watchers.Remove(id); } } } |
ネクストぷよを取得できるようにPuyoMatchGameクラスを修正する
これまでは次回、次々回に落下してくるぷよが決まったら対戦者だけに送信すればよかったのですが、観戦者はいつあらわれるかわからないので、いつでも次回、次々回に落下してくるぷよの情報を取得できるようにします。そのためにNextPuyoTextsプロパティを定義するとともに、既存のメソッドを修正します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
namespace PuyoMatch { public class PuyoMatchGame { string[,] _nextPuyoTexts = new string[2, 2]; public string[,] NextPuyoTexts { get { return _nextPuyoTexts; } } private void FieldPlayer0_SendNext(FallingPuyo[] nextMainSub1, FallingPuyo[] nextMainSub2) { IgnoreTimerForPuyoDowns[0] = true; string nextMainSubText1 = CreateTextFromNextPuyo(nextMainSub1[0], nextMainSub1[1]); string nextMainSubText2 = CreateTextFromNextPuyo(nextMainSub2[0], nextMainSub2[1]); string[] nextPuyoTexts = new string[2] { nextMainSubText1, nextMainSubText2 }; SendNext?.Invoke(this, true, nextPuyoTexts); // nextPuyoTextsをいつでも取得できるようにプロパティにセットする NextPuyoTexts[0, 0] = nextPuyoTexts[0]; NextPuyoTexts[0, 1] = nextPuyoTexts[1]; } private void FieldPlayer1_SendNext(FallingPuyo[] nextMainSub1, FallingPuyo[] nextMainSub2) { IgnoreTimerForPuyoDowns[1] = true; string nextMainSubText1 = CreateTextFromNextPuyo(nextMainSub1[0], nextMainSub1[1]); string nextMainSubText2 = CreateTextFromNextPuyo(nextMainSub2[0], nextMainSub2[1]); string[] nextPuyoTexts = new string[2] { nextMainSubText1, nextMainSubText2 }; SendNext?.Invoke(this, false, nextPuyoTexts); NextPuyoTexts[1, 0] = nextPuyoTexts[0]; NextPuyoTexts[1, 1] = nextPuyoTexts[1]; } } } |
クライアントサイドにおける処理
クライアントサイドで”SuccessfulWatchGameToClient”を受信したときは観戦者登録の処理が正常におこなわれたということなのでフィールドの描画処理ができるようにcanvasを表示させます。
また対戦リストは表示させておいてもじゃまなだけなので非表示にします。かわりに[観戦をやめる]ボタンを表示させます。
1 2 3 4 5 6 7 8 9 10 11 12 |
connection.on("SuccessfulWatchGameToClient", function () { can.style.display = 'block'; isThisFirstPlayer = true; isPlaying = true; let games = document.getElementById('games'); games.style.display = 'none'; let stopWatching = document.getElementById('stop-watching'); if (stopWatching != null) stopWatching.style.display = 'block'; }); |
データ更新時の処理
PuyoMatchHub.GetClientsメソッドは描画処理のためにデータを送信するユーザーを取得するためのものですが、観戦者にもデータを送信しなければならないので修正します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public class PuyoMatchHub : Hub { IClientProxy?[] GetClients(PuyoMatchGame game) { List<IClientProxy?> clients = new List<IClientProxy?>(); if (game.ConnectionIds[0] != null && ClientProxyMap.ContainsKey(game.ConnectionIds[0])) clients.Add(ClientProxyMap[game.ConnectionIds[0]]); else clients.Add(null); if (game.ConnectionIds[1] != null && ClientProxyMap.ContainsKey(game.ConnectionIds[1])) clients.Add(ClientProxyMap[game.ConnectionIds[1]]); else clients.Add(null); List<string> ids = game.GetWatchersId(); foreach (string id in ids) { if(ClientProxyMap.ContainsKey(id)) clients.Add(ClientProxyMap[id]); } return clients.ToArray(); } } |
回転や着地、連鎖時のイベントが発生したときも、これを対戦者だけでなく観戦者にも通知できるように修正を加えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
public class PuyoMatchHub : Hub { private void Game_Rotated(PuyoMatchGame game, bool isFirstPlayer) { IClientProxy?[] clients = GetClients(game); foreach (IClientProxy? client in clients) { if (client != null) _ = client.SendAsync("RotatedToClient", isFirstPlayer); } } private void Game_Fixed(PuyoMatchGame game, bool isFirstPlayer) { IClientProxy?[] clients = GetClients(game); foreach (IClientProxy? client in clients) { if (client != null) _ = client.SendAsync("FixedToClient", isFirstPlayer); } } private void Game_Rensa(PuyoMatchGame game, bool isFirstPlayer, int rensa) { IClientProxy?[] clients = GetClients(game); foreach (IClientProxy? client in clients) { if (client != null) _ = client.SendAsync("RensaToClient", isFirstPlayer, rensa); } } private void Game_OjamaFalling(PuyoMatchGame game, bool isFirstPlayer, int ojamaCount) { IClientProxy?[] clients = GetClients(game); foreach (IClientProxy? client in clients) { if (client != null) _ = client.SendAsync("OjamaFallingToClient", isFirstPlayer, ojamaCount); } } private void Game_Offseted(PuyoMatchGame game, bool isFirstPlayer, int offsetCount) { IClientProxy?[] clients = GetClients(game); foreach (IClientProxy? client in clients) { if (client != null) _ = client.SendAsync("OffsetToClient", isFirstPlayer, offsetCount); } } private void Game_SendNext(PuyoMatchGame game, bool isFirstPlayer, string[] nextPuyoTextsMainSub) { IClientProxy?[] clients = GetClients(game); foreach (IClientProxy? client in clients) { if (client != null) _ = client.SendAsync("SendNextToClient", isFirstPlayer, nextPuyoTextsMainSub[0], nextPuyoTextsMainSub[1]); } } } |
敗戦時の処理
一方が負けたときの処理ですが、長くなるので処理を分けました。
まず勝った側のPuyoMatchGame.WinCounts[n]をインクリメントします。そして後述するSendGameResultメソッドでどちらがどちらに勝ったのか負けたのかをクライアントサイドで表示するための文字列を送信します。
次にCheckGameSetメソッドでゲームセット(片方が3勝している)なのかを調べて、その場合はGamesリストからPuyoMatchGameオブジェクトを除去する処理をおこないます。ゲームセットになっていない場合は3秒間小休止したあと次の対戦がはじまります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
public class PuyoMatchHub : Hub { private async void Game_LostGame(PuyoMatchGame game, bool isPlayer) { if (isPlayer) game.WinCounts[1]++; else game.WinCounts[0]++; // 対戦結果を表示するための文字列を送信する await SendGameResult(game, isPlayer); // どちらかが3勝していないかチェック bool ret = await CheckGameSet(game, isPlayer); if (ret) return; await Task.Delay(3000); // 次の対戦を開始 game.NextGameStart(true); // 誰と誰が対戦しているのかを表示する await SendNextGameStarted(game, isPlayer); // 小休止しているあいだに試合放棄があるかもしれない CheckGameAbandoned(game.ConnectionIds, game.EscapedPlayerNames); } } |
次にゲームセット(片方が3勝している)なのかを調べて、その場合はGamesリストからPuyoMatchGameオブジェクトを除去する処理をおこないます。ゲームセットになっていない場合は3秒間小休止したあと次の対戦がはじまります。
これは勝ち負けそれぞれのプレイヤー名と接続IDを取得するためのメソッドです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public class PuyoMatchHub : Hub { (string winnerId, string loserId, string escapedWinnerName, string escapedLoserName) GetIdsNames(PuyoMatchGame game, bool isPlayer) { string winnerId, loserId, escapedWinnerName, escapedLoserName; if (isPlayer) { winnerId = game.ConnectionIds[1]; loserId = game.ConnectionIds[0]; escapedWinnerName = game.EscapedPlayerNames[1]; escapedLoserName = game.EscapedPlayerNames[0]; } else { winnerId = game.ConnectionIds[0]; loserId = game.ConnectionIds[1]; escapedWinnerName = game.EscapedPlayerNames[0]; escapedLoserName = game.EscapedPlayerNames[1]; } return (winnerId, loserId, escapedWinnerName, escapedLoserName); } } |
これはクライアントサイドに、誰が誰に勝ったのか負けたのかを表示するための文字列を送信するためのメソッドです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
public class PuyoMatchHub : Hub { async Task SendGameResult(string id, string a, string b, string c) { string str = $" {YellowTag}{a}{SpanTagEnd} は {YellowTag}{b}{SpanTagEnd} さんに{RedTag}{c}{SpanTagEnd}ました。"; if (ClientProxyMap.ContainsKey(id)) await ClientProxyMap[id].SendAsync("NotifyToClient", str); } async Task SendGameResult(PuyoMatchGame game, bool isPlayer) { var idsNames = GetIdsNames(game, isPlayer); string winnerId = idsNames.winnerId; string loserId = idsNames.loserId; string escapedWinnerName = idsNames.escapedWinnerName; string escapedLoserName = idsNames.escapedLoserName; await Task.Run(() => { if (ClientProxyMap.ContainsKey(winnerId)) { _ = SendGameResult(winnerId, "あなた", escapedLoserName, "勝ち"); _ = ClientProxyMap[winnerId].SendAsync("WonThisGameToClient"); Task.Run(async ()=> { await ClientProxyMap[winnerId].SendAsync("WinCountsToClient", true, game.WinCounts[0]); await ClientProxyMap[winnerId].SendAsync("WinCountsToClient", false, game.WinCounts[1]); _ = ClientProxyMap[winnerId].SendAsync("EndUpdatedToClient"); }); } if (ClientProxyMap.ContainsKey(loserId)) { _ = SendGameResult(loserId, "あなた", escapedWinnerName, "負け"); if (game.WinCounts[0] >= PuyoMatchGame.WIN_COUNT_MAX || game.WinCounts[1] >= PuyoMatchGame.WIN_COUNT_MAX) _ = ClientProxyMap[loserId].SendAsync("LostThisGameToClient"); else _ = ClientProxyMap[loserId].SendAsync("LostThisGameToClient2"); Task.Run(async ()=> { await ClientProxyMap[loserId].SendAsync("WinCountsToClient", true, game.WinCounts[0]); await ClientProxyMap[loserId].SendAsync("WinCountsToClient", false, game.WinCounts[1]); _ = ClientProxyMap[loserId].SendAsync("EndUpdatedToClient"); }); } List<string> ids = game.GetWatchersId(); foreach (string id in ids) { if (!ClientProxyMap.ContainsKey(id)) continue; if (isPlayer) _ = SendGameResult(id, escapedLoserName, escapedWinnerName, "負け"); else _ = SendGameResult(id, escapedWinnerName, escapedLoserName, "勝ち"); if (isPlayer) { if (game.WinCounts[0] >= PuyoMatchGame.WIN_COUNT_MAX || game.WinCounts[1] >= PuyoMatchGame.WIN_COUNT_MAX) _ = ClientProxyMap[id].SendAsync("LostThisGameToClient"); else _ = ClientProxyMap[id].SendAsync("LostThisGameToClient2"); } else _ = ClientProxyMap[id].SendAsync("WonThisGameToClient"); Task.Run(async ()=> { await ClientProxyMap[id].SendAsync("WinCountsToClient", true, game.WinCounts[0]); await ClientProxyMap[id].SendAsync("WinCountsToClient", false, game.WinCounts[1]); _ = ClientProxyMap[id].SendAsync("EndUpdatedToClient"); }); } }); } } |
これはプレイヤーの一方が3勝していたときに対戦者と観戦者に”FinishedGameToClient”を送信してGamesリストからPuyoMatchGameオブジェクトをRemoveするための処理です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
public class PuyoMatchHub : Hub { async Task<bool> CheckGameSet(PuyoMatchGame game, bool isPlayer) { var idsNames = GetIdsNames(game, isPlayer); string winnerId = idsNames.winnerId; string loserId = idsNames.loserId; if (game.WinCounts[0] >= PuyoMatchGame.WIN_COUNT_MAX || game.WinCounts[1] >= PuyoMatchGame.WIN_COUNT_MAX) { await Task.Run(() => { game.Uninitialize(); RemoveEventHandlers(game); _ = ClientProxyMap[winnerId].SendAsync("FinishedGameToClient"); _ = ClientProxyMap[loserId].SendAsync("FinishedGameToClient"); List<string> ids = game.GetWatchersId(); foreach (string id in ids) { if (!ClientProxyMap.ContainsKey(id)) continue; _ = ClientProxyMap[id].SendAsync("FinishedGameToClient"); } try { if (!Games.Remove(game)) return; foreach (IClientProxy client in ClientProxyMap.Values) _ = client.SendAsync("GameCountChangedToClient", Games.Count); } catch { Console.WriteLine("例外発生"); return; } }); return true; } else return false; } } |
これは次の対戦が開始されたときに対戦者情報をクライアントサイド(対戦者と観戦者)に送信するための処理です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
public class PuyoMatchHub : Hub { async Task SendNextGameStarted(PuyoMatchGame game, bool isPlayer) { var idsNames = GetIdsNames(game, isPlayer); string winnerId = idsNames.winnerId; string loserId = idsNames.loserId; string escapedWinnerName = idsNames.escapedWinnerName; string escapedLoserName = idsNames.escapedLoserName; await Task.Run(()=> { int gameCount = game.WinCounts[0] + game.WinCounts[1] + 1; if (ClientProxyMap.ContainsKey(winnerId)) { _ = SendMatchString(winnerId, escapedWinnerName, escapedLoserName, gameCount); _ = ClientProxyMap[winnerId].SendAsync("NextGameStartedToClient"); } if (ClientProxyMap.ContainsKey(loserId)) { _ = SendMatchString(loserId, escapedLoserName, escapedWinnerName, gameCount); _ = ClientProxyMap[loserId].SendAsync("NextGameStartedToClient"); } List<string> ids = game.GetWatchersId(); foreach (string id in ids) { if (!ClientProxyMap.ContainsKey(id)) continue; if (isPlayer) _ = SendMatchString(id, escapedLoserName, escapedWinnerName, gameCount); else _ = SendMatchString(id, escapedWinnerName, escapedLoserName, gameCount); _ = ClientProxyMap[id].SendAsync("NextGameStartedToClient"); } }); } } |
PuyoMatchGame.NextGameStartメソッドは次の対戦を開始するためのものです。引数がtrueだと前回のスコアをリセットします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
namespace PuyoMatch { public class PuyoMatchGame { public void NextGameStart(bool isScoreRest) { Task.Run(async () => { await Fields[0].Init(); await Fields[1].Init(); if (isScoreRest) { Scores[0] = 0; Scores[1] = 0; } OjamaCounts[0] = 0; OjamaCounts[1] = 0; IsPlaying = true; _timerForPuyoDown.Start(); }); } } } |
ゲームセット時の処理
ゲームセットになったらゲームをするためのページにおける処理に変更はありません。観戦用のページでは非表示にしていた対戦リストを再表示させます。また[観戦をやめる]ボタンは不要なので非表示にさせます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
connection.on("FinishedGameToClient", function () { isPlaying = false; isStoppingPlay = false; Update(); if (entery != null) { entery.style.display = 'block'; enteryButton.value = 'もう一度エントリーする'; } // 追加部分 let games = document.getElementById('games'); if (games != null) games.style.display = 'block'; let stopWatching = document.getElementById('stop-watching'); if (stopWatching != null) stopWatching.style.display = 'none'; }); |
観戦をやめるときの処理
[観戦をやめる]ボタンがクリックされるとcanvasは非表示にします。PuyoMatchGameオブジェクト内の観戦者リストから自身を削除する処理をおこなわせるために、サーバーサイドに”RemoveWatcher”を送信します。また対戦リストを表示させるための文字列が必要なので、これを要求するために”SendGamesToClient”を送信します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
function StopWatching() { can.style.display = 'none'; isPlaying = false; let games = document.getElementById('games'); if (games != null) games.style.display = 'block'; let stopWatching = document.getElementById('stop-watching'); if (stopWatching != null) stopWatching.style.display = 'none'; connection.invoke("RemoveWatcher"); connection.invoke("SendGamesToClient"); } |
サーバーサイドではRemoveWatcherが呼び出されたらPuyoMatchGame.RemoveWatcherメソッドを呼び出して観戦者リストからContext.ConnectionIdを削除します。
1 2 3 4 5 6 7 8 9 10 11 |
public class PuyoMatchHub : Hub { public void RemoveWatcher() { foreach (PuyoMatchGame game in Games) { if (game.RemoveWatcher(Context.ConnectionId)) break; } } } |
試合放棄時の処理
対戦を観戦しているとその対戦で試合放棄がある場合も考えられます。この場合はOnDisconnectedAsyncが呼び出されるので、このなかで処理をおこないます。CheckGameAbandonedメソッド内でこの処理をおこないます。それ以外の部分は既存のOnDisconnectedAsyncメソッドと同じです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public class PuyoMatchHub : Hub { public override async Task OnDisconnectedAsync(Exception? exception) { await base.OnDisconnectedAsync(exception); if (ClientProxyMap.ContainsKey(Context.ConnectionId)) ClientProxyMap.Remove(Context.ConnectionId); CheckGameAbandoned(Context.ConnectionId); if (WaitingPlayers.ContainsKey(Context.ConnectionId)) { WaitingPlayers.Remove(Context.ConnectionId); foreach (IClientProxy client in ClientProxyMap.Values) await client.SendAsync("PlayerCountChangedToClient", WaitingPlayers.Count); } if (ClientProxyMap.Count == 0) TimerForUpdate.Stop(); } } |
CheckGameAbandonedメソッドでは引数の接続IDをもつユーザーが対戦者の一方であるPuyoMatchGameオブジェクトを探して、残されたプレイヤーに試合放棄があったことを知らせます。それと同時にこれを観戦しているユーザーにも同様の通知をおこないます。そのあとGamesリストから該当するPuyoMatchGameオブジェクトを取り除きます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
public class PuyoMatchHub : Hub { void CheckGameAbandoned(string connectionId) { PuyoMatchGame? game = Games.FirstOrDefault(_ => _.ConnectionIds[0] == connectionId); if (game == null) game = Games.FirstOrDefault(_ => _.ConnectionIds[1] == connectionId); if (game == null) return; string str1 = ""; if (ClientProxyMap.ContainsKey(game.ConnectionIds[0]) && !ClientProxyMap.ContainsKey(game.ConnectionIds[1])) { string str2 = $"{YellowTag}{game.EscapedPlayerNames[1]}{SpanTagEnd} が {RedTag}試合放棄{SpanTagEnd} をしました。あなたの勝ちです。"; str1 = $"{YellowTag}{game.EscapedPlayerNames[1]}{SpanTagEnd} が {RedTag}試合放棄{SpanTagEnd} をしました。"; _ = ClientProxyMap[game.ConnectionIds[0]].SendAsync("NotifyToClient", str2); _ = ClientProxyMap[game.ConnectionIds[0]].SendAsync("WonThisGameToClient"); _ = ClientProxyMap[game.ConnectionIds[0]].SendAsync("FinishedGameToClient"); } if (!ClientProxyMap.ContainsKey(game.ConnectionIds[0]) && ClientProxyMap.ContainsKey(game.ConnectionIds[1])) { string str2 = $"{YellowTag}{game.EscapedPlayerNames[0]}{SpanTagEnd} が {RedTag}試合放棄{SpanTagEnd} をしました。あなたの勝ちです。"; str1 = $"{YellowTag}{game.EscapedPlayerNames[0]}{SpanTagEnd} が {RedTag}試合放棄{SpanTagEnd} をしました。"; _ = ClientProxyMap[game.ConnectionIds[1]].SendAsync("NotifyToClient", str2); _ = ClientProxyMap[game.ConnectionIds[1]].SendAsync("WonThisGameToClient"); _ = ClientProxyMap[game.ConnectionIds[1]].SendAsync("FinishedGameToClient"); } if (str1 != "") { foreach (string id in game.GetWatchersId()) { if (!ClientProxyMap.ContainsKey(id)) continue; _ = ClientProxyMap[id].SendAsync("NotifyToClient", str1); _ = ClientProxyMap[id].SendAsync("WonThisGameToClient"); _ = ClientProxyMap[id].SendAsync("FinishedGameToClient"); } } game.Uninitialize(); RemoveEventHandlers(game); try { // 非同期でforeachを回しているのでタイミングが悪いと例外が発生することがある Games.Remove(game); foreach (IClientProxy client in ClientProxyMap.Values) _ = client.SendAsync("GameCountChangedToClient", Games.Count); } catch { Console.WriteLine("例外発生"); } } } |