/// <summary> /// Game session monitor (worker thread) /// </summary> public void GameSessionsMonitorThreadFunc() { NetworkBackgammonEventQueueElement queueElement = null; while (gameSessionsMonitorKeepRunning) { gameSessionsSemaphore.WaitOne(); lock (gameSessionsEventQueue) { if (gameSessionsEventQueue.Count > 0) { queueElement = gameSessionsEventQueue.Dequeue(); } } if (queueElement != null) { if (queueElement.Notifier is NetworkBackgammonGameSession && queueElement.Event is NetworkBackgammonGameSessionEvent) { NetworkBackgammonGameSession gameSession = (NetworkBackgammonGameSession)queueElement.Notifier; NetworkBackgammonGameSessionEvent gameSessionEvent = (NetworkBackgammonGameSessionEvent)queueElement.Event; if (gameSessionEvent.EventType == NetworkBackgammonGameSessionEvent.GameSessionEventType.GameFinished) { // Broadcast the game active event to all registered listeners Broadcast(new NetworkBackgammonGameRoomEvent(NetworkBackgammonGameRoomEvent.GameRoomEventType.PlayerFinished)); gameSession.Stop(); gameSessions.Remove(gameSession); gameSession = null; GC.Collect(); } } } queueElement = null; } }
/// <summary> /// Game session state machine (worker thread) /// </summary> private void Run() { GameSessionState currentState = GameSessionState.InitialDiceRoll; NetworkBackgammonEventQueueElement eventQueueElement = null; // Number of moves left for the active player UInt32 activePlayerMovesLeft = 0; bool activePlayerMoveDoubles = false; while (bStateMachineKeepRunning) { // Wait for another event semStateMachine.WaitOne(); // HACK: Add function here to deal with the big switch statement if (!bStateMachineKeepRunning) { break; } // Read the event element queue to get information on the next event to be processed if (eventQueue.Count > 0) { eventQueueElement = eventQueue.Dequeue(); } #region State Machine Pre-Processing // Handle special events that interfere with the regular game play (e.g. player resignation) if (eventQueueElement != null) { if (eventQueueElement.Event is GameSessionPlayerResignationEvent) { currentState = GameSessionState.PlayerResigned; } } #endregion #region State Machine Main Processing switch (currentState) { #region State: Initial Dice Roll case GameSessionState.InitialDiceRoll: { // Initialize both players checkers to their intial configuration // (start positions) player1.InitCheckers(); player2.InitCheckers(); // Use random number generator to figure out which player starts RollDice(); player1.Active = false; player2.Active = false; player1.InitialDice = dice[0]; player2.InitialDice = dice[1]; Broadcast(new GameSessionInitialDiceRollEvent( player1.PlayerName, player1.InitialDice, player2.PlayerName, player2.InitialDice)); currentState = GameSessionState.InitialDiceRollAcknowledgeExpected; } break; #endregion #region State: Initial Dice Roll Acknowledge Expected case GameSessionState.InitialDiceRollAcknowledgeExpected: { if (eventQueueElement != null) { try { if (eventQueueElement.Event is GameSessionInitialDiceRollAcknowledgeEvent) { GameSessionInitialDiceRollAcknowledgeEvent gameSessionEvent = (GameSessionInitialDiceRollAcknowledgeEvent)eventQueueElement.Event; NetworkBackgammonPlayer sendingPlayer = (NetworkBackgammonPlayer)eventQueueElement.Notifier; // Latch (flag) acknowledge of initial dice roll from respective player sendingPlayer.InitialDice.FlagUsed = true; // Check whether both players have acknowledged initial dice roll if (player1.InitialDice.FlagUsed && player2.InitialDice.FlagUsed) { // If dice rolled are a tie, roll again if (dice[0].CurrentValue == dice[1].CurrentValue) { RollDice(); player1.InitialDice = dice[0]; player2.InitialDice = dice[1]; Broadcast(new GameSessionInitialDiceRollEvent( player1.PlayerName, player1.InitialDice, player2.PlayerName, player2.InitialDice)); } else { // Determine active player (the one who won the initial dice roll if (dice[0].CurrentValueUInt32 > dice[1].CurrentValueUInt32) { player1.Active = true; } else { player2.Active = true; } // Calculate number of moves left for active player (based on dice values) activePlayerMoveDoubles = dice[0].CurrentValue == dice[1].CurrentValue; activePlayerMovesLeft = (UInt32)(activePlayerMoveDoubles ? 4 : 2); // Calculate possible moves for active player (and figure out whether active player // actually has possible moves) bool activePlayerHasPossibleMoves = NetworkBackgammonGameEngine.CalculatePossibleMoves( ref player1, ref player2, activePlayerMoveDoubles ? new NetworkBackgammonDice[] { dice[0] } : dice); // Send initial checkers with positions (and possible valid moves // for the active player) to both players Broadcast(new GameSessionCheckerUpdatedEvent(player1, player2, dice[0], dice[1])); if (activePlayerHasPossibleMoves) { // Inform players about who's expected to make the next move Broadcast(new GameSessionMoveExpectedEvent(player1.Active ? player1.PlayerName : player2.PlayerName)); // Set next iteration's state currentState = GameSessionState.MoveExpected; } else { // Inform both players that currently active player has no moves left // which needs to be acknowledged by the active player Broadcast(new GameSessionNoPossibleMovesEvent(player1.Active ? player1.PlayerName : player2.PlayerName)); currentState = GameSessionState.NoPossibleMovesAcknowledgeExpected; } } } } } catch (Exception) { Broadcast(new NetworkBackgammonGameSessionEvent(NetworkBackgammonGameSessionEvent.GameSessionEventType.Error)); } } } break; #endregion #region State: Move Expected case GameSessionState.MoveExpected: { if (eventQueueElement != null) { try { NetworkBackgammonPlayer sendingPlayer = (NetworkBackgammonPlayer)eventQueueElement.Notifier; GameSessionMoveSelectedEvent moveSelectedEvent = (GameSessionMoveSelectedEvent)eventQueueElement.Event; // Check whether the active player attempted to move if (sendingPlayer.Active) { // Perform the selected move of the active player (and check whether the active player won the game if (!NetworkBackgammonGameEngine.ExecuteMove(ref player1, ref player2, moveSelectedEvent.CheckerSelected, moveSelectedEvent.MoveSelected)) { // Figure out the next active player (could be the current // active player since up to 4 moves are allowed per turn) if (--activePlayerMovesLeft <= 0) { player1.Active = !player1.Active; player2.Active = !player2.Active; // Roll dice for active player RollDice(); // Calculate number of moves left for active player (based on dice values) activePlayerMoveDoubles = dice[0].CurrentValue == dice[1].CurrentValue; activePlayerMovesLeft = (UInt32)(activePlayerMoveDoubles ? 4 : 2); } // Calculate possible moves for active player (and figure out whether active player // actually has possible moves) bool activePlayerHasPossibleMoves = NetworkBackgammonGameEngine.CalculatePossibleMoves( ref player1, ref player2, activePlayerMoveDoubles ? new NetworkBackgammonDice[] { dice[0] } : activePlayerMovesLeft == 2 ? dice : new NetworkBackgammonDice[] { dice[0] == moveSelectedEvent.MoveSelected ? dice[1] : dice[0] }); // Send updated checkers with positions (and possible valid moves // for the active player) to both players Broadcast(new GameSessionCheckerUpdatedEvent(player1, player2, dice[0], dice[1])); if (activePlayerHasPossibleMoves) { // Inform players about who's expected to make the next move Broadcast(new GameSessionMoveExpectedEvent(player1.Active ? player1.PlayerName : player2.PlayerName)); } else { // Inform both players that currently active player has no moves left // which needs to be acknowledged by the active player Broadcast(new GameSessionNoPossibleMovesEvent(player1.Active ? player1.PlayerName : player2.PlayerName)); currentState = GameSessionState.NoPossibleMovesAcknowledgeExpected; } } else { // Inform both players that the game has been won by the active player Broadcast(new GameSessionPlayerWonEvent(sendingPlayer.PlayerName)); currentState = GameSessionState.GameFinished; } } else { Broadcast(new NetworkBackgammonGameSessionEvent(NetworkBackgammonGameSessionEvent.GameSessionEventType.Error)); } } catch (Exception) { Broadcast(new NetworkBackgammonGameSessionEvent(NetworkBackgammonGameSessionEvent.GameSessionEventType.Error)); } } } break; #endregion #region State: No Possible Moves Acknowledge Expected case GameSessionState.NoPossibleMovesAcknowledgeExpected: { if (eventQueueElement != null) { try { GameSessionNoPossibleMovesAcknowledgeEvent gameSessionNoPossibleMovesAcknowledgeEvent = (GameSessionNoPossibleMovesAcknowledgeEvent)eventQueueElement.Event; // Make sure that the active player acknowledges (and not the waiting player) if (gameSessionNoPossibleMovesAcknowledgeEvent.PlayerName == (player1.Active ? player1.PlayerName : player2.PlayerName)) { player1.Active = !player1.Active; player2.Active = !player2.Active; // Roll dice for (new) active player RollDice(); // Calculate number of moves left for active player (based on dice values) activePlayerMoveDoubles = dice[0].CurrentValue == dice[1].CurrentValue; activePlayerMovesLeft = (UInt32)(activePlayerMoveDoubles ? 4 : 2); bool activePlayerHasPossibleMoves = NetworkBackgammonGameEngine.CalculatePossibleMoves( ref player1, ref player2, activePlayerMoveDoubles ? new NetworkBackgammonDice[] { dice[0] } : activePlayerMovesLeft == 2 ? dice : new NetworkBackgammonDice[] { dice[0], dice[1] }); // Send updated checkers with positions (and possible valid moves // for the active player) to both players Broadcast(new GameSessionCheckerUpdatedEvent(player1, player2, dice[0], dice[1])); if (activePlayerHasPossibleMoves) { // Inform players about who's expected to make the next move Broadcast(new GameSessionMoveExpectedEvent(player1.Active ? player1.PlayerName : player2.PlayerName)); currentState = GameSessionState.MoveExpected; } else { // Inform both players that currently active player has no moves left // which needs to be acknowledged by the active player Broadcast(new GameSessionNoPossibleMovesEvent(player1.Active ? player1.PlayerName : player2.PlayerName)); } } else { Broadcast(new NetworkBackgammonGameSessionEvent(NetworkBackgammonGameSessionEvent.GameSessionEventType.Error)); } } catch (Exception) { Broadcast(new NetworkBackgammonGameSessionEvent(NetworkBackgammonGameSessionEvent.GameSessionEventType.Error)); } } } break; #endregion #region State: Player Resigned case GameSessionState.PlayerResigned: { if (eventQueueElement != null) { try { NetworkBackgammonPlayer resignedPlayer = (NetworkBackgammonPlayer)eventQueueElement.Notifier; Broadcast(new GameSessionPlayerResignationEvent(resignedPlayer.PlayerName)); currentState = GameSessionState.GameFinished; } catch (Exception ex) { Broadcast(new NetworkBackgammonGameSessionEvent(NetworkBackgammonGameSessionEvent.GameSessionEventType.Error)); } } } break; #endregion default: break; } #endregion #region State Machine Post-Processing if (currentState == GameSessionState.GameFinished) { Broadcast(new NetworkBackgammonGameSessionEvent(NetworkBackgammonGameSessionEvent.GameSessionEventType.GameFinished)); bStateMachineKeepRunning = false; } #endregion eventQueueElement = null; } }
public void OnEventNotification(INetworkBackgammonNotifier sender, INetworkBackgammonEvent e) { if (sender is NetworkBackgammonPlayer) { NetworkBackgammonPlayer player = (NetworkBackgammonPlayer)sender; if (e is NetworkBackgammonChallengeResponseEvent) { NetworkBackgammonChallengeResponseEvent challengeResponseEvent = (NetworkBackgammonChallengeResponseEvent)e; if (challengeSyncList.Keys.Contains(player)) { if (challengeSyncList[player] != null) { challengeSyncList[player].ChallengeAccepted = challengeResponseEvent.ChallengeAccepted; try { challengeSyncList[player].ChallengeSemaphore.Release(); } catch (SemaphoreFullException ex) { // TODO: If this exception occurs calling Release too many times... } } } } else if (e is NetworkBackgammonChatEvent) { // Pass the message through to the listeners Broadcast((NetworkBackgammonChatEvent)e); } } else if (sender is NetworkBackgammonGameSession) { NetworkBackgammonGameSession gameSession = (NetworkBackgammonGameSession)sender; if (e is NetworkBackgammonGameSessionEvent) { NetworkBackgammonGameSessionEvent gameSessionEvent = (NetworkBackgammonGameSessionEvent)e; if (gameSessionEvent.EventType == NetworkBackgammonGameSessionEvent.GameSessionEventType.GameFinished) { bool newQueueItemAdd = true; NetworkBackgammonEventQueueElement newQueueItem = new NetworkBackgammonEventQueueElement(e, sender); lock (newQueueItem) { /* * if (gameSessionsEventQueue.Count > 0) * { * NetworkBackgammonEventQueueElement lastQueueItem = gameSessionsEventQueue.Last(); * // Avoid adding the same event (from the same sender) twice * // Reason: Game Room listens to events from all players, i.e. also * // both players that are in one Game Session. Thus, all events broadcasted * // by the Game Session arrive here (at the Game Room) twice * if (gameSessionsEventQueue.Last().Notifier == sender && * gameSessionsEventQueue.Last().Event == e) * { * newQueueItemAdd = false; * } * } */ if (newQueueItemAdd) { gameSessionsEventQueue.Enqueue(new NetworkBackgammonEventQueueElement(e, sender)); gameSessionsSemaphore.Release(); } } } } } }