/// <summary> /// Generate a <see cref="IReadOnlyList{T}" /> of <see cref="Tetrimino" />s that fills the given template. /// </summary> /// <param name="template"> /// <para> /// A two-dimensional <see cref="Array" /> of <see cref="Block" />s. /// </para> /// <para> /// This array should only contain blocks with <see cref="TetriminoKind.AvailableToFill" /> or /// <see cref="TetriminoKind.UnavailableToFill" />. /// The former one indicates that this block is fillable with <see cref="Tetrimino" /> while the latter one has the /// opposite meaning. /// </para> /// </param> /// <param name="rand">A random number generator</param> /// <returns> /// When this method returns, contains a <see cref="IReadOnlyList{T}" /> of <see cref="Tetrimino" />s of settled /// (placed) tetriminos, or /// an empty one when fails to generate. /// </returns> private static IReadOnlyList <Tetrimino> GetPossibleTetriminoPattern(Block[,] template, Random rand) { // This is where we do our tracking jobs - to track which blocks are available to fill var workspace = template; // This is where we place our settled tetriminos - we use Stack because we may need to go back a few steps if a plan fails // The initial capacity is the estimated numbers of tetriminos var settledTetrimino = new Stack <Tetrimino>(GeneratorHelper.TotalAvailableBlocks / 4); // This is where we place our information to generate randomized tetriminos var pendingTetriminoKinds = new Stack <Stack <KindDirectionsPair> >(); // These are cursors. Before each iteration var rewindingRequired = false; /* Here in the main loop there are many things we need to do: * 0. All cursors are set to null when beginning this loop. * * 1. Check if there are any more unplaced blocks, if so, we will record its coordinates. * Otherwise we will just return the placed Stack as there are no more Tetriminos to * place. * * 2. Generate a randomly-ordered list of TetriminoKind and iterate all over them. Check * if there are any possible placeable Tetrimino configurations and push it to the stack. * The cursors are set to null for a new iteration from Step 1. Do not forget to set its Blocks * to its suitable FilledBy state in the workspace. * * 3. If there are no possible placeable Tetriminos then we need to 'rewind' the stack. * We pop back one Tetrimino and its corresponding stack of TetriminoKind, choose the * next one and then go back to Step 2. Do not forget to erase any settled block state of * the popped Tetrimino in the workspace. */ while (true) { // Step 1. // First we need to find the position of the first block in the workspace int firstBlockRow; int firstBlockCol; (firstBlockCol, firstBlockRow) = GetFirstAvailableBlockCoordination(workspace); if (!(firstBlockCol >= 0 && firstBlockRow >= 0)) { // There are no more blocks to fill. Returning. return(settledTetrimino.ToArray()); } // Step 2. // Now we find an empty block. Generate a new Tetrimino using randomized stack of TetriminoKind. Stack <KindDirectionsPair> currentTetriminoKindDirectionsPairStack; if (!rewindingRequired) { // This could mean this is the first run or the last iteration has succeeded in placing a block. currentTetriminoKindDirectionsPairStack = new Stack <KindDirectionsPair>(new[] { new KindDirectionsPair(TetriminoKind.Cubic, rand), new KindDirectionsPair(TetriminoKind.Linear, rand), new KindDirectionsPair(TetriminoKind.LShapedCis, rand), new KindDirectionsPair(TetriminoKind.LShapedTrans, rand), new KindDirectionsPair(TetriminoKind.TeeShaped, rand), new KindDirectionsPair(TetriminoKind.ZigZagCis, rand), new KindDirectionsPair(TetriminoKind.ZigZagTrans, rand) }.OrderBy(x => rand.Next())); } else { // In this case we need to rewind the stack for one. if (settledTetrimino.Count == 0) { // No way out, return! return(settledTetrimino.ToArray()); } currentTetriminoKindDirectionsPairStack = pendingTetriminoKinds.Pop(); // We still have chances... var lastTetrimino = settledTetrimino.Pop(); foreach (var block in lastTetrimino.Blocks) { workspace[block.Position.Y, block.Position.X].FilledBy = TetriminoKind.AvailableToFill; } } // Anyway, we need to obtain a new TetriminoKind and test over all // possible directions. var solutionFound = false; while (currentTetriminoKindDirectionsPairStack.Count > 0) { var currentPair = currentTetriminoKindDirectionsPairStack.Pop(); while (currentPair.PendingDirections.Count > 0) { var direction = currentPair.PendingDirections.Pop(); var tetrimino = Tetrimino.ByFirstBlockPosition(currentPair.TetriminoKind, new Position(firstBlockCol, firstBlockRow), direction ); if (!tetrimino.Blocks.Any(CheckBlockCollision)) { // Now that we found a solution. Push these to the stack and go to the next iteration. settledTetrimino.Push(tetrimino); pendingTetriminoKinds.Push(currentTetriminoKindDirectionsPairStack); foreach (var block in tetrimino.Blocks) { block.AtomicNumber = workspace[block.Position.Y, block.Position.X].AtomicNumber; workspace[block.Position.Y, block.Position.X].FilledBy = block.FilledBy; } currentTetriminoKindDirectionsPairStack = null; solutionFound = true; rewindingRequired = false; break; } // Oops, collision found. // We will continue to test over the rest possible combinations. } if (solutionFound) { break; } } // Step 3. if (!solutionFound) { // We have run out of options. We need to pop out the previous one. rewindingRequired = true; } } // True if the block will collide. bool CheckBlockCollision(Block block) { var nRow = block.Position.Y; var nCol = block.Position.X; // Left, right or bottom border collision if (nCol < 0 || nCol >= workspace.GetLength(1) || nRow >= workspace.GetLength(0)) { return(true); } // Block-block collision return(workspace[nRow, nCol].FilledBy != TetriminoKind.AvailableToFill); } }