private void HandleActiveGroups(IStateOwner pOwner, bool ForceFall = false)
        {
            bool reprocess = true;

            while (reprocess)
            {
                reprocess = false;
                List <Nomino> HandledGroups = new List <Nomino>();

                var AnyMoved = false;

                //go through each active block group, starting from the lowest to the highest.
                foreach (var iterate in from abg in PlayField.BlockGroups orderby abg.Max((i) => i.Y) ascending select abg)
                {
                    //if this blockgroup is empty (somehow) we don't need to waste time with the rest of it.
                    if (iterate.Count() == 0)
                    {
                        continue;
                    }
                    //if forcing a fall (down key was pressed for example) Or if we need to make it fall naturally because the appropriate fall speed elapsed, do so.
                    if (ForceFall || (pOwner.GetElapsedTime() - iterate.LastFall).TotalMilliseconds > iterate.FallSpeed)
                    {
                        if (HandleGroupOperation(pOwner, iterate) == GroupOperationResult.Operation_Success)
                        {
                            if (!SuspendFieldSet)
                            {
                                GameHandler.ProcessFieldChange(this, pOwner, iterate);
                                HandledGroups.Add(iterate);
                                reprocess = true;
                                continue;
                            }
                        }
                        else
                        {
                            AnyMoved = true;

                            if (iterate.MoveSound && !SuspendFieldSet)
                            {
                                //Make a movement sound as we fall.
                                Sounds.PlaySound(pOwner.AudioThemeMan.BlockFalling.Key);
                            }
                            iterate.LastFall = pOwner.GetElapsedTime();
                        }
                    }
                }
                if (SuspendFieldSet && !AnyMoved)
                {
                    SuspendFieldSet = false;                               //all blocks fell...
                }
            }
        }
예제 #2
0
 public void SetY(IStateOwner pOwner, int Value)
 {
     if (pOwner != null)
     {
         LastFall = pOwner.GetElapsedTime();
     }
     Y = Value;
 }
예제 #3
0
        public static Func <BaseParticle, BCColor> GetRainbowColorFunc(IStateOwner pOwner, int cycletime = 2000)
        {
            return(new Func <BaseParticle, BCColor>((o) =>
            {
                int timebase = cycletime;
                int hue = (int)((float)(pOwner.GetElapsedTime().Ticks % timebase) / (float)timebase * 240);
                BCColor usecolor = new HSLColor(hue, 200d, 128d);

                return usecolor;
            }));
        }
예제 #4
0
            public StatefulReplayState(IStateOwner pOwner, GameplayGameState Source)
            {
                var Field = Source.PlayField;

                Rows            = Field.RowCount;
                Columns         = Field.ColCount;
                ElapsedGameTime = pOwner.GetElapsedTime();
                BoardState      = new StatefulReplayStateBlockInformation[Field.RowCount][];
                for (int y = 0; y < Field.RowCount; y++)
                {
                    BoardState[y] = new StatefulReplayStateBlockInformation[Field.ColCount];
                    for (int x = 0; x < BoardState[y].Length; x++)
                    {
                        BoardState[y][x] = new StatefulReplayStateBlockInformation(StatefulReplayStateBlockInformation.BlockInformation.Block_Empty);
                    }
                }

                //step one: set the Active Block positions.
                foreach (var activegroup in Source.PlayField.BlockGroups)
                {
                    int XGroup = activegroup.X;
                    int YGroup = activegroup.Y;
                    foreach (var iterateblock in activegroup)
                    {
                        int BlockX = iterateblock.X + XGroup;
                        int BlockY = iterateblock.Y + YGroup;
                        BoardState[BlockY][BlockX] = new StatefulReplayStateBlockInformation(StatefulReplayStateBlockInformation.BlockInformation.Block_Active);
                    }
                }
                //step two: occupied block positions.
                for (int CurrentRow = 0; CurrentRow < Source.PlayField.Contents.Length; CurrentRow++)
                {
                    for (int CurrentCol = 0; CurrentCol < Source.PlayField.Contents[CurrentRow].Length; CurrentCol++)
                    {
                        var thisBlock = Source.PlayField.Contents[CurrentRow][CurrentCol];

                        if (thisBlock != null)
                        {
                            BoardState[CurrentRow][CurrentCol].State = StatefulReplayStateBlockInformation.BlockInformation.Block_Occupied;
                        }
                        else
                        {
                            BoardState[CurrentRow][CurrentCol].State = StatefulReplayStateBlockInformation.BlockInformation.Block_Empty;
                        }
                    }
                }
            }
예제 #5
0
 public float GetHeightTranslation(IStateOwner pOwner, float BlockHeight)
 {
     if (!pOwner.CurrentState.GamePlayActive)
     {
         return(0);
     }
     else
     {
         double Percent = ((pOwner.GetElapsedTime() - LastFall).TotalMilliseconds) / (double)FallSpeed;
         if (Percent > 1)
         {
             Percent = 1;
         }
         if (Percent < 0)
         {
             Percent = 0;
         }
         return((float)(((float)BlockHeight) * (Percent)));
     }
 }
        private GroupOperationResult HandleGroupOperation(IStateOwner pOwner, Nomino activeItem)
        {
            if (activeItem.HandleBlockOperation(pOwner))
            {
                return(GroupOperationResult.Operation_Success);
            }
            var fitresult = PlayField.CanFit(activeItem, activeItem.X, activeItem.Y + 1, false);

            if (fitresult == TetrisField.CanFitResultConstants.CanFit)
            {
                activeItem.SetY(pOwner, activeItem.Y + 1);
            }
            else if (fitresult == TetrisField.CanFitResultConstants.CantFit_Field)
            {
                if (GameOptions.MoveResetsSetTimer && (DateTime.Now - lastHorizontalMove).TotalMilliseconds > pOwner.Settings.std.LockTime)
                {
                    var elapsed = pOwner.GetElapsedTime();
                    //any and all blockgroups in the field that are set not to allow input must have not moved in the last 750ms before we allow any groups to set.

                    var allgroups  = PlayField.GetActiveBlockGroups();
                    var Applicable = allgroups.All((f) =>
                    {
                        return(!f.Controllable || (elapsed - f.LastFall).TotalMilliseconds > 750);
                    });
                    Applicable = true;
                    if (Applicable)
                    {
                        PlayField.SetGroupToField(activeItem);
                        GameStats.AddScore(25 - activeItem.Y);
                        if (activeItem.PlaceSound)
                        {
                            Sounds.PlaySound(pOwner.AudioThemeMan.BlockGroupPlace.Key, pOwner.Settings.std.EffectVolume);
                        }
                        return(GroupOperationResult.Operation_Success);
                    }
                }
            }


            return(GroupOperationResult.Operation_Error);
        }
예제 #7
0
        protected String FormatGameTime(IStateOwner stateowner)
        {
            TimeSpan useCalc = stateowner.GetElapsedTime();

            return(useCalc.ToString(@"hh\:mm\:ss"));
        }
        protected virtual void SpawnNewTetromino(IStateOwner pOwner)
        {
            //TODO (Possibly)- could we animate the next nomino in about 250ms from the position it is in in the "next" circle group, to above the playfield?
            //Maybe we can implement that as part of the drawing code instead? it can record when the last nomino dropped and use that as a basis for
            //tweening between the "last" position of the next nomino and the middle top of the playfield.


            if (NoTetrominoSpawn)
            {
                return;
            }
            BlockHold = false;
            if (NextBlocks.Count == 0)
            {
                RefillBlockQueue();
            }

            var nextget = NextBlocks.Dequeue();

            if (NextBlocks.Count < GameOptions.NextQueueSize)
            {
                RefillBlockQueue();
            }

            nextget.X = (int)(((float)PlayField.ColCount / 2) - ((float)nextget.GroupExtents.Width / 2));
            nextget.SetY(null, 0);
            if (GameStats is TetrisStatistics ts)
            {
                if (nextget is Tetromino_I)
                {
                    ts.I_Piece_Count++;
                }
                else if (nextget is Tetromino_J)
                {
                    ts.J_Piece_Count++;
                }
                else if (nextget is Tetromino_L)
                {
                    ts.L_Piece_Count++;
                }
                else if (nextget is Tetromino_O)
                {
                    ts.O_Piece_Count++;
                }
                else if (nextget is Tetromino_S)
                {
                    ts.S_Piece_Count++;
                }
                else if (nextget is Tetromino_T)
                {
                    ts.T_Piece_Count++;
                }
                else if (nextget is Tetromino_Z)
                {
                    ts.Z_Piece_Count++;
                }
                //FallSpeed is 1000 -50 for each level. Well, for now.
            }
            SetLevelSpeed(nextget);
            NextAngleOffset += Math.PI * 2 / 5;
            nextget.LastFall = pOwner.GetElapsedTime().Add(new TimeSpan(0, 0, 0, 0, 100));
            PlayField.AddBlockGroup(nextget);
            PlayField.Theme.ApplyTheme(nextget, GameHandler, PlayField, NominoTheme.ThemeApplicationReason.NewNomino);
        }
        public override void GameProc(IStateOwner pOwner)
        {
            if (ReplayData == null)
            {
                ReplayData = new StatefulReplay();
            }
            if (!FirstRun)
            {
                if (GameOptions.MusicEnabled)
                {
                    iActiveSoundObject musicplay = null;
                    if (pOwner.Settings.std.MusicOption == "<RANDOM>")
                    {
                        musicplay = Sounds.PlayMusic(pOwner.AudioThemeMan.BackgroundMusic.Key, pOwner.Settings.std.MusicVolume, true);
                    }
                    else
                    {
                        musicplay = Sounds.PlayMusic(pOwner.Settings.std.MusicOption, pOwner.Settings.std.MusicVolume, true);
                    }


                    if (musicplay != null)
                    {
                        musicplay.Tempo = 1f;
                    }
                    FirstRun = true;
                    GameHandler.PrepareField(this, pOwner);
                }
            }

            //update particles.

            FrameUpdate();
            if (pOwner.GameStartTime == DateTime.MinValue)
            {
                pOwner.GameStartTime = DateTime.Now;
            }
            if (pOwner.LastPausedTime != DateTime.MinValue)
            {
                pOwner.GameStartTime += (DateTime.Now - pOwner.LastPausedTime);
                pOwner.LastPausedTime = DateTime.MinValue;
                foreach (var iterate in PlayField.BlockGroups)
                {
                    iterate.LastFall           = pOwner.GetElapsedTime();
                    iterate.HighestHeightValue = 0;
                }
            }


            PlayField.AnimateFrame();
            HandleActiveGroups(pOwner);

            if (GameOvered)
            {
                //For testing: write out the replay data as a sequence of little images.
                //ReplayData.WriteStateImages("T:\\ReplayData");
                Sounds.StopMusic();
                pOwner.FinalGameTime = DateTime.Now - pOwner.GameStartTime;
                GameHandler.Statistics.TotalGameTime = pOwner.FinalGameTime;
                NextAngleOffset = 0;
                pOwner.EnqueueAction(() => { pOwner.CurrentState = new GameOverGameState(this, GameHandler.GetGameOverStatistics(this, pOwner)); });
            }

            if (PlayField.BlockGroups.Count == 0 && !SpawnWait && !pOwner.CurrentState.GameProcSuspended && !NoTetrominoSpawn)
            {
                SpawnWait = true;
                pOwner.EnqueueAction
                    (() =>
                {
                    SpawnNewTetromino(pOwner);
                    SpawnWait = false;
                });
            }
        }
예제 #10
0
        private void AddParticles_Row(IStateOwner pOwner, BCRect RowBounds, int Lines = 1)
        {
            String UseText = RowClearText?[Lines] ?? $"{Lines}LINE";
            Func <BaseParticle, BCColor> TetrisColorFunc     = BaseParticle.GetRainbowColorFunc(pOwner);
            Func <BaseParticle, BCColor> SingleLineColorFunc = (o) =>
            {
                int    timebase      = 2000;
                double DarknessValue = (Math.Sin((float)pOwner.GetElapsedTime().Ticks / 2000) / 2) + 1;

                BCColor usecolor = (Color) new HSLColor(0, 120, DarknessValue * 120);

                return(usecolor);
            };

            Func <BaseParticle, BCColor> DoubleLineColorFunc = (o) =>
            {
                int    timebase      = 2000;
                double DarknessValue = Math.Sin((float)pOwner.GetElapsedTime().Ticks / 2000);

                BCColor usecolor = (Color) new HSLColor(75, 120, DarknessValue * 120);

                return(usecolor);
            };
            Func <BaseParticle, BCColor> TripleLineColorFunc = (o) =>
            {
                int    timebase      = 2000;
                double DarknessValue = Math.Sin((float)pOwner.GetElapsedTime().Ticks / 2000);

                BCColor usecolor = (Color) new HSLColor(150, 120, DarknessValue * 120);

                return(usecolor);
            };


            Func <BaseParticle, BCColor>[] LineFuncs = new Func <BaseParticle, BCColor>[]
            {
                SingleLineColorFunc, DoubleLineColorFunc, TripleLineColorFunc, TetrisColorFunc
            };
            //split the text into characters...

            char[] CharsToShow = UseText.ToCharArray();

            float XOffset = (int)((float)RowBounds.Width / 2 - ((float)CharsToShow.Length / 2)); //one character per block, ideally.

            List <CharParticle> MakeParticles = new List <CharParticle>();

            for (int x = 0; x < CharsToShow.Length; x++)
            {
                int          i            = x % CharsToShow.Length;
                CharParticle makeparticle = new CharParticle(new BCPoint(RowBounds.Left + XOffset + x, RowBounds.Top + RowBounds.Height / 2), new BCPoint(0, -0.05f), Color.Red, CharsToShow[i].ToString());
                makeparticle.TTL = 1500;
                //makeparticle.Decay = new BCPoint(0.5f, 0.5f);
                MakeParticles.Add(makeparticle);
                Lines = Lines > 4 ? Lines = 4:Lines;
                if (Lines >= 4)
                {
                    makeparticle.ColorCalculatorFunction = LineFuncs[Lines - 1];
                }

                else
                {
                    makeparticle.Color = new Color[] { Color.Red, Color.Green, Color.Yellow }[Lines - 1];
                }



                lock (_BaseState.TopParticles)
                {
                    _BaseState.TopParticles.AddRange(MakeParticles);
                }
            }
        }
예제 #11
0
        public FieldChangeResult ProcessFieldChange(GameplayGameState state, IStateOwner pOwner, Nomino Trigger)
        {
            if (state.PlayField.GetActiveBlockGroups().Count() > 0)
            {
                return new FieldChangeResult()
                       {
                           ScoreResult = 0
                       }
            }
            ;
            //here we would go through the field and handle where the blocks line up to more than the required critical mass.

            //Nomino's have two blocks- usually. But, we should account for more. This handler may be expanded for the Tetris2 handler, (if we ever bother to make one)
            //in any case we want to check all the positions of the trigger nomino and check for critical masses.
            int             MasterCount    = 0;
            bool            LevelCompleted = false;
            HashSet <Point> CriticalMasses = null;

            for (int y = 0; y < state.PlayField.RowCount; y++)
            {
                var currRow = state.PlayField.Contents[y];
                for (int x = 0; x < state.PlayField.ColCount; x++)
                {
                    if (state.PlayField.Contents[y][x] is LineSeriesPrimaryBlock)
                    {
                        MasterCount++;
                    }
                    if (state.PlayField.Contents[y][x] is LineSeriesBlock)
                    {
                        var foundmasses = FindCriticalMasses(state, pOwner, new Point(x, y));
                        foreach (var iterate in foundmasses)
                        {
                            if (CriticalMasses == null)
                            {
                                CriticalMasses = new HashSet <Point>(foundmasses);
                            }
                            else if (!CriticalMasses.Contains(iterate))
                            {
                                CriticalMasses.Add(iterate);
                            }
                        }
                    }
                }
            }
            PrimaryBlockCount = MasterCount;

            //if MasterCount is 0 then we completed this level.
            //if there are no primary blocks left, this level is now complete. We need a "Level complete" screen state with an overlay- we would switch to that state. It should
            //operate similar to the TemporaryInputPauseGameState in that we provide a routine to be called after the user opts to press a button to continue.
            if (MasterCount == 0)
            {
                LevelCompleted = true;
            }

            if (CriticalMasses != null && CriticalMasses.Any())
            {
                state.NoTetrominoSpawn = true;

                //process the critical masses.
                //first: we need to switch the blocks in question to "pop" them.
                //then we need to switch to a temporary state that allows them to display as "popped" for a moment or so, without processing drops or other actions.

                //after the delay expires, the state will then process the critical mass blocks, changing the underlying nomino to remove the deleted block, so that if only part of a nomino is cleared
                //the other parts are separated from it.

                //then it will check the full field again, changing unsupported field blocks into active groups and removing them from the field.
                // Blocks that are part of a nomino will be resurrected with the other blocks that are part of that nomino.)

                //check the field again and change unsupported field blocks back into active groups.

                HashSet <Nomino> MassNominoes = new HashSet <Nomino>();
                foreach (var iterate in CriticalMasses)
                {
                    var popItem = state.PlayField.Contents[iterate.Y][iterate.X];

                    if (popItem is LineSeriesBlock lsb)
                    {
                        lsb.Popping = true;
                        GeneratePopParticles(pOwner, state, new SKPointI(iterate.X, iterate.Y));
                        if (popItem.Owner != null)
                        {
                            state.PlayField.Theme.ApplyTheme(popItem.Owner, this, state.PlayField, NominoTheme.ThemeApplicationReason.Normal);
                        }
                        else
                        {
                            var Dummino = new Nomino()
                            {
                            };
                            Dummino.AddBlock(new Point[] { new Point(0, 0) }, popItem);
                            state.PlayField.Theme.ApplyTheme(Dummino, this, state.PlayField, NominoTheme.ThemeApplicationReason.Normal);
                        }
                    }
                    if (popItem.Owner != null)
                    {
                        popItem.Owner.RemoveBlock(popItem);
                    }
                    state.PlayField.HasChanged = true;
                }
                var originalstate = state;
                state.Sounds.PlaySound(pOwner.AudioThemeMan.BlockPop.Key);



                //need to determine a way to detect chains here, where we create an active block and then it results in another "pop".



                TemporaryInputPauseGameState tpause = new TemporaryInputPauseGameState(state, 1000, (owner) =>
                {
                    //first, remove the CriticalMasses altogether.
                    foreach (var iterate in CriticalMasses)
                    {
                        //clear out the cell at the appropriate position.
                        var popItem = state.PlayField.Contents[iterate.Y][iterate.X];
                        state.PlayField.Contents[iterate.Y][iterate.X] = null;
                        //now apply the theme to the specified location
                        if (popItem.Owner != null)
                        {
                            state.PlayField.Theme.ApplyTheme(popItem.Owner, this, state.PlayField, NominoTheme.ThemeApplicationReason.Normal);
                        }
                        else
                        {
                            //create a "dummy" nomino for the application of the "pop" theme animation.
                            var Dummino = new Nomino()
                            {
                            };
                            Dummino.AddBlock(new Point[] { new Point(0, 0) }, popItem);
                            state.PlayField.Theme.ApplyTheme(Dummino, this, state.PlayField, NominoTheme.ThemeApplicationReason.Normal);
                        }
                    }
                    //algorithm change: instead of going through the entire field, we'll go through all the critical masses.
                    //With Each one:
                    //check the block to the left, to the right, and above.


                    //next, go through the entire field.
                    List <NominoBlock> CheckedBlocks           = new List <NominoBlock>();
                    HashSet <Nomino> ResurrectNominos          = new HashSet <Nomino>();
                    HashSet <CascadingBlock> AddedBlockAlready = new HashSet <CascadingBlock>();

                    //keep track of the blocks we've examined already.
                    for (int row = 0; row < state.PlayField.RowCount; row++)
                    {
                        for (int column = 0; column < state.PlayField.ColCount; column++)
                        {
                            var currentblock = state.PlayField.Contents[row][column];
                            bool isPopping   = false;
                            if (currentblock != null)
                            {
                                if (currentblock is CascadingBlock cb)
                                {
                                    if (currentblock is LineSeriesBlock lsb)
                                    {
                                        isPopping = lsb.Popping;  //blocks that are popping shouldn't be resurrected.
                                    }
                                    if (row < 5)
                                    {
                                        ;
                                    }
                                    if (!isPopping && !cb.IsSupported(cb.Owner, state.PlayField) && !ResurrectNominos.Contains(cb.Owner) && !AddedBlockAlready.Contains(cb))
                                    {
                                        //resurrect this block and other blocks that are in the same nomino.
                                        //since we remove busted blocks from the nomino, we can take the Duomino this
                                        //block belongs to and add it back to the Active Groups, then remove all the blocks that are in the nomino from the field.
                                        foreach (var iterate in cb.Owner)
                                        {
                                            var useX = iterate.X + cb.Owner.X;
                                            var useY = iterate.Y + cb.Owner.Y;
                                            state.PlayField.Contents[useY][useX] = null;
                                        }

                                        Nomino resurrect       = cb.Owner;
                                        resurrect.Controllable = false;
                                        resurrect.FallSpeed    = 250;
                                        resurrect.InitialY     = resurrect.Y;
                                        resurrect.LastFall     = pOwner.GetElapsedTime();
                                        resurrect.MoveSound    = true;
                                        resurrect.PlaceSound   = false;
                                        resurrect.NoGhost      = true;
                                        ResurrectNominos.Add(resurrect);
                                        AddedBlockAlready.Add(cb);
                                    }
                                    //state.PlayField.AddBlockGroup(resurrect);
                                }
                            }

                            //now recursively process for the block to our left, the block to our right, and the block above. But only if that block is not part of the same nomino as currentblock or currentblock is null.
                        }
                    }



                    if (ResurrectNominos.Any())
                    {
                        HashSet <Point> AddedPoints = new HashSet <Point>();
                        foreach (var addresurrected in ResurrectNominos)
                        {
                            List <Point> AllPoints = (from b in addresurrected select new Point(b.X + addresurrected.X, b.Y + addresurrected.Y)).ToList();

                            if (!AllPoints.Any((w) => AddedPoints.Contains(w)))
                            {
                                state.PlayField.AddBlockGroup(addresurrected);
                                foreach (var point in AllPoints)
                                {
                                    AddedPoints.Add(point);
                                }
                            }
                        }
                    }


                    originalstate.NoTetrominoSpawn     = false;
                    originalstate.PlayField.HasChanged = true;
                    originalstate.SuspendFieldSet      = true;
                    //if we determined the level was completed earlier,
                    //we need to switch to the level completion state, and from there will need to resume starting with that new level.
                    if (LevelCompleted)
                    {
                        LevelCompleted = false;
                        HandleLevelComplete(owner, state);
                    }
                    else
                    {
                        owner.CurrentState = originalstate;
                    }
                });
                pOwner.CurrentState = tpause;
            }


            if (LevelCompleted)
            {
                LevelCompleted = false;
                var completionState = new DrMarioLevelCompleteState(state, () => SetupNextLevel(state, pOwner));
                pOwner.CurrentState = completionState;
            }

            //Remove those blocks from the field.
            //then, reprocess the field: find any unsupported blocks, and generate new ActiveBlockGroups for them. Add them to the list of active block groups. Set the fallspeed appropriately.
            //if we found any unsupported blocks groups, change the state to the GroupFallState (not defined) which is a composite state that doesn't allow input, and waits for all active block groups to come to rest before
            //continuing.

            //once all block groups come to rest, ProcessFieldChange will be called again.

            //Note: for visual flair eventually we'll want to have a temporary state which does nothing but allow the blocks being destroyed to be indicated for perhaps 250ms, before advancing to the state where blocks
            //will fall



            return(new FieldChangeResult()
            {
                ScoreResult = 5
            });
        }

        const int ParticlesPerPop        = 400;
예제 #12
0
        public FieldChangeResult ProcessFieldChange(GameplayGameState state, IStateOwner pOwner, Nomino Trigger)
        {
            var HotLines                    = new List <HotLine>();
            FieldChangeResult FCR           = new FieldChangeResult();
            int           rowsfound         = 0;
            List <int>    CompletedRows     = new List <int>();
            List <Action> AfterClearActions = new List <Action>();
            var           PlayField         = state.PlayField;
            var           Sounds            = state.Sounds;
            var           GameOptions       = state.GameOptions;

            //checks the field contents for lines. If there are lines found, they are removed, and all rows above it are shifted down.
            for (int r = 0; r < PlayField.RowCount; r++)
            {
                if (PlayField.Contents[r].All((d) => d != null))
                {
                    Debug.Print("Found completed row at row " + r);
                    if (PlayField.Flags.HasFlag(TetrisField.GameFlags.Flags_Hotline) && PlayField.HotLines.ContainsKey(r))
                    {
                        Debug.Print("Found hotline row at row " + r);
                        HotLines.Add(PlayField.HotLines[r]);
                    }
                    CompletedRows.Add(r);
                    rowsfound++;
                    //enqueue an action to perform the clear. We'll be replacing the current state with a clear action state, so this should execute AFTER that state returns control.
                    var r1 = r;
                    AfterClearActions.Add
                        (() =>
                    {
                        for (int g = r1; g > 0; g--)
                        {
                            Debug.Print("Moving row " + (g - 1).ToString() + " to row " + g);

                            for (int i = 0; i < PlayField.ColCount; i++)
                            {
                                PlayField.Contents[g][i] = PlayField.Contents[g - 1][i];
                            }
                        }
                    });
                }
            }
            AfterClearActions.Add(() => { PlayField.HasChanged = true; });

            long PreviousLineCount = Statistics.LineCount;

            if (Trigger != null)
            {
                Statistics.AddLineCount(Trigger.GetType(), rowsfound);
            }

            if ((PreviousLineCount % 10) > (Statistics.LineCount % 10))
            {
                state.InvokePlayFieldLevelChanged(state, new TetrisField.LevelChangeEventArgs((int)Statistics.LineCount / 10));
                Statistics.SetLevelTime(pOwner.GetElapsedTime());

                state.Sounds.PlaySound(pOwner.AudioThemeMan.LevelUp.Key, pOwner.Settings.std.EffectVolume);
                PlayField.SetFieldColors(this);
                state.f_RedrawStatusBitmap = true;
            }

            if (rowsfound > 0 && rowsfound < 4)
            {
                Sounds.PlaySound(pOwner.AudioThemeMan.ClearLine.Key, pOwner.Settings.std.EffectVolume * 2);
            }
            else if (rowsfound == 4)
            {
                Sounds.PlaySound(pOwner.AudioThemeMan.ClearTetris.Key, pOwner.Settings.std.EffectVolume * 2);
            }


            int topmost = PlayField.RowCount;

            //find the topmost row with any blocks.
            for (int i = 0; i < PlayField.RowCount; i++)
            {
                if (PlayField.Contents[i].Any((w) => w != null))
                {
                    topmost = i;
                    break;
                }
            }

            topmost = topmost + rowsfound; //subtract the rows that were cleared to get an accurate measurement.
            if (topmost < 9)
            {
                if (state.currenttempo == 1)
                {
                    state.currenttempo = 68;
                    if (GameOptions.MusicRestartsOnTempoChange)
                    {
                        if (GameOptions.MusicEnabled)
                        {
                            Sounds.PlayMusic(pOwner.AudioThemeMan.BackgroundMusic.Key, pOwner.Settings.std.MusicVolume, true);
                        }
                    }

                    var grabbed = Sounds.GetPlayingMusic_Active();
                    if (grabbed != null)
                    {
                        Sounds.GetPlayingMusic_Active().Tempo = 75f;
                    }
                }
            }
            else
            {
                if (state.currenttempo != 1)
                {
                    state.currenttempo = 1;
                    if (GameOptions.MusicRestartsOnTempoChange)
                    {
                        if (GameOptions.MusicEnabled)
                        {
                            if (pOwner.Settings.std.MusicOption == "<RANDOM>")
                            {
                                Sounds.PlayMusic(pOwner.AudioThemeMan.BackgroundMusic.Key, pOwner.Settings.std.MusicVolume, true);
                            }
                            else
                            {
                                Sounds.PlayMusic(pOwner.Settings.std.MusicOption, pOwner.Settings.std.MusicVolume, true);
                            }
                        }
                    }
                    var grabbed = Sounds.GetPlayingMusic_Active();
                    if (grabbed != null)
                    {
                        grabbed.Tempo = 1f;
                    }
                }
            }

            PlayField.HasChanged |= rowsfound > 0;

            if (rowsfound > 0)
            {
                var ClearState = new FieldLineActionGameState(state, CompletedRows.ToArray(), AfterClearActions);
                ClearState.ClearStyle = TetrisGame.Choose((FieldLineActionGameState.LineClearStyle[])(Enum.GetValues(typeof(FieldLineActionGameState.LineClearStyle))));

                pOwner.CurrentState = ClearState;
            }

            //if(rowsfound > 0) pOwner.CurrentState = new FieldLineActionDissolve(this,CompletedRows.ToArray(),AfterClearActions);
            var scoreresult = GetScore(rowsfound, HotLines, state, pOwner, Trigger);

            pOwner.Feedback(0.9f * (float)scoreresult, scoreresult * 250);
            FCR.ScoreResult = rowsfound;
            return(FCR);
        }