private void Handle_Play_Shortform_Command( CMatchPlayShortformSlashCommand command )
        {
            ECardColor play_color;
            ECardValue play_value;
            ECardColor draw_color;

            CCardUtils.Convert_Card_From_Short_Form( command.CardShortform, out play_color, out play_value );
            if ( play_color == ECardColor.Invalid || play_value == ECardValue.Invalid )
            {
                CClientResource.Output_Text< EClientTextID >( EClientTextID.Client_Game_Action_Invalid_Card_Shortform, command.CardShortform );
                return;
            }

            CCardUtils.Extract_Color_From_Short_Form( command.DrawCode, out draw_color );

            CGameActionBase draw_action = null;
            if ( draw_color == ECardColor.Invalid )
            {
                draw_action = new CDrawFromDeckGameAction();
            }
            else
            {
                draw_action = new CDrawFromDiscardGameAction( draw_color );
            }

            Handle_Pending_Turn( new CMatchTakeTurnRequest( new CPlayCardGameAction( new CCard( play_color, play_value ) ), draw_action ) );
            if ( m_PendingTurn != null )
            {
                CClientResource.Output_Text< EClientTextID >( EClientTextID.Client_Game_Action_Turn_Submitted );

                CClientLogicalThread.Instance.Send_Message_To_Server( m_PendingTurn );
                m_PendingTurn = null;
            }
        }
        private void Handle_Reset_Turn_Command( CMatchResetTurnSlashCommand command )
        {
            EGameActionFailure failure_message = Is_Local_Player_Turn();
            if ( failure_message != EGameActionFailure.None )
            {
                CClientResource.Output_Text< EClientTextID >( Convert_Game_Action_Failure_To_Text_ID( failure_message ) );
                return;
            }

            CClientResource.Output_Text< EClientTextID >( EClientTextID.Client_Game_Action_Turn_Reset );

            m_PendingTurn = null;
        }
        private void Handle_Pass_Shortform_Command( CMatchPassShortformSlashCommand command )
        {
            ECardColor pass_color1;
            ECardValue pass_value1;

            CCardUtils.Convert_Card_From_Short_Form( command.CardShortform1, out pass_color1, out pass_value1 );
            if ( pass_color1 == ECardColor.Invalid || pass_value1 == ECardValue.Invalid )
            {
                CClientResource.Output_Text< EClientTextID >( EClientTextID.Client_Game_Action_Invalid_Card_Shortform, command.CardShortform1 );
                return;
            }

            ECardColor pass_color2;
            ECardValue pass_value2;

            CCardUtils.Convert_Card_From_Short_Form( command.CardShortform2, out pass_color2, out pass_value2 );
            if ( pass_color2 == ECardColor.Invalid || pass_value2 == ECardValue.Invalid )
            {
                CClientResource.Output_Text< EClientTextID >( EClientTextID.Client_Game_Action_Invalid_Card_Shortform, command.CardShortform2 );
                return;
            }

            Handle_Pending_Turn( new CMatchTakeTurnRequest( new CPassCardsGameAction( new CCard( pass_color1, pass_value1 ), new CCard( pass_color2, pass_value2 ) ) ) );
            if ( m_PendingTurn != null )
            {
                CClientResource.Output_Text< EClientTextID >( EClientTextID.Client_Game_Action_Turn_Submitted );

                CClientLogicalThread.Instance.Send_Message_To_Server( m_PendingTurn );
                m_PendingTurn = null;
            }
        }
        private void Handle_Pending_Turn( CMatchTakeTurnRequest pending_turn )
        {
            EGameActionFailure failure_message = Is_Local_Player_Turn();
            if ( failure_message != EGameActionFailure.None )
            {
                CClientResource.Output_Text< EClientTextID >( Convert_Game_Action_Failure_To_Text_ID( failure_message ) );
                return;
            }

            if ( m_PendingTurn != null )
            {
                CClientResource.Output_Text< EClientTextID >( EClientTextID.Client_Game_Action_Turn_Is_Already_Complete );
                return;
            }

            failure_message = Match.Validate_Turn( pending_turn, CClientLogicalThread.Instance.ConnectedID );
            if ( failure_message != EGameActionFailure.None )
            {
                CClientResource.Output_Text< EClientTextID >( Convert_Game_Action_Failure_To_Text_ID( failure_message ) );
                return;
            }

            CClientResource.Output_Text< EClientTextID >( EClientTextID.Client_Game_Action_Queued, CClientResource.Get_Text< EClientTextID >( EClientTextID.Client_Command_Name_Match_End_Turn ) );

            m_PendingTurn = pending_turn;
        }
        private void Handle_End_Turn_Command( CMatchEndTurnSlashCommand command )
        {
            EGameActionFailure failure_message = Is_Local_Player_Turn();
            if ( failure_message != EGameActionFailure.None )
            {
                CClientResource.Output_Text< EClientTextID >( Convert_Game_Action_Failure_To_Text_ID( failure_message ) );
                return;
            }

            if ( m_PendingTurn == null )
            {
                CClientResource.Output_Text< EClientTextID >( EClientTextID.Client_Game_Action_Your_Turn_Is_Incomplete );
                return;
            }

            CClientResource.Output_Text< EClientTextID >( EClientTextID.Client_Game_Action_Turn_Submitted );

            CClientLogicalThread.Instance.Send_Message_To_Server( m_PendingTurn );
            m_PendingTurn = null;
        }
        public void Try_Take_Turn( CMatchTakeTurnRequest request, EPersistenceID source_player )
        {
            if ( !MatchState.Is_Player( source_player ) )
            {
                CServerMessageRouter.Send_Message_To_Player( new CMatchTakeTurnResponse( request.RequestID, EGameActionFailure.Not_A_Player ), source_player );
                return;
            }

            EGameActionFailure failure_reason = Validate_Turn( request, source_player );
            if ( failure_reason != EGameActionFailure.None )
            {
                CServerMessageRouter.Send_Message_To_Player( new CMatchTakeTurnResponse( request.RequestID, failure_reason ), source_player );
                return;
            }

            CServerMessageRouter.Send_Message_To_Player( new CMatchTakeTurnResponse( request.RequestID, EGameActionFailure.None ), source_player );

            // Build, Apply, Broadcast deltas
            List< IObservedClonableDelta > deltas = new List< IObservedClonableDelta >();
            Build_Turn_Deltas( request, source_player, deltas );
            Apply_Turn_Deltas( deltas );

            foreach ( var pid in ConnectedPlayersAndObservers )
            {
                List< IObservedClonableDelta > cloned_deltas = new List< IObservedClonableDelta >( deltas.Select( delta => delta.Clone( pid, MatchState.Is_Admin_Observer( pid ) ) ) );
                CServerMessageRouter.Send_Message_To_Player( new CMatchDeltaSetMessage( cloned_deltas ), pid );
            }

            if ( GameState.Is_Game_Finished() )
            {
                On_Game_End_Naturally();
            }
        }
        public void Build_Turn_Deltas( CMatchTakeTurnRequest request, EPersistenceID source_player, List< IObservedClonableDelta > deltas )
        {
            foreach ( var action in request.Actions )
            {
                switch ( action.Action )
                {
                    case EGameAction.Play_Card:
                        CPlayCardGameAction play_action = action as CPlayCardGameAction;
                        ECardColor play_color = play_action.Card.Color;
                        ECardValue play_value = play_action.Card.Value;
                        deltas.Add( new CPlayerHand.CPlayerHandDelta( source_player, ECardDelta.Remove, play_color, play_value ) );
                        deltas.Add( new CCardCollection.CCardCollectionDelta( MatchState.Get_Side_For_Player( source_player ), play_color, play_value ) );
                        break;

                    case EGameAction.Discard_Card:
                        CDiscardCardGameAction discard_action = action as CDiscardCardGameAction;
                        ECardColor discard_color = discard_action.Card.Color;
                        ECardValue discard_value = discard_action.Card.Value;
                        deltas.Add( new CPlayerHand.CPlayerHandDelta( source_player, ECardDelta.Remove, discard_color, discard_value ) );
                        deltas.Add( new CDiscardPile.CDiscardPileDelta( ECardDelta.Add, discard_color, discard_value ) );
                        break;

                    case EGameAction.Draw_From_Deck:
                        CDeck deck = GameState.Get_Deck();
                        CCard top_card = deck.Peek_Top_Card();
                        deltas.Add( new CDeck.CDeckDelta() );
                        deltas.Add( new CPlayerHand.CPlayerHandDelta( source_player, ECardDelta.Add, top_card.Color, top_card.Value ) );
                        break;

                    case EGameAction.Draw_From_Discard:
                        CDrawFromDiscardGameAction draw_from_discard_action = action as CDrawFromDiscardGameAction;
                        ECardColor draw_color = draw_from_discard_action.Color;
                        CDiscardPile discard_pile = GameState.Get_Discard_Pile( draw_color );
                        ECardValue draw_value = discard_pile.Get_Top_Card().Value;
                        deltas.Add( new CPlayerHand.CPlayerHandDelta( source_player, ECardDelta.Add, draw_color, draw_value ) );
                        deltas.Add( new CDiscardPile.CDiscardPileDelta( ECardDelta.Remove, draw_color, draw_value ) );
                        break;

                    case EGameAction.Pass_Cards:
                        CPassCardsGameAction pass_action = action as CPassCardsGameAction;
                        deltas.Add( new CPlayerHand.CPlayerHandDelta( source_player, ECardDelta.Remove, pass_action.Card1.Color, pass_action.Card1.Value ) );
                        deltas.Add( new CPlayerHand.CPlayerHandDelta( source_player, ECardDelta.Remove, pass_action.Card2.Color, pass_action.Card2.Value ) );

                        EPersistenceID partner_id = MatchState.Get_Partner_For( source_player );
                        deltas.Add( new CPlayerHand.CPlayerHandDelta( partner_id, ECardDelta.Add, pass_action.Card1.Color, pass_action.Card1.Value ) );
                        deltas.Add( new CPlayerHand.CPlayerHandDelta( partner_id, ECardDelta.Add, pass_action.Card2.Color, pass_action.Card2.Value ) );
                        break;
                }
            }

            deltas.Add( new CGameState.CGameStateDelta( ( GameState.CurrentTurnIndex + 1 ) % CGameModeUtils.Player_Count_For_Game_Mode( GameState.Mode ) ) );
        }
        private void Handle_Match_Take_Turn_Message( CMatchTakeTurnRequest request, EPersistenceID source_player )
        {
            CConnectedPlayer player = CConnectedPlayerManager.Instance.Get_Player_By_Persistence_ID( source_player );
            if ( player == null )
            {
                return;
            }

            CServerMatchInstance match_instance = Get_Match_Instance( player.MatchID );
            if ( match_instance == null )
            {
                CServerMessageRouter.Send_Message_To_Player( new CMatchTakeTurnResponse( request.RequestID, EGameActionFailure.Not_In_Match ), source_player );
                return;
            }

            match_instance.Try_Take_Turn( request, source_player );
        }
        public EGameActionFailure Validate_Turn( CMatchTakeTurnRequest pending_turn, EPersistenceID player_id )
        {
            if ( MatchState.State != EMatchInstanceState.Idle )
            {
                return EGameActionFailure.Match_Halted;
            }

            if ( GameState.CurrentPlayer != player_id )
            {
                return EGameActionFailure.Not_Your_Turn;
            }

            if ( pending_turn.Actions.Count > 2 )
            {
                return EGameActionFailure.Invalid_Turn;
            }

            if ( pending_turn.Actions.Count == 0 )
            {
                return EGameActionFailure.Turn_Is_Incomplete;
            }

            for ( int i = 0; i < pending_turn.Actions.Count; i++ )
            {
                CGameActionBase action = pending_turn.Actions[ i ];
                switch ( action.Action )
                {
                    case EGameAction.Play_Card:
                        if ( i != 0 )
                        {
                            return EGameActionFailure.Play_Card_Must_Be_First_Action;
                        }

                        if ( pending_turn.Actions.Count != 2 )
                        {
                            return EGameActionFailure.Turn_Is_Incomplete;
                        }

                        CPlayCardGameAction play_action = action as CPlayCardGameAction;
                        ECardColor color = play_action.Card.Color;
                        ECardValue value = play_action.Card.Value;
                        if ( !GameState.Get_Player_Hand( player_id ).Contains_Card( color, value ) )
                        {
                            return EGameActionFailure.Card_Is_Not_In_Your_Hand;
                        }

                        CCardCollection local_collection = GameState.Get_Card_Collection( Get_Side_For_Player( player_id ), color );
                        int local_collection_top_value = local_collection.Get_Top_Card_Score_Value();
                        if ( CCardUtils.Get_Card_Score_Value( value ) <= local_collection_top_value && local_collection_top_value != 0 )
                        {
                            return EGameActionFailure.A_Higher_Card_Already_Exists;
                        }

                        break;

                    case EGameAction.Discard_Card:
                        if ( i != 0 )
                        {
                            return EGameActionFailure.Discard_Card_Must_Be_First_Action;
                        }

                        if ( pending_turn.Actions.Count != 2 )
                        {
                            return EGameActionFailure.Turn_Is_Incomplete;
                        }

                        CDiscardCardGameAction discard_action = action as CDiscardCardGameAction;
                        if ( !GameState.Get_Player_Hand( player_id ).Contains_Card( discard_action.Card.Color, discard_action.Card.Value ) )
                        {
                            return EGameActionFailure.Card_Is_Not_In_Your_Hand;
                        }
                        break;

                    case EGameAction.Draw_From_Deck:
                        if ( i != 1 )
                        {
                            return EGameActionFailure.You_Must_Play_A_Card_Before_Drawing;
                        }

                        if ( GameState.Get_Deck().Count == 0 )
                        {
                            return EGameActionFailure.Deck_Is_Empty;
                        }
                        break;

                    case EGameAction.Draw_From_Discard:
                        if ( i != 1 )
                        {
                            return EGameActionFailure.You_Must_Play_A_Card_Before_Drawing;
                        }

                        CDrawFromDiscardGameAction draw_action = action as CDrawFromDiscardGameAction;
                        if ( GameState.Get_Discard_Pile( draw_action.Color ).Count == 0 )
                        {
                            // handle pathological case of "spinning in place" where you discard a card and then redraw it; if the discard pile is empty when you attempt this, then
                            // the simple validation approach of checking current state is insufficient
                            CDiscardCardGameAction discard_to_pile_action = pending_turn.Actions[ 0 ] as CDiscardCardGameAction;
                            if ( discard_to_pile_action == null || discard_to_pile_action.Card.Color != draw_action.Color )
                            {
                                return EGameActionFailure.Discard_Pile_Is_Empty;
                            }
                        }
                        break;

                    case EGameAction.Pass_Cards:
                        if ( GameState.Mode != EGameModeType.Four_Players )
                        {
                            return EGameActionFailure.Can_Only_Pass_Cards_In_A_Four_Player_Game;
                        }

                        if ( i != 0 )
                        {
                            return EGameActionFailure.Passing_Cards_Must_Be_Only_Action;
                        }

                        if ( pending_turn.Actions.Count != 1 )
                        {
                            return EGameActionFailure.Passing_Cards_Must_Be_Only_Action;
                        }

                        CPassCardsGameAction pass_action = action as CPassCardsGameAction;
                        CPlayerHand player_hand = GameState.Get_Player_Hand( player_id );
                        if ( !player_hand.Contains_Card( pass_action.Card1.Color, pass_action.Card1.Value ) || !player_hand.Contains_Card( pass_action.Card2.Color, pass_action.Card2.Value ) )
                        {
                            return EGameActionFailure.Card_Is_Not_In_Your_Hand;
                        }

                        if ( pass_action.Card1.Color == pass_action.Card2.Color && pass_action.Card1.Value == pass_action.Card2.Value )
                        {
                            return EGameActionFailure.Two_Distinct_Cards_Must_Be_Passed;
                        }

                        if ( player_hand.Count < 8 )
                        {
                            return EGameActionFailure.Not_Enough_Cards_In_Hand_To_Pass;
                        }
                        break;
                }
            }

            return EGameActionFailure.None;
        }