Ejemplo n.º 1
0
        private async Task <GameState> InitializeOrJoinGameVsRandomOpponent(Player player)
        {
            string randomGameId = serverState.PopRandomOpenGameId();

            if (randomGameId != null)
            {
                JoinGameResponse joinGameResponse = await JoinGameAsync(randomGameId, player).ConfigureAwait(false);

                if (joinGameResponse.State != JoinGameResponseType.SUCCESS)
                {
                    throw new Exception($"Error joining random game '{randomGameId}', expected success but got '{joinGameResponse.State}'");
                }
                return(joinGameResponse.GameState);
            }
            else
            {
                var gameState = new GameState
                {
                    Id           = Guid.NewGuid().ToString("N"),
                    Type         = GameType.VS_RANDOM_PLAYER,
                    StartTimeUtc = DateTime.UtcNow,
                    Stage        = GameStage.WAITING_FOR_OPPONENT,
                    Player1      = player,
                    Player2      = null,
                    BoardState   = null,                   /*Board will be initialized when second player joins the game*/
                };
                serverState.AddNewGame(gameState, player);
                return(gameState);
            }
        }
Ejemplo n.º 2
0
        private void GetGMMessage(Message message)
        {
            if (message.IsGameStarted())
            {
                _acceptingAgents = false;
            }
            if (message.IsEndGame())
            {
                HandleEndGame(message);
                //_gameMasterConnection.Dispose();
                return;
            }

            Console.WriteLine("I've got such message: " + message.GetPayload());
            Log.Information("GetGMMessege: {@m}", message);
            AgentDescriptor agent = _agentsConnections.Find(x => x.Id == message.AgentId);

            if (message.MessageId == MessageType.JoinGameResponse)
            {
                JoinGameResponse resp = (JoinGameResponse)message.GetPayload();
                if (resp.Accepted == false)
                {
                    var removedConnection = _agentsConnections.Find(a => a.Id == message.AgentId);
                    _agentsConnections.Remove(removedConnection);
                    SendMessageWithErrorHandling(agent, message);
                    removedConnection.Dispose();
                    return;
                }
            }
            SendMessageWithErrorHandling(agent, message);
        }
Ejemplo n.º 3
0
 private void HandleJoinGameResponse(JoinGameResponse response)
 {
     _logger.LogInformation("HandleJoinGameResponse Message={0}", response);
     if (OnJoinGameResponse != null)
     {
         OnJoinGameResponse(response);
     }
 }
Ejemplo n.º 4
0
        private static JoinGameResponse GetJoinGameResponse(OperationResponse op)
        {
            var res = new JoinGameResponse();

            res.Address = GetParameter <string>(op, Operations.ParameterCode.Address, true);
            res.NodeId  = GetParameter <byte>(op, Operations.ParameterCode.NodeId, true);
            return(res);
        }
Ejemplo n.º 5
0
        public async Task JoinGameAsync(JoinGameRequest request, Func <JoinGameResponse, Task> responseHandler)
        {
            _logger.LogInformation("JoinGame {}", request);

            var resp = new JoinGameResponse()
            {
                RequestId = request.RequestId
            };

            try
            {
                if (!await AcquireGameLock(request.GameId, request.RequestId, TimeSpan.FromSeconds(30)))
                {
                    resp.ErrorMessage = "Timed out while acquiring game lock";
                    await responseHandler(resp);

                    return;
                }

                var gameState = await _dbContext.GameStates
                                .FirstOrDefaultAsync(x => x.GameId == request.GameId);

                if (gameState == null)
                {
                    resp.ErrorMessage = "GameId not found";
                    await responseHandler(resp);

                    return;
                }

                if (gameState.IsFinished)
                {
                    resp.ErrorMessage = "Game is finished";
                    await responseHandler(resp);

                    return;
                }

                var gameEngine = new GameEngine(_loggerProvider, _dbContext, _cardRepo);
                gameEngine.GameState = gameState;

                var playerState = await gameEngine.JoinGameAsync(request);

                resp.IsSuccess = true;
                resp.Game      = gameEngine.GameState.Game;
                await responseHandler(resp);

                InvokeOnPlayerUpdate(MakePlayerUpdateResponse(playerState));
                InvokeOnGameUpdate(MakeGameUpdateResponse(gameEngine.GameState));
                InvokeOnTradeUpdate(playerState.PlayerId,
                                    MakeTradeUpdateResponse(gameEngine.GameState.GameId, gameEngine.GameState.Trades));
            }
            finally
            {
                ReleaseGameLock(request.GameId, request.RequestId);
            }
        }
Ejemplo n.º 6
0
        protected static JoinGameResponse GetJoinGameResponse(OperationResponse op)
        {
            var res = new JoinGameResponse
            {
                Address        = GetParameter <string>(op, ParameterCode.Address, true),
                GameProperties = GetParameter <Hashtable>(op, ParameterCode.GameProperties, true),
            };

            return(res);
        }
        protected static JoinGameResponse GetJoinGameResponse(OperationResponse op)
        {
            var res = new JoinGameResponse
            {
                Address          = GetParameter <string>(op, ParameterCode.Address, true),
                GameProperties   = GetParameter <Hashtable>(op, ParameterCode.GameProperties, true),
                ActorsProperties = GetParameter <Hashtable>(op, ParameterCode.PlayerProperties, true),
                RoomFlags        = GetParameter <int>(op, ParameterCode.RoomOptionFlags, true),
            };

            return(res);
        }
Ejemplo n.º 8
0
        public async Task <APIGatewayProxyResponse> Post(APIGatewayProxyRequest request, ILambdaContext context)
        {
            JoinGameRequest      input        = JsonSerializer.Deserialize <JoinGameRequest>(request.Body);
            AmazonGameLiftClient amazonClient = new AmazonGameLiftClient(Amazon.RegionEndpoint.USEast1);

            ListAliasesRequest aliasReq = new ListAliasesRequest();

            aliasReq.Name = "WarshopServer";
            Alias aliasRes = (await amazonClient.ListAliasesAsync(aliasReq)).Aliases[0];
            DescribeAliasRequest describeAliasReq = new DescribeAliasRequest();

            describeAliasReq.AliasId = aliasRes.AliasId;
            string fleetId = (await amazonClient.DescribeAliasAsync(describeAliasReq.AliasId)).Alias.RoutingStrategy.FleetId;

            DescribeGameSessionsResponse gameSession = await amazonClient.DescribeGameSessionsAsync(new DescribeGameSessionsRequest()
            {
                GameSessionId = input.gameSessionId
            });

            bool   IsPrivate = gameSession.GameSessions[0].GameProperties.Find((GameProperty gp) => gp.Key.Equals("IsPrivate")).Value.Equals("True");
            string Password  = IsPrivate ? gameSession.GameSessions[0].GameProperties.Find((GameProperty gp) => gp.Key.Equals("Password")).Value : "";

            if (!IsPrivate || input.password.Equals(Password))
            {
                CreatePlayerSessionRequest playerSessionRequest = new CreatePlayerSessionRequest();
                playerSessionRequest.PlayerId      = input.playerId;
                playerSessionRequest.GameSessionId = input.gameSessionId;
                CreatePlayerSessionResponse playerSessionResponse = amazonClient.CreatePlayerSessionAsync(playerSessionRequest).Result;
                JoinGameResponse            response = new JoinGameResponse
                {
                    playerSessionId = playerSessionResponse.PlayerSession.PlayerSessionId,
                    ipAddress       = playerSessionResponse.PlayerSession.IpAddress,
                    port            = playerSessionResponse.PlayerSession.Port
                };

                return(new APIGatewayProxyResponse
                {
                    StatusCode = (int)HttpStatusCode.OK,
                    Body = JsonSerializer.Serialize(response),
                    Headers = new Dictionary <string, string> {
                        { "Content-Type", "application/json" }
                    }
                });
            }
            else
            {
                return(new APIGatewayProxyResponse
                {
                    StatusCode = (int)HttpStatusCode.BadRequest,
                    Body = "Incorrect password for private game",
                });
            }
        }
Ejemplo n.º 9
0
        public void JoinGameResponse_Correct_ObjectCreated()
        {
            // Arrange
            var joinGameResponse = new JoinGameResponse
            {
                IsSuccessful = this.isSuccessful,
                Error        = this.error404
            };

            // Act
            // Assert
            Assert.Equal(this.isSuccessful, joinGameResponse.IsSuccessful);
            Assert.Equal(this.error404, joinGameResponse.Error);
        }
    public void handleJoinGameResponse(string msg)
    {
        //Debug.Log(msg);
        JoinGameResponse m = JsonUtility.FromJson <JoinGameResponse>(msg);

        if (m.join_successful == true)
        {
            player_color = m.player_color;
        }
        else
        {
            player_color = "";
        }
    }
        private void HandleJoinGameResponse(JoinGameResponse obj)
        {
            if (JoinGameResponse != null)
            {
                InvokeStateChanged(new GameAlertEventArgs()
                {
                    Title   = "Rejoined Game",
                    Message = string.Empty
                });
            }

            this.JoinGameResponse = obj;
            SetInitializedIfReady();
            InvokeStateChanged(EventArgs.Empty);
        }
Ejemplo n.º 12
0
        public async Task <ActionResult <JoinGameResponse> > Join([FromBody] JoinGameRequest addUserToGameRequest)
        {
            var gameId = await _GameLogic.JoinGameAsync(addUserToGameRequest.AccessCode, HttpContext.User.Identity.Name).ConfigureAwait(false);

            if (string.IsNullOrEmpty(gameId))
            {
                return(BadRequest());
            }

            var response = new JoinGameResponse()
            {
                GameId = gameId
            };

            return(Ok(response));
        }
Ejemplo n.º 13
0
        public override Response DoWork(string request)
        {
            var workRequest = JsonConvert.DeserializeObject<JoinGameRequest>(request);
            var workResponse = new JoinGameResponse();
            if (!Server.Games.Keys.ToArray().Contains(workRequest.GameID))
            {
                workResponse.Status = Statuses.GameNotFound;
                return workResponse;
            }

            if (Server.Games[workRequest.GameID].Act == Act.Canceld)
            {
                workResponse.Status = Statuses.GameCanceld;
                return workResponse;
            }

            var user = Server.Users.Values.FirstOrDefault(u => u.Name == workRequest.NewPlayer.Name);
            if (user == null)
            {
                workResponse.Status = Statuses.NoUser;
                return workResponse;
            }
            if (Server.Games[workRequest.GameID].PlayerBlack == null)
            {
                Server.Games[workRequest.GameID].Act = Act.InProgress;
                Server.Games[workRequest.GameID].PlayerBlack = user;
                Server.Messages.GetOrAdd(Server.Games[workRequest.GameID].PlayerWhite.Name, i => new List<Message>()).Add(MessageSender.OpponentJoinedGame());
                workResponse.Status = Statuses.Ok;
            }
            else if (Server.Games[workRequest.GameID].PlayerWhite == null)
            {
                Server.Games[workRequest.GameID].Act = Act.InProgress;
                Server.Games[workRequest.GameID].PlayerWhite = user;
                Server.Messages.GetOrAdd(Server.Games[workRequest.GameID].PlayerBlack.Name, i => new List<Message>()).Add(MessageSender.OpponentJoinedGame());
                workResponse.Status = Statuses.Ok;
            }
            else
            {
                workResponse.Status = Statuses.GameIsRunning;
            }

            return workResponse;
        }
Ejemplo n.º 14
0
        /// <summary>
        /// Put's the user in the queue with the server, saying they'd like to start a game. Stores
        /// the GameID that the server returns, so the player can actually join the game once it starts.
        /// </summary>
        /// <param name="TimeLimit">The user's desired time limit for the game</param>
        /// <returns></returns>
        private async Task JoinGame(int TimeLimit)
        {
            try
            {
                using (HttpClient client = CreateClient(DesiredServer))
                {
                    view.SetUpControlsWhileWaitingForGame();
                    tokenSource = new CancellationTokenSource();
                    // Create a dynamic object that will be serialized.
                    dynamic body = new ExpandoObject();
                    body.UserToken = UserToken;
                    body.TimeLimit = TimeLimit;

                    // Serialize the body and create the URI
                    StringContent content  = new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json");
                    string        usersURI = DesiredServer + "/BoggleService/games";

                    // Post to the server.
                    HttpResponseMessage response = await client.PostAsync(usersURI, content, tokenSource.Token);

                    // Tell the user if they're already in a game, so they can't start a new game!
                    if (!response.IsSuccessStatusCode)
                    {
                        view.ShowMessage("You're already in a game!");
                        return;
                    }
                    // Otherwise, parse the input from the server.
                    string responseBodyAsString = await response.Content.ReadAsStringAsync();

                    JoinGameResponse responseBody = JsonConvert.DeserializeObject <JoinGameResponse>(responseBodyAsString);
                    GameID       = responseBody.GameID;
                    ArePlayerOne = responseBody.IsPending;
                }
            }
            catch (Exception e)
            {
                if (e is TaskCanceledException)
                {
                    HandleCancelGame();
                }
            }
        }
Ejemplo n.º 15
0
        protected virtual void RealmSocket_OnJoinGameResponse(JoinGameResponse Packet)
        {
            JoinGameResult result = Packet.Result;

            if (result == JoinGameResult.Sucess)
            {
                this.Realm.WriteToLog("[GAME] Connecting To Game Server (" + Packet.GameServerIP + ")", Color.Yellow);
                LeaveChat bnetPacket = new LeaveChat();
                this.Chat.SendPacket(bnetPacket);
                this.D2GHash = Packet.GameHash;
                this.D2Token = (uint)Packet.GameToken;
                this.Game.Connect(Packet.GameServerIP, this.Proxy);
                this.Game.WaitForPacket(2);
            }
            else
            {
                this.Realm.WriteToLog("[GAME] Cannot Join Game, Reason: " + Packet.Result, Color.Red);
                this.FailToJoinGameEvent?.Invoke();
            }
        }
        public JoinGameResponse PostJoinGame([FromBody] JoinGameRequest join)
        {
            // Time limits can't be too long or too short.
            if (join.TimeLimit < 5 || join.TimeLimit > 120)
            {
                throw new HttpResponseException(HttpStatusCode.Forbidden);
            }

            JoinGameResponse response;

            using (SqlConnection conn = new SqlConnection(DBConnection))
            {
                conn.Open();
                using (SqlTransaction trans = conn.BeginTransaction())
                {
                    // Test to see if the user is actually registered.
                    using (SqlCommand cmd =
                               new SqlCommand("SELECT UserID FROM Users WHERE UserID=@UserID",
                                              conn,
                                              trans))
                    {
                        cmd.Parameters.AddWithValue("@UserID", join.UserToken);
                        using (SqlDataReader reader = cmd.ExecuteReader())
                        {
                            reader.Read();
                            if (!reader.HasRows)
                            {
                                throw new HttpResponseException(HttpStatusCode.Forbidden);
                            }
                        }
                    }

                    // Test to see if the player is already in a pending game.
                    using (SqlCommand cmd =
                               new SqlCommand("SELECT GameID FROM Games WHERE Player1=@UserID AND Player2 IS NULL",
                                              conn,
                                              trans))
                    {
                        cmd.Parameters.AddWithValue("@UserID", join.UserToken);
                        using (SqlDataReader reader = cmd.ExecuteReader())
                        {
                            // If the reader has rows, that means that the player is already in a pending game.
                            reader.Read();
                            if (reader.HasRows)
                            {
                                throw new HttpResponseException(HttpStatusCode.Conflict);
                            }
                        }
                    }

                    // Check for any already pending games.
                    using (SqlCommand cmd =
                               new SqlCommand("SELECT GameID, TimeLimit FROM Games WHERE Player2 IS NULL",
                                              conn,
                                              trans))
                    {
                        SqlDataReader reader = cmd.ExecuteReader();

                        reader.Read();
                        // If the reader doesn't have any rows, we need to create a pending game.
                        if (!reader.HasRows)
                        {
                            // Close the reader so we can do another SQL Query.
                            reader.Close();
                            using (SqlCommand startPending =
                                       new SqlCommand("INSERT INTO Games (Player1, TimeLimit) VALUES (@Player1, @TimeLimit)",
                                                      conn,
                                                      trans))
                            {
                                startPending.Parameters.AddWithValue("@Player1", join.UserToken);
                                startPending.Parameters.AddWithValue("@TimeLimit", join.TimeLimit);
                                if (startPending.ExecuteNonQuery() != 1)
                                {
                                    throw new DatabaseException("Start pending game has failed unexpectedly");
                                }
                            }
                            // Obtain the new generated GameID from the database.
                            using (SqlCommand getPending = new SqlCommand("SELECT GameID from Games WHERE Player2 IS NULL", conn, trans))
                            {
                                using (SqlDataReader readPending = cmd.ExecuteReader())
                                {
                                    readPending.Read();
                                    if (!readPending.HasRows)
                                    {
                                        throw new DatabaseException("What on earth just happened. this shouldn't EVER happen. line 205 just for reference, you twig.");
                                    }
                                    else
                                    {
                                        response = new JoinGameResponse
                                        {
                                            GameID    = "" + readPending.GetInt32(0),
                                            IsPending = true
                                        };
                                    }
                                }
                            }
                        }

                        // If the reader has rows, a game is already pending, so add this player
                        // to that game and start the game.
                        else
                        {
                            string GameID    = "" + reader.GetInt32(0);
                            int    TimeLimit = reader.GetInt32(1);
                            // Close the reader so we can do another SQL Query.
                            reader.Close();
                            using (SqlCommand startGame =
                                       new SqlCommand("UPDATE Games SET Player2 = @Player2, TimeLimit = @TimeLimit, Board = @Board, StartTime = @StartTime WHERE GameID = @GameID",
                                                      conn,
                                                      trans))
                            {
                                // The time limit is the average of the two player's desired time limits.
                                TimeLimit = (TimeLimit + join.TimeLimit) / 2;
                                // Sanitize all of the input.
                                startGame.Parameters.AddWithValue("@Player2", join.UserToken);
                                startGame.Parameters.AddWithValue("@TimeLimit", TimeLimit);
                                startGame.Parameters.AddWithValue("@Board", new BoggleBoard().ToString());
                                startGame.Parameters.AddWithValue("@StartTime", DateTime.Now);
                                startGame.Parameters.AddWithValue("@GameID", GameID);

                                // Execute the query.
                                if (startGame.ExecuteNonQuery() != 1)
                                {
                                    throw new DatabaseException("Failed to start game!");
                                }
                                response = new JoinGameResponse
                                {
                                    GameID    = GameID,
                                    IsPending = false
                                };
                            }
                        }
                    }
                    // Commit our transanction regardless of what path was taken.
                    trans.Commit();
                }
            }
            // Return the constructed response.
            return(response);
        }
Ejemplo n.º 17
0
        private static void HandleToClient(string source, MessageReader packet)
        {
            var messageType = (MessageType)packet.Tag;

            Console.ForegroundColor = ConsoleColor.Cyan;
            //Console.WriteLine($"{source,-15} Client received: {packet.Tag,-2} {messageType}");

            try
            {
                var reader = packet.GetHazelReader();
                var body   = reader.PeekToEnd();
                if (SaveMessages)
                {
                    File.WriteAllBytes(Path.Combine(MessageFolder, $"recv_{packet.Tag}_{allId++}.bin"), body);
                }

                switch (messageType)
                {
                case MessageType.ReselectServer:
                    var reselect = ReselectServer.Deserialize(reader);
                    DumpToConsole(reselect);
                    break;

                case MessageType.Redirect:
                    var redirect = Redirect.Deserialize(reader);
                    DumpToConsole(redirect);
                    break;

                case MessageType.HostGame:
                    var host = HostGameResponse.Deserialize(reader);
                    DumpToConsole(host);
                    break;

                case MessageType.JoinGame:
                    var join = JoinGameResponse.Deserialize(reader);
                    DumpToConsole(join);
                    break;

                case MessageType.GameData:
                    var gamedata = GameData.Deserialize(reader);
                    foreach (var item in gamedata)
                    {
                        HandleGameData(item, false);
                    }

                    //Directory.CreateDirectory("gamedata");
                    //File.WriteAllBytes(Path.Combine("gamedata", $"recv_data_{gamedataId++}.bin"), body);
                    break;

                case MessageType.GameDataTo:
                    var gamedatato = GameDataTo.Deserialize(reader);
                    foreach (var item in gamedatato)
                    {
                        HandleGameDataTo(item, false);
                    }
                    break;

                case MessageType.JoinedGame:
                    var joined = JoinedGame.Deserialize(reader);
                    DumpToConsole(joined);
                    break;

                case MessageType.AlterGame:
                    var alter = AlterGameResponse.Deserialize(reader);
                    DumpToConsole(alter);
                    break;

                case MessageType.GetGameListV2:
                    var gamelist = GetGameListV2Response.Deserialize(reader);
                    DumpToConsole(gamelist);
                    break;

                case MessageType.RemovePlayer:
                    var removeplayer = RemovePlayerResponse.Deserialize(reader);
                    DumpToConsole(removeplayer);
                    break;

                case MessageType.StartGame:
                    var start = StartGame.Deserialize(reader);
                    DumpToConsole(start);
                    if (BreakOnGameStart)
                    {
                        Console.WriteLine("Press any key to continue...");
                        Console.ReadKey();
                    }
                    break;

                case MessageType.EndGame:
                    var end = EndGame.Deserialize(reader);
                    DumpToConsole(end);
                    if (BreakOnGameEnd)
                    {
                        Console.WriteLine("Press any key to continue...");
                        Console.ReadKey();
                    }

                    //Clear Entities to prevent collisions
                    EntityTracker.entities.Clear();
                    break;

                default:
                    Console.WriteLine($"Unhandled Message: {messageType} size: {body.Length}");
                    return;
                }

                if (reader.GetBytesLeft() > 0 && LogNotConsumed)
                {
                    Console.WriteLine($"[{messageType}]{reader.GetBytesLeft()} bytes not cunsumed");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error handling ToClient: " + ex.Message);
            }
        }
Ejemplo n.º 18
0
        public async Task Test_Join_Private_Game()
        {
            // Doesn't depend on server's state as opposed to the Game_Vs_Random_Opponent test
            #region Game initialization
            var initRequest = new InitGameRequest
            {
                UserName = "******",
                GameType = GameType.VS_RANDOM_PLAYER
            };

            // Request game by player1
            HubConnection firstPlayerConnection = await StartNewConnectionAsync().ConfigureAwait(false);

            GameState initialGameState = await firstPlayerConnection.InvokeAsync <GameState>(GameHubMethodNames.INIT_GAME, initRequest).ConfigureAwait(false);

            initialGameState.Should().NotBeNull();
            initialGameState.BoardState.Should().BeNull();
            initialGameState.Player1.Should().BeEquivalentTo(new Player
            {
                Id   = ExtractUserId(firstPlayerConnection),
                Name = initRequest.UserName,
                Type = PlayerType.HUMAN
            }, "Player 1 must be the one who requested new game");
            initialGameState.Stage.Should().Be(GameStage.WAITING_FOR_OPPONENT);
            initialGameState.Player2.Should().Be(null);

            bool      player1Notified = false;
            GameState player1NotificationGameState = null;
            Semaphore player1NotificationSem       = new Semaphore(0, 1);
            // Setup gameStateUpdate message handler for player 1
            firstPlayerConnection.On <GameNotification>(GameHubMethodNames.RECEIVE_GAME_STATE_UPDATE, (notification) =>
            {
                if (!player1Notified)
                {
                    // First game state update, Just store the state to compare it to state received by player2
                    player1NotificationGameState = notification.NewGameState;
                    player1Notified = true;
                    player1NotificationSem.Release();
                }
            });

            // Request game by player2
            HubConnection secondPlayerConnection = await StartNewConnectionAsync().ConfigureAwait(false);

            secondPlayerConnection.ConnectionId.Should().NotBe(firstPlayerConnection.ConnectionId);             // Just to be safe

            var joinRequest = new JoinPrivateGameRequest
            {
                GameId   = initialGameState.Id,
                UserName = "******"
            };
            JoinGameResponse response = await secondPlayerConnection.InvokeAsync <JoinGameResponse>(GameHubMethodNames.JOIN_PRIVATE_GAME, joinRequest).ConfigureAwait(false);

            GameState joinedGameState = response.GameState;

            response.State.Should().Be(JoinGameResponseType.SUCCESS);

            joinedGameState.Id.Should().Be(initialGameState.Id);
            joinedGameState.Stage.Should().Be(GameStage.PLAYING);
            joinedGameState.Player2.Should().BeEquivalentTo(new Player
            {
                Id   = ExtractUserId(secondPlayerConnection),
                Name = joinRequest.UserName,
                Type = PlayerType.HUMAN
            }, "Player 1 must be the one who requested new game");
            joinedGameState.BoardState.Should().NotBeNull();

            // Wait for notification handler to finish
            if (player1NotificationSem.WaitOne(80000))
            {
                player1Notified.Should().Be(true);
                joinedGameState.Should().BeEquivalentTo(player1NotificationGameState);
            }
            else
            {
                Assert.Fail("Timeout waiting for first player connection to handle notification");
            }
            #endregion
        }