/// <summary>
        /// Request field state after block tap
        /// </summary>
        public void OnPlayerBlockTap(int x, int y)
        {
            Debug.Log("Send tap");

            BlockTapRequest request = new BlockTapRequest();

            request.X = x;
            request.Y = y;

            NetworkManager.Instance.SendBlockTapData(request);
        }
        /// <summary>
        /// Players tap turn processing
        /// </summary>
        /// <param name="clientID"></param>
        /// <param name="request"></param>
        public void ProcessBlockTap(int clientID, BlockTapRequest request)
        {
            Player player = PlayersManager.GetPlayer(clientID);

            if (player == null)
            {
                Console.WriteLine($"Can't find player {clientID}");
                return;
            }

            ProcessBlockTap(player, request);
        }
        /// <summary>
        /// Players tap turn processing
        /// </summary>
        /// <param name="player"></param>
        /// <param name="request"></param>
        public void ProcessBlockTap(Player player, BlockTapRequest request)
        {
            if (player.CurrentMatch == null)
            {
                Console.WriteLine($"Player {player.ClientID} is not in the game");
                return;
            }

            GameMatch         match       = player.CurrentMatch;
            Field             playerField = match.Player1 == player ? match.Field1 : match.Field2;
            Field             enemyField  = match.Player1 == player ? match.Field2 : match.Field1;
            Player            enemy       = match.Player1 == player ? match.Player2 : match.Player1;
            List <EffectData> effectsData = new List <EffectData>();

            FieldManager.RefreshGlobalEffects(playerField, player);
            FieldManager.RefreshGlobalEffects(enemyField, enemy);

            FieldManager.RefreshDurationEffects(playerField);
            FieldManager.RefreshDurationEffects(enemyField);

            if (!FieldManager.TryLockBlock(playerField, request.X, request.Y, out Combo combo))
            {
                // Lock removed, try to pop combo
                if (combo != null)
                {
                    // TODO: merge combo processing code
                    FieldManager.DestroyBlocks(enemyField, combo.Blocks, BlockState.DestroyedAsCombo);
                    effectsData.AddRange(BlockEffectsManager.ApplyEffectsFromCombo(match, match.Player1 == player ? 1 : 2, combo));

                    effectsData.AddRange(FieldManager.ClearDestroyedBlocks(playerField, match, player));
                    effectsData.AddRange(FieldManager.ClearDestroyedBlocks(enemyField, match, enemy));

                    FieldManager.FillHoles(playerField);
                    FieldManager.FillHoles(enemyField);
                }
            }

            GameStateResponse response = new GameStateResponse
            {
                GameState = GetPlayer1MatchStateData(match),
                Effects   = effectsData.ToArray(),
            };

            Server.SendDataToClient(match.Player1.ClientID, (int)DataTypes.GameStateResponse, response);

            if (match.GameMode == GameMode.Practice)
            {
                FieldManager.SetDefaultState(playerField);
                FieldManager.SetDefaultState(enemyField);

                if (CheckForGameEnd(match, out GameEndResponse gameEndResponseDebug))
                {
                    GiveMatchReward(match, gameEndResponseDebug.PlayerWon);
                    RecalculateRating(match, gameEndResponseDebug.PlayerWon);
                    PlayersManager.UpdatePlayer(match.Player1);

                    MatchManager.DropMatch(player.CurrentMatch);

                    gameEndResponseDebug.PlayerStats = match.Player1.GetStatsData();
                    Server.SendDataToClient(match.Player1.ClientID, (int)DataTypes.GameEndResponse, gameEndResponseDebug);
                }
                return;
            }

            response = new GameStateResponse
            {
                GameState = GetPlayer2MatchStateData(match),
                Effects   = effectsData.ToArray(),
            };
            Server.SendDataToClient(match.Player2.ClientID, (int)DataTypes.GameStateResponse, response);

            FieldManager.SetDefaultState(playerField);
            effectsData.AddRange(FieldManager.ClearDestroyedBlocks(playerField, match, player));
            FieldManager.SetDefaultState(enemyField);
            effectsData.AddRange(FieldManager.ClearDestroyedBlocks(enemyField, match, enemy));

            if (CheckForGameEnd(match, out GameEndResponse gameEndResponse))
            {
                GiveMatchReward(match, gameEndResponse.PlayerWon);
                RecalculateRating(match, gameEndResponse.PlayerWon);
                PlayersManager.UpdatePlayer(match.Player1);
                PlayersManager.UpdatePlayer(match.Player2);

                MatchManager.DropMatch(player.CurrentMatch);

                gameEndResponse.PlayerStats = match.Player1.GetStatsData();
                Server.SendDataToClient(match.Player1.ClientID, (int)DataTypes.GameEndResponse, gameEndResponse);

                gameEndResponse.PlayerStats = match.Player2.GetStatsData();
                Server.SendDataToClient(match.Player2.ClientID, (int)DataTypes.GameEndResponse, gameEndResponse);

                return;
            }
        }
 public void SendBlockTapData(BlockTapRequest request)
 {
     client.SendData((int)DataTypes.BlockTapRequest, request);
 }