ASP.NET Core版 対人対戦できるぷよぷよをつくる(1)の続きです。
Contents
PuyoMatchHubクラスの定義
AspNetCore.SignalR.Hubクラスを継承してPuyoMatchHubクラスを定義します。名前空間は省略して書きます。
1 2 3 4 5 6 7 8 9 10 |
using Microsoft.AspNetCore.SignalR; using System.Timers; using System.Text; namespace PuyoMatch { public class PuyoMatchHub : Hub { } } |
1 2 3 |
public class PuyoMatchHub : Hub { } |
接続時の処理
静的変数と定数部分を示します。
AspNetCore.SignalRのConnectionId(一意の接続ID)をキーにしてIClientProxyとプレイヤー情報が格納されたPlayerInfoオブジェクトを登録する静的変数を定義しています。また現在対戦されているゲームの処理をするためのPuyoMatchGameオブジェクトのリストも定義します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class PuyoMatchHub : Hub { static Dictionary<string, IClientProxy> ClientProxyMap = new Dictionary<string, IClientProxy>(); static Dictionary<string, PlayerInfo> WaitingPlayers = new Dictionary<string, PlayerInfo>(); static List<PuyoMatchGame> Games = new List<PuyoMatchGame>(); static System.Timers.Timer TimerForUpdate = new System.Timers.Timer(); static System.Timers.Timer TimerForGC = new System.Timers.Timer(); static bool IsFirst = true; // 文字を強調表示するためのHTMLタグ const string RedTag = "<span style=\"font-weight:bold; color:#ff0000;\">"; const string YellowTag = "<span style=\"font-weight:bold; color:#ffff00;\">"; const string SpanTagEnd = "</span>"; } |
接続されたときの処理を示します。
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 |
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); } } |
更新時の処理
Timer.Elapsedイベントが発生したらPuyoMatchGameクラスの各プロパティから文字列を取得してクライアントサイドに送信します。またゲームをしているユーザーであってもそうでなくても”SendToClient”を送信して通信が切れるのを防ぎます。
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 |
public class PuyoMatchHub : Hub { private void TimerForUpdate_Elapsed(object? sender, ElapsedEventArgs e) { foreach (PuyoMatchGame game in Games) Update(game); foreach (IClientProxy client in ClientProxyMap.Values) _ = client.SendAsync("SendToClient"); } void Update(PuyoMatchGame game) { if (!game.IsPlaying) return; IClientProxy?[] clients = GetClients(game); foreach (IClientProxy? client in clients) { if (client == null) continue; Task.Run(async () => { await client.SendAsync("BeginUpdatedToClient"); await client.SendAsync( "FixedPuyoUpdatedToClient", true, game.FixedPuyoRowsTexts[0], game.FixedPuyoColsTexts[0], game.FixedPuyoTypesTexts[0], game.FixedPuyoRensasTexts[0] ); await client.SendAsync( "FixedPuyoUpdatedToClient", false, game.FixedPuyoRowsTexts[1], game.FixedPuyoColsTexts[1], game.FixedPuyoTypesTexts[1], game.FixedPuyoRensasTexts[1] ); await client.SendAsync( "FallingPuyoUpdatedToClient", true, game.FallingPuyoRowsTexts[0], game.FallingPuyoColsTexts[0], game.FallingPuyoTypesTexts[0] ); await client.SendAsync( "FallingPuyoUpdatedToClient", false, game.FallingPuyoRowsTexts[1], game.FallingPuyoColsTexts[1], game.FallingPuyoTypesTexts[1] ); await client.SendAsync("ScoreUpdatedToClient", true, game.Scores[0], game.OjamaCounts[0], game.PlayerNames[0]); await client.SendAsync("ScoreUpdatedToClient", false, game.Scores[1], game.OjamaCounts[1], game.PlayerNames[1]); await client.SendAsync("PingUpdatedToClient", true, game.Pings[0]); await client.SendAsync("PingUpdatedToClient", false, game.Pings[1]); await client.SendAsync("EndUpdatedToClient"); }); } } // たぶん必要ないと思うけど数秒おきにガベージコレクションを実行しておく private void TimerForGC_Elapsed(object? sender, ElapsedEventArgs e) { try { System.GC.Collect(); } catch { Console.WriteLine("ガベージコレクション失敗"); } } } |
登録時の処理
ユーザーはページにアクセスして[エントリー]ボタンを押すと自分自身をプレイヤーとして登録できます。そのための処理を示します。
もしそのときにすでに登録している人がいたらその人との対戦がはじまります。誰もいない場合は辞書WaitingPlayersに登録され、他のプレイヤーがくるのを待ちます。
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 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
public class PuyoMatchHub : Hub { public async void Entery(string id, string playerName) { if (!ClientProxyMap.ContainsKey(id)) return; if (playerName == "") { await ClientProxyMap[id].SendAsync("NotifyToClient", "名前は必ず登録してください"); return; } if (WaitingPlayers.ContainsKey(id)) { await ClientProxyMap[id].SendAsync("NotifyToClient", "すでに登録されています"); return; } PlayerInfo player = new PlayerInfo(id, playerName); // 登録したユーザーに登録処理がおこなわれたことを通知する if (ClientProxyMap.ContainsKey(id)) await ClientProxyMap[id].SendAsync("EnteredToClient", $"{player.EscapedPlayerName}でエントリーしました。"); if (WaitingPlayers.Count > 0) { // 自分以外に登録している人がいたらその人と対戦する KeyValuePair<string, PlayerInfo> keyPair = WaitingPlayers.FirstOrDefault(_ => _.Value != player); if (keyPair.Value == null) return; string[] connectionIds = new string[2]; connectionIds[0] = id; connectionIds[1] = keyPair.Value.ConnectionId; WaitingPlayers.Remove(keyPair.Value.ConnectionId); // 接続されているユーザーに登録されている人数が変更されたことを通知する foreach (IClientProxy client in ClientProxyMap.Values) { await client.SendAsync("PlayerCountChangedToClient", WaitingPlayers.Count); } PlayerInfo[] playerInfos = new PlayerInfo[2]; playerInfos[0] = player; playerInfos[1] = keyPair.Value; // PuyoMatchGameオブジェクトを生成してイベントハンドラを追加する PuyoMatchGame game = new PuyoMatchGame(playerInfos); AddEventHandlers(game); Games.Add(game); // 現在の対戦数(これを含む)が変更されたことを通知する foreach (IClientProxy client in ClientProxyMap.Values) await client.SendAsync("GameCountChangedToClient", Games.Count); await Task.Delay(1000); // マッチングが成功したことを対戦者を自分自身と対戦者に通知する string[] esNames = new string[2]; esNames[0] = player.EscapedPlayerName; esNames[1] = keyPair.Value.EscapedPlayerName; string str1 = $"【マッチング成功】{YellowTag}{esNames[0]}{SpanTagEnd} さんの対戦相手は {RedTag}{esNames[1]}{SpanTagEnd} さんです。"; string str2 = $"【マッチング成功】{YellowTag}{esNames[1]}{SpanTagEnd}さんの対戦相手が見つかりました。{RedTag}{esNames[0]}{SpanTagEnd} さんです。"; if (ClientProxyMap.ContainsKey(connectionIds[0])) await ClientProxyMap[connectionIds[0]].SendAsync("MatchingSucceededToClient", str1); if (ClientProxyMap.ContainsKey(connectionIds[1])) await ClientProxyMap[connectionIds[1]].SendAsync("MatchingSucceededToClient", str2); // 3秒後にゲームスタート await Task.Delay(3000); game.GameStart(); // ゲームが開始されたことを自分自身と対戦者に通知する // それぞれの対戦者は自分自身が左側に表示されるようにするため、"EventGameStartToClient"だけでなく // 引数も渡すことにする if (ClientProxyMap.ContainsKey(connectionIds[0])) await ClientProxyMap[connectionIds[0]].SendAsync("EventGameStartToClient", true); if (ClientProxyMap.ContainsKey(connectionIds[1])) await ClientProxyMap[connectionIds[1]].SendAsync("EventGameStartToClient", false); // 両プレイヤーに自分自身の名前と対戦相手の名前を通知する。 // これは対戦が終了するまでページ上部に表示され続ける str1 = $"{YellowTag}{esNames[0]}{SpanTagEnd} さんは {RedTag}{esNames[1]}{SpanTagEnd} さんと対戦中です。"; if (ClientProxyMap.ContainsKey(connectionIds[0])) await ClientProxyMap[connectionIds[0]].SendAsync("NotifyToClient", str1); str2 = $"{YellowTag}{esNames[1]}{SpanTagEnd} さんは {RedTag}{esNames[0]}{SpanTagEnd} さんと対戦中です。"; if (ClientProxyMap.ContainsKey(connectionIds[1])) await ClientProxyMap[connectionIds[1]].SendAsync("NotifyToClient", str2); // マッチング成功からゲーム開始までに試合放棄があるかもしれないのでチェックする CheckGameAbandoned(connectionIds, esNames); } else { // 登録したけど相手がない場合 WaitingPlayers.Add(id, player); string str = $"{RedTag}{player.EscapedPlayerName}でエントリーしました。対戦相手が見つかるまでしばらくお待ちください{SpanTagEnd}"; if (ClientProxyMap.ContainsKey(id)) await ClientProxyMap[id].SendAsync("EnteredToClient", str); // 接続されているユーザーに登録されている人数が変更されたことを通知する foreach (IClientProxy client in ClientProxyMap.Values) await client.SendAsync("PlayerCountChangedToClient", WaitingPlayers.Count); // ただし自分自身には自分以外の登録人数を通知する if (ClientProxyMap.ContainsKey(id)) await ClientProxyMap[id].SendAsync("PlayerCountChangedToClient", WaitingPlayers.Count(_ =>_.Value != player)); } } } |
CheckGameAbandonedメソッドはマッチング成立からゲーム開始までに試合放棄があるかもしれないので、これをチェックします。試合放棄があった場合は放棄しなかった側が勝者です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class PuyoMatchHub : Hub { void CheckGameAbandoned(string[] connectionIds, string[] esNames) { if (ClientProxyMap.ContainsKey(connectionIds[0]) && !ClientProxyMap.ContainsKey(connectionIds[1])) { _ = ClientProxyMap[connectionIds[0]].SendAsync("WonGameToClient"); string str = $"{YellowTag}{esNames[1]}{SpanTagEnd} が {RedTag}試合放棄{SpanTagEnd} をしました。あなたの勝ちです。"; _ = ClientProxyMap[connectionIds[0]].SendAsync("NotifyToClient", str); } if (!ClientProxyMap.ContainsKey(connectionIds[0]) && ClientProxyMap.ContainsKey(connectionIds[1])) { _ = ClientProxyMap[connectionIds[1]].SendAsync("WonGameToClient"); string str = $"{YellowTag}{esNames[0]}{SpanTagEnd} が {RedTag}試合放棄{SpanTagEnd} をしました。あなたの勝ちです。"; _ = ClientProxyMap[connectionIds[1]].SendAsync("NotifyToClient", str); } } } |
イベントハンドラの追加とその定義
PuyoMatchGameオブジェクトの初期化でイベントハンドラを追加する処理を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class PuyoMatchHub : Hub { void AddEventHandlers(PuyoMatchGame game) { game.Rotated += Game_Rotated; game.Fixed += Game_Fixed; ; game.Rensa += Game_Rensa; game.OjamaFalling += Game_OjamaFalling; game.Offseted += Game_Offseted; game.SendNext += Game_SendNext; game.LostGame += Game_LostGame; } } |
PuyoMatchGameクラスでイベント発生時はクライアントサイドにデータを送信しますが、そのときにどこへ送信するかが問題になります。対戦している両者のIClientProxyを取得する処理を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class PuyoMatchHub : Hub { IClientProxy?[] GetClients(PuyoMatchGame game) { IClientProxy?[] clients = new IClientProxy[2]; if (game.ConnectionIds[0] != null && ClientProxyMap.ContainsKey(game.ConnectionIds[0])) clients[0] = ClientProxyMap[game.ConnectionIds[0]]; else clients[0] = null; if (game.ConnectionIds[1] != null && ClientProxyMap.ContainsKey(game.ConnectionIds[1])) clients[1] = ClientProxyMap[game.ConnectionIds[1]]; else clients[1] = null; return clients; } } |
イベントハンドラの定義部分を示します。
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 |
public class PuyoMatchHub : Hub { private async void Game_Rotated(PuyoMatchGame game, bool isFirstPlayer) { IClientProxy?[] clients = GetClients(game); IClientProxy? client0 = clients[0]; IClientProxy? client1 = clients[1]; if (client0 != null) await client0.SendAsync("RotatedToClient", isFirstPlayer); if (client1 != null) await client1.SendAsync("RotatedToClient", isFirstPlayer); } private async void Game_Fixed(PuyoMatchGame game, bool isFirstPlayer) { IClientProxy?[] clients = GetClients(game); IClientProxy? client0 = clients[0]; IClientProxy? client1 = clients[1]; if (client0 != null) await client0.SendAsync("FixedToClient", isFirstPlayer); if (client1 != null) await client1.SendAsync("FixedToClient", isFirstPlayer); } private async void Game_Rensa(PuyoMatchGame game, bool isFirstPlayer, int rensa) { IClientProxy?[] clients = GetClients(game); IClientProxy? client0 = clients[0]; IClientProxy? client1 = clients[1]; if (client0 != null) await client0.SendAsync("RensaToClient", isFirstPlayer, rensa); if (client1 != null) await client1.SendAsync("RensaToClient", isFirstPlayer, rensa); } private async void Game_OjamaFalling(PuyoMatchGame game, bool isFirstPlayer, int ojamaCount) { IClientProxy?[] clients = GetClients(game); IClientProxy? client0 = clients[0]; IClientProxy? client1 = clients[1]; if (client0 != null) await client0.SendAsync("OjamaFallingToClient", isFirstPlayer, ojamaCount); if (client1 != null) await client1.SendAsync("OjamaFallingToClient", isFirstPlayer, ojamaCount); } private async void Game_Offseted(PuyoMatchGame game, bool isFirstPlayer, int offsetCount) { IClientProxy?[] clients = GetClients(game); IClientProxy? client0 = clients[0]; IClientProxy? client1 = clients[1]; if (client0 != null) await client0.SendAsync("OffsetToClient", isFirstPlayer, offsetCount); if (client1 != null) await client1.SendAsync("OffsetToClient", isFirstPlayer, offsetCount); } private async void Game_SendNext(PuyoMatchGame game, bool isFirstPlayer, string[] nextPuyoTextsMainSub) { IClientProxy?[] clients = GetClients(game); IClientProxy? client0 = clients[0]; IClientProxy? client1 = clients[1]; if (client0 != null) await client0.SendAsync("SendNextToClient", isFirstPlayer, nextPuyoTextsMainSub[0], nextPuyoTextsMainSub[1]); if (client1 != null) await client1.SendAsync("SendNextToClient", isFirstPlayer, nextPuyoTextsMainSub[0], nextPuyoTextsMainSub[1]); } } |
ゲームオーバー時のイベントハンドラを示します。
ここではゲーム開始時に追加されたイベントハンドラ(+PuyoMatchGameクラス内で追加されたイベントハンドラ)をRemoveしてGamesから該当するインスタンスを取り除きます。
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 |
public class PuyoMatchHub : Hub { private async void Game_LostGame(PuyoMatchGame game, bool isPlayer) { string winnerId; string loserId; string escapedWinnerName; string escapedLoserName; game.Uninitialize(); RemoveEventHandlers(game); try { if (!Games.Remove(game)) return; foreach (IClientProxy client in ClientProxyMap.Values) await client.SendAsync("GameCountChangedToClient", Games.Count); } catch { Console.WriteLine("例外発生"); return; } // 勝ったユーザーには誰に勝ったか、負けたユーザーには誰に負けたかを表示するための文字列を送信する 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]; } if (ClientProxyMap.ContainsKey(winnerId)) { await ClientProxyMap[winnerId].SendAsync("WonGameToClient"); string str1 = $"あなたは {YellowTag}{escapedLoserName}{SpanTagEnd} さんに{RedTag}勝ち{SpanTagEnd}ました。"; await ClientProxyMap[winnerId].SendAsync("NotifyToClient", str1); } if (ClientProxyMap.ContainsKey(loserId)) { await ClientProxyMap[loserId].SendAsync("LostGameToClient"); string str2 = $"あなたは {YellowTag}{escapedWinnerName}{SpanTagEnd} さんに{RedTag}負け{SpanTagEnd}ました。"; await ClientProxyMap[loserId].SendAsync("NotifyToClient", str2); } } } |
ゲーム開始時に追加されたイベントハンドラをRemoveする処理を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class PuyoMatchHub : Hub { void RemoveEventHandlers(PuyoMatchGame game) { game.Rotated -= Game_Rotated; game.Fixed -= Game_Fixed; ; game.Rensa -= Game_Rensa; game.OjamaFalling -= Game_OjamaFalling; game.Offseted -= Game_Offseted; game.SendNext -= Game_SendNext; game.LostGame -= Game_LostGame; } } |
キー操作に対応する処理
キー操作されたときには該当する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 62 63 64 65 66 67 68 69 70 71 |
public class PuyoMatchHub : Hub { public void SendKeyDown(string key) { // 長大なデータが送りつけられるかもしれないので対策 if (key.Length > 64) return; bool isPrimary = false; PuyoMatchGame? game = Games.FirstOrDefault(_ => _.ConnectionIds[0] == Context.ConnectionId); if (game != null) isPrimary = true; if (game == null) game = Games.FirstOrDefault(_ => _.ConnectionIds[1] == Context.ConnectionId); if (game == null) return; if (key == "ArrowLeft") game.OnKeydownLeftKey(isPrimary); if (key == "ArrowRight") game.OnKeydownRightKey(isPrimary); if (key == "KeyX") game.RotateRight(isPrimary); if (key == "KeyZ") game.RotateLeft(isPrimary); if (key == "ArrowDown") game.OnKeydownDownKey(isPrimary); } public void SendKeyUp(string key) { // 長大なデータが送りつけられるかもしれないので対策 if (key != null && key.Length > 64) return; PuyoMatchGame? game = null; bool isPrimary = false; game = Games.FirstOrDefault(_ => _.ConnectionIds[0] == Context.ConnectionId); if (game != null) isPrimary = true; if (game == null) game = Games.FirstOrDefault(_ => _.ConnectionIds[1] == Context.ConnectionId); if (game == null) return; if (key == "ArrowLeft") { if (isPrimary) game.IsLeftKeyDowns[0] = false; else game.IsLeftKeyDowns[1] = false; } if (key != null && key == "ArrowRight") { if (isPrimary) game.IsRightKeyDowns[0] = false; else game.IsRightKeyDowns[1] = false; } if (key == "ArrowDown") { if (isPrimary) game.IsDownKeyDowns[0] = false; else game.IsDownKeyDowns[1] = false; } } } |
Ping値を表示させるための処理
動作が重いときがあるのでPing値を表示させてはどうかというリクエストがあったので、クライアント側から飛ばしたPingを受け取るメソッドを定義します(中身は空っぽでOK)。
1 2 3 4 5 6 |
public class PuyoMatchHub : Hub { public void Ping(string connectionId) { } } |
自分自身のPing値だけでなく対戦している相手のPing値も表示させたいので、クライアントサイドでPing値を計測できたら、再度これをサーバーサイドに送り返させて両者に送信します。PingResultToServerメソッドは受け取ったPing値をgame.Pings[n]にセットします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class PuyoMatchHub : Hub { public void PingResultToServer(string connectionId, int pingResult) { PuyoMatchGame? game = Games.FirstOrDefault(_ => _.ConnectionIds[0] == connectionId); if (game != null) game.Pings[0] = pingResult; else { game = Games.FirstOrDefault(_ => _.ConnectionIds[1] == connectionId); if (game != null) game.Pings[1] = pingResult; } } } |
通信が切断時の処理
通信が切断されたときの処理を示します。
ClientProxyMapからキーを削除します。また対戦中なのに通信を切断したり、プレイヤーとして登録しているユーザーが通信を切断する場合があるかもしれません。その場合はGamesリストまたはWaitingPlayers辞書から該当するものを取り除きます。対戦中なのに通信を切断するのは試合放棄になります。この場合は相手にそれを通知するための処理をおこないます。
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 |
public class PuyoMatchHub : Hub { public override async Task OnDisconnectedAsync(Exception? exception) { await base.OnDisconnectedAsync(exception); if (ClientProxyMap.ContainsKey(Context.ConnectionId)) ClientProxyMap.Remove(Context.ConnectionId); // 対戦中なのに通信が切断されたかもしれない PuyoMatchGame? game = Games.FirstOrDefault(_ => _.ConnectionIds[0] == Context.ConnectionId); if (game == null) game = Games.FirstOrDefault(_ => _.ConnectionIds[1] == Context.ConnectionId); if (game != null) { game.Uninitialize(); RemoveEventHandlers(game); try { Games.Remove(game); foreach (IClientProxy client in ClientProxyMap.Values) await client.SendAsync("GameCountChangedToClient", Games.Count); } catch { Console.WriteLine("例外発生"); } string winnerId = ""; string escapedloserName = ""; string str = ""; if (game.ConnectionIds[0] == Context.ConnectionId) { winnerId = game.ConnectionIds[1]; escapedloserName = game.EscapedPlayerNames[0]; } if (game.ConnectionIds[1] == Context.ConnectionId) { winnerId = game.ConnectionIds[0]; escapedloserName = game.EscapedPlayerNames[1]; } if (ClientProxyMap.ContainsKey(winnerId)) { await ClientProxyMap[winnerId].SendAsync("WonGameToClient"); str = $"{YellowTag}{escapedloserName}{SpanTagEnd} が {RedTag}試合放棄{SpanTagEnd} をしました。あなたの勝ちです。"; await ClientProxyMap[winnerId].SendAsync("NotifyToClient", str); } } // プレイヤーとして登録しているのに通信が切断されたかもしれない if (WaitingPlayers.ContainsKey(Context.ConnectionId)) { WaitingPlayers.Remove(Context.ConnectionId); foreach (IClientProxy client in ClientProxyMap.Values) await client.SendAsync("PlayerCountChangedToClient", WaitingPlayers.Count); } // 接続ユーザー数が0になったら更新処理用のタイマーを停止させる if (ClientProxyMap.Count == 0) TimerForUpdate.Stop(); } } |