Пример #1
0
        // Yeah, i know, it's a method 700+ lines big...
        public override Puzzle GeneratePanel(int?seed = null)
        {
            // Use provided seed or generate random one -- a number from 0 to 1 billion minus one (9 digits)
            if (seed == null)
            {
                seed = seedGenerator.Next(1000000000);
            }
            Random rnd = new Random(seed.Value);

            Puzzle         panel    = null;
            SymmetryPuzzle symPanel = null;

            //var panelPalette = ColorPalettesLibrary.Palettes[0];
            var   panelPalette = ColorPalettesLibrary.Palettes[rnd.Next(ColorPalettesLibrary.Size)];
            Color mainColor = panelPalette.MainLineColor, mirrorColor = panelPalette.MirrorLineColor;

            // Generate random panel size
            double num         = rnd.NextDouble();
            int    panelHeight = num > 0.05 ? (num > 0.3 ? (num > 0.75 ? (num > 0.9 ? (num > 0.95 ? 7 : 6) : 5) : 4) : 3) : 2;

            num = rnd.NextDouble();
            int  panelWidth        = num > 0.05 ? (num > 0.3 ? (num > 0.75 ? (num > 0.9 ? (num > 0.95 ? 7 : 6) : 5) : 4) : 3) : 2;
            bool symmetry          = false;
            bool ySymmetry         = false;
            bool mirrorTransparent = false;

            // Panel can be symmetric only if it's big enough
            #region Symmetry and panel creation
            if (panelWidth >= 4 && panelHeight >= 4 && rnd.NextDouble() > 0.3)
            {
                symmetry = true;
                if (rnd.NextDouble() > 0.5)
                {
                    ySymmetry = true;
                }

                if (rnd.NextDouble() > 0.84)
                {
                    mirrorTransparent = true;
                }
            }

            panel = symmetry
                ? new SymmetryPuzzle(panelWidth, panelHeight, ySymmetry, mirrorTransparent, panelPalette.MainLineColor, panelPalette.MirrorLineColor, panelPalette.BackgroundColor, panelPalette.WallsColor, panelPalette.ButtonsColor, seed.Value)
                : new Puzzle(panelWidth, panelHeight, panelPalette.SingleLineColor, panelPalette.BackgroundColor, panelPalette.WallsColor, panelPalette.ButtonsColor, seed.Value);

            if (symmetry)
            {
                symPanel = panel as SymmetryPuzzle;
            }
            #endregion

            // Amount of nodes in one row (1 more than width in blocks)
            int width1 = panelWidth + 1;
            // ID of the last node
            int maxNodeID = width1 * (panelHeight + 1) - 1;

            // Generate start points
            #region Start points
            List <int> startPoints = new List <int>();
            num = rnd.NextDouble();
            int startPointsAmount = symmetry
                ? num > 0.8 ? (num > 0.97 ? 3 : 2) : 1
                : num > 0.7 ? (num > 0.89 ? (num > 0.98 ? 4 : 3) : 2) : 1;
            for (int i = 0; i < startPointsAmount; i++)
            {
                int  index;
                bool acceptable;
                do
                {
                    index = rnd.Next(maxNodeID + 1);
                    // Node should not be already marked as start node
                    acceptable = !startPoints.Contains(index);
                    // Node should not be on the line of symmetry
                    acceptable = acceptable && !(symmetry && index == GetMirrorNodeID(index));
                }while (!acceptable);
                startPoints.Add(index);
                if (symmetry)
                {
                    startPoints.Add(GetMirrorNodeID(index));
                }
            }

            foreach (int start in startPoints)
            {
                panel.Nodes[start].SetState(NodeState.Start);
            }
            #endregion

            // Generate end points
            #region End points
            List <int> endPoints = new List <int>();
            num = rnd.NextDouble();
            int endPointsAmount = symmetry
                ? num > 0.88 ? 2 : 1
                : num > 0.75 ? (num > 0.91 ? (num > 0.99 ? 4 : 3) : 2) : 1;
            List <int> borderNodes = GetBorderNodes();
            for (int i = 0; i < endPointsAmount; i++)
            {
                int  index;
                bool acceptable;
                do
                {
                    index = rnd.Next(borderNodes.Count);
                    // Node should not be already marked as start node or end node
                    acceptable = !startPoints.Contains(borderNodes[index]);
                    acceptable = acceptable && !endPoints.Contains(borderNodes[index]);
                    // Node should not be on the line of symmetry
                    acceptable = acceptable && !(symmetry && borderNodes[index] == GetMirrorNodeID(borderNodes[index]));
                }while (!acceptable);
                endPoints.Add(borderNodes[index]);
                if (symmetry)
                {
                    endPoints.Add(GetMirrorNodeID(borderNodes[index]));
                }
            }

            foreach (int end in endPoints)
            {
                panel.Nodes[end].SetState(NodeState.Exit);
            }
            #endregion

            int startPoint = startPoints[rnd.Next(startPoints.Count)];
            int endPoint   = endPoints[rnd.Next(endPoints.Count)];

            // If panel is X-Symmetric, we can't cross the line of symmetry
            // Therefore we should check that our start and end nodes are on the same side
            if (symmetry && !ySymmetry)
            {
                float lineOfSymmetry = panelWidth / 2f;
                int   startNodeX     = startPoint % width1;
                int   endNodeX       = endPoint % width1;
                // If the start node and the end node are lying on the different sides across the line of symmetry => swap the end node to the mirror one
                if (Math.Sign(lineOfSymmetry - startNodeX) != Math.Sign(lineOfSymmetry - endNodeX))
                {
                    endPoint = GetMirrorNodeID(endPoint);
                }
            }

            // Generate random line on the panel
            List <int> randomSolution = GetRandomSolutionLine(startPoint, endPoint);
            panel.SetSolution(randomSolution);

            var allSolutionNodes = panel.SolutionNodes.ToList();
            var allSolutionEdges = panel.SolutionEdges.ToList();

            // Generate amount of hexagon rules on the panel
            #region Hexagon rule
            num = rnd.NextDouble();
            int amountOfHexagonMarks = num > 0.6 ? (num > 0.65 ? (num > 0.85 ? (num > 0.92 ? (num > 0.97 ? 6 : 4) : 3) : 2) : 1) : 0;
            // Can't be more than 40% of total amount of nodes/edges
            amountOfHexagonMarks = Math.Min(amountOfHexagonMarks, (int)((allSolutionNodes.Count + allSolutionEdges.Count) * 0.4f));
            int amountOfMarkedNodes = Math.Min((int)(amountOfHexagonMarks * (0.3 + 0.7 * rnd.NextDouble())), (int)(allSolutionNodes.Count * 0.4f));
            int amountOfMarkedEdges = Math.Min(amountOfHexagonMarks - amountOfMarkedNodes, (int)(allSolutionEdges.Count * 0.4f));

            // Spawn hexagons on nodes
            #region Marked nodes
            var mainsolutionNodes = symPanel?.MainSolutionNodes;
            var mirSolutionNodes  = symPanel?.MirrorSolutionNodes;
            for (int i = 0; i < amountOfMarkedNodes; i++)
            {
                var acceptableNodes = allSolutionNodes.Where(x => x.State == NodeState.Normal).ToList();
                if (acceptableNodes.Count > 0)
                {
                    int index = rnd.Next(acceptableNodes.Count);
                    if (symmetry)
                    {
                        bool applyColor = rnd.NextDouble() > 0.65;
                        if (applyColor)
                        {
                            if (mainsolutionNodes.Contains(acceptableNodes[index]))
                            {
                                acceptableNodes[index].SetStateAndColor(NodeState.Marked, mainColor);
                            }
                            else
                            {
                                acceptableNodes[index].SetStateAndColor(NodeState.Marked, mirrorColor);
                            }
                        }
                        else
                        {
                            acceptableNodes[index].SetState(NodeState.Marked);
                        }
                    }
                    else
                    {
                        acceptableNodes[index].SetState(NodeState.Marked);
                    }
                }
            }
            #endregion

            // Spawn hexagons on edges
            #region Marked edges
            var mainsolutionEdges = symPanel?.MainSolutionEdges;
            var mirSolutionEdges  = symPanel?.MirrorSolutionEdges;
            for (int i = 0; i < amountOfMarkedEdges; i++)
            {
                int index;
                // Edge should not be Marked or Broken and both its nodes should not be marked too
                var acceptableEdges = allSolutionEdges.Where(x => x.State == EdgeState.Normal && x.NodeA.State == NodeState.Normal && x.NodeB.State == NodeState.Normal).ToList();
                if (acceptableEdges.Count == 0)
                {
                    break;
                }
                index = rnd.Next(acceptableEdges.Count);
                if (symmetry)
                {
                    bool applyColor = rnd.NextDouble() > 0.65;
                    if (applyColor)
                    {
                        if (mainsolutionEdges.Contains(acceptableEdges[index]))
                        {
                            acceptableEdges[index].SetStateAndColor(EdgeState.Marked, mainColor);
                        }
                        else
                        {
                            acceptableEdges[index].SetStateAndColor(EdgeState.Marked, mirrorColor);
                        }
                    }
                    else
                    {
                        acceptableEdges[index].SetState(EdgeState.Marked);
                    }
                }
                else
                {
                    acceptableEdges[index].SetState(EdgeState.Marked);
                }
            }
            #endregion
            #endregion

            // Generate amount of broken edges
            #region Broken edges
            num = rnd.NextDouble();
            int amountOfBrokenEdges;
            // 0 for small panels and 0..6 for bigger panels
            amountOfBrokenEdges = panelWidth >= 3 && panelHeight >= 3
                                ? panelWidth >= 5 || panelHeight >= 5
                ? num > 0.3 ? (num > 0.45 ? (num > 0.65 ? (num > 0.77 ? (num > 0.87 ? (num > 0.94 ? 6 : 5) : 4) : 3) : 2) : 1) : 0
                : num > 0.5 ? (num > 0.7 ? (num > 0.85 ? (num > 0.95 ? 4 : 3) : 2) : 1) : 0
                : 0;

            // Spawn broken edges
            for (int i = 0; i < amountOfBrokenEdges; i++)
            {
                // Edge should not be Marked or already Broken
                var allBreakableEdges = panel.Edges.Except(panel.SolutionEdges).Where(x => x.State == EdgeState.Normal).ToList();
                int index             = rnd.Next(allBreakableEdges.Count);
                allBreakableEdges[index].SetState(EdgeState.Broken);
            }
            #endregion

            float usedRuleTypesCount = 0;
            if (amountOfMarkedNodes + amountOfMarkedEdges > 0)
            {
                usedRuleTypesCount++;
            }
            if (amountOfBrokenEdges > 0)
            {
                usedRuleTypesCount++;
            }

            List <Color> colors = new List <Color>();
            num = rnd.NextDouble();
            int amountOfColors = num > 0.6 ? (num > 0.8 ? (num > 0.94 ? 5 : 4) : 3) : 2;
            for (int i = 0; i < amountOfColors; i++)
            {
                int  index      = -1;
                bool acceptable = false;
                while (!acceptable)
                {
                    index      = rnd.Next(colorPalette.Count);
                    acceptable = !colors.Contains(colorPalette[index]);
                }
                colors.Add(colorPalette[index]);
            }

            List <Sector> sectors = panel.GetSectors();

            // Colored square rule
            #region Colored Squares
            num = rnd.NextDouble();
            int amountOfColoredSquares = num > 0.5 ? (num > 0.55 ? (num > 0.62 ? (num > 76 ? (num > 86 ? (num > 92 ? (num > 96 ? 8 : 7) : 6) : 5) : 4) : 3) : 2) : 0;
            if (amountOfColoredSquares > 0)
            {
                List <(int amount, Color color)> squaresInSector = new List <(int, Color)>();
                int squaresLeft = amountOfColoredSquares;
                int colorsUsed  = 0;
                for (int i = 0; i < sectors.Count; i++)
                {
                    int amount = squaresLeft > 0
                        ? i < sectors.Count - 1
                            ? Math.Min(rnd.Next(1, squaresLeft), sectors[i].Blocks.Count)
                            : Math.Min(squaresLeft, sectors[i].Blocks.Count)
                        : 0;
                    squaresLeft -= amount;

                    Color col = colors.Count == colorsUsed
                        ? colors[rnd.Next(colors.Count)]
                        : colors[colorsUsed++];

                    squaresInSector.Add((amount, col));
                }

                // Spawn colored squares
                for (int i = 0; i < sectors.Count; i++)
                {
                    for (int j = 0; j < squaresInSector[i].amount; j++)
                    {
                        int  index;
                        bool acceptable;
                        do
                        {
                            index      = rnd.Next(sectors[i].Blocks.Count);
                            acceptable = sectors[i].Blocks[index].Rule == null;
                        }while (!acceptable);
                        sectors[i].Blocks[index].Rule = new ColoredSquareRule(squaresInSector[i].color);
                    }
                }
            }
            #endregion
            usedRuleTypesCount += amountOfColoredSquares > 0 ? 1 : 0;


            //  Sun rules
            for (int i = 0; i < sectors.Count; i++)
            {
                var freeBlocks    = sectors[i].Blocks.Where(x => x.Rule == null).ToList();
                var coloredBlocks = sectors[i].Blocks.Where(x => x.Rule is IColorable).ToList();

                if (((freeBlocks.Count > 0 && coloredBlocks.Count == 1) || (freeBlocks.Count > 1)) && rnd.NextDouble() > 0.5)
                {
                    Color?blocksColor = null;
                    if (coloredBlocks.Count > 0)
                    {
                        blocksColor = (coloredBlocks[0].Rule as IColorable).Color;
                    }

                    if (coloredBlocks.Count == 1)
                    {
                        int index = rnd.Next(freeBlocks.Count);
                        freeBlocks[index].Rule = new SunPairRule(blocksColor ?? colors[rnd.Next(colors.Count)]);
                    }
                    else
                    {
                        Color col;
                        if (coloredBlocks.Count == 0)
                        {
                            col = colors[rnd.Next(colors.Count)];
                        }
                        else
                        {
                            do
                            {
                                col = colors[rnd.Next(colors.Count)];
                            }while (col == blocksColor);
                        }

                        int indexA = rnd.Next(freeBlocks.Count);
                        int indexB;
                        do
                        {
                            indexB = rnd.Next(freeBlocks.Count);
                        }while (indexB == indexA);

                        freeBlocks[indexA].Rule = new SunPairRule(col);
                        freeBlocks[indexB].Rule = new SunPairRule(col);
                    }
                }
            }

            // Triangle rules
            #region Triangles
            num = rnd.NextDouble();
            int amountOfTriangles = usedRuleTypesCount <= 2
                ? num > 0.5 ? (num > 0.7 ? (num > 0.9 ? 5 : 4) : 3) : 2
                : num > 0.5 ? (num > 0.75 ? (num > 95 ? 3 : 2) : 1) : 0;

            for (int i = 0; i < amountOfTriangles; i++)
            {
                // Get all blocks near solution line without rules
                var acceptableBlocks = panel.Blocks.Where(x => x.Rule == null && x.Edges.Intersect(allSolutionEdges).Count() > 0).ToList();
                if (acceptableBlocks.Count == 0)
                {
                    break;
                }

                int index = rnd.Next(acceptableBlocks.Count);
                acceptableBlocks[index].Rule = new TriangleRule(acceptableBlocks[index].Edges.Intersect(allSolutionEdges).Count());
            }
            #endregion

            // Tetris rules
            int totalTetrisRulesAdded = 0;
            for (int i = 0; i < sectors.Count; i++)
            {
                int totalBlocks = sectors[i].Blocks.Count;
                var freeBlocks  = GetFreeSectorBlocks();

                // If all tetrominos will be this size, we still have enough free blocks to fit them in
                // Tetrominos can grow larger than minimum size though
                // For empty sector the number is 1
                // But we don't really want a large sector with five or more tetris rules, even if we have five or more free blocks, so here comes the limit
                int tetrominoMinSize          = (int)Math.Ceiling((double)totalBlocks / Math.Min(4, freeBlocks.Count));
                int tetrominoTrueMinSize      = (int)Math.Ceiling((double)totalBlocks / freeBlocks.Count);
                int exprectedTetrominosAmount = (int)Math.Ceiling((double)totalBlocks / tetrominoMinSize);

                // Create tetrominos only if sector is good sized and expected size of one tetromino is not too big
                if (totalTetrisRulesAdded < 5 && totalBlocks > 1 && totalBlocks < 14 && tetrominoMinSize <= 4 && rnd.NextDouble() > 0.2)
                {
                    List <Block> unusedBlocks = new List <Block>(sectors[i].Blocks);

                    // By default we are not gonna create subtractive shapes
                    int subtractiveQuota         = 0;
                    int subtractiveTetrominoSize = -1;

                    // If we have plenty of free space, we can spawn subtractive shape
                    if (tetrominoTrueMinSize <= 2 && freeBlocks.Count > 1 && totalBlocks < 18 && exprectedTetrominosAmount <= 3)
                    {
                        subtractiveQuota++;
                        // Compensate subtractive tetromino with bigger regular ones
                        if (tetrominoMinSize != tetrominoTrueMinSize)
                        {
                            tetrominoMinSize++;
                        }
                        // Average size of subtractive shape should be about the amount of freed space by the increase of tetrominoMinSize
                        // Every block yields one bit of space, but also we need one free block to store subtractive tetromino, so it's (Count - 1)
                        // But also we wouldn't want a shape much bigger than 4 bits
                        int subtractiveTetrominoAvgSize = Math.Min(4, freeBlocks.Count - 1);
                        // Exact size of subtractive tetromino
                        subtractiveTetrominoSize = MathHelper.Clamp(rnd.Next(subtractiveTetrominoAvgSize - 1, subtractiveTetrominoAvgSize + 2), 1, 5);
                    }

                    // Loop untill we use all sector blocks
                    while (unusedBlocks.Count > 0)
                    {
                        // Just a precaution. May happen, but extremely rarely, when RNGesus is really mad at us
                        if (freeBlocks.Count == 0)
                        {
                            // Undo all spawned tetris rules in sector
                            var tetrisBlocks = sectors[i].Blocks.Where(x => x.Rule is TetrisRule);
                            foreach (var block in tetrisBlocks)
                            {
                                block.Rule = null;
                            }
                            break;
                        }

                        List <Block> tetrominoBlocks = new List <Block>();

                        // Decide if new tetromino will be subtractive
                        bool isSubtractiveTetromino = false;
                        if (subtractiveQuota > 0)
                        {
                            isSubtractiveTetromino = true;
                            subtractiveQuota--;
                        }

                        CreateTetrominoShape(isSubtractiveTetromino);

                        // Turn tetromino blocks into bool[,] shape
                        int tetroMinX = tetrominoBlocks.Min(x => x.X);
                        int tetroMaxX = tetrominoBlocks.Max(x => x.X);
                        int tetroMinY = tetrominoBlocks.Min(x => x.Y);
                        int tetroMaxY = tetrominoBlocks.Max(x => x.Y);
                        bool[,] shape = new bool[tetroMaxX - tetroMinX + 1, tetroMaxY - tetroMinY + 1];
                        foreach (Block block in tetrominoBlocks)
                        {
                            shape[block.X - tetroMinX, block.Y - tetroMinY] = true;
                        }

                        // Randomly make shape rotatable
                        bool rotatable = false;
                        if (rnd.NextDouble() > 0.85 && !TetrisRotatableRule.AreShapesIdentical(shape, TetrisRotatableRule.RotateShapeCW(shape)))
                        {
                            rotatable = true;
                        }

                        // Choose random free block to plant the new tetris rule
                        Block tetrisBlock = freeBlocks[rnd.Next(freeBlocks.Count)];
                        if (rotatable)
                        {
                            tetrisBlock.Rule = new TetrisRotatableRule(shape, isSubtractiveTetromino);
                        }
                        else
                        {
                            tetrisBlock.Rule = new TetrisRule(shape, isSubtractiveTetromino);
                        }
                        totalTetrisRulesAdded++;

                        // Update free blocks
                        freeBlocks = GetFreeSectorBlocks();

                        // Repeat untill all unused blocks will be used in tetris rules
                        continue;

                        // == Inner methods ==
                        void AddBlockToTetromino(Block block, bool isSubtractive = false)
                        {
                            tetrominoBlocks.Add(block);
                            if (isSubtractive)
                            {
                                unusedBlocks.Add(block);
                            }
                            else
                            {
                                unusedBlocks.Remove(block);
                            }
                        }

                        void CreateTetrominoShape(bool isSubtractive)
                        {
                            if (isSubtractive)
                            {
                                // Take random unused block as first tetromino
                                AddBlockToTetromino(unusedBlocks[rnd.Next(unusedBlocks.Count)], true);
                            }
                            else
                            {
                                // Take first unused block as the first bit of tetromino
                                AddBlockToTetromino(unusedBlocks[0]);
                            }

                            // Do minimum amount of cycles and then shape has some chance to grow bigger (the smaller shape the bigger chance; for 4-sized shape it's 10%)
                            // But if shape is subtractive then do exact amount of cycles
                            for (int k = 1; isSubtractive?k < subtractiveTetrominoSize : (k < tetrominoMinSize || rnd.Next(6 + tetrominoMinSize) == 0); k++)
                            {
                                // Get all blocks that are next to current shape
                                // For subtractive shape use not only unused blocks, but all of them
                                var sourceBlocks = isSubtractive ? panel.Blocks : unusedBlocks;
                                var neighbours   = sourceBlocks.Where(x => x.Edges.Intersect(tetrominoBlocks.SelectMany(z => z.Edges)).Count() > 0 && !tetrominoBlocks.Contains(x)).ToList();
                                if (neighbours.Count == 0)
                                {
                                    break;
                                }

                                // Take one random neighbour block
                                Block neighbour = null;
                                bool  acceptable;
                                int   retries = 8;
                                do
                                {
                                    neighbour = neighbours[rnd.Next(neighbours.Count)];
                                    int minX = tetrominoBlocks.Min(x => x.X);
                                    int maxX = tetrominoBlocks.Max(x => x.X);
                                    int minY = tetrominoBlocks.Min(x => x.Y);
                                    int maxY = tetrominoBlocks.Max(x => x.Y);
                                    // This neighbour block is not acceptable if it would make tetromino bigger than 4x4 bits
                                    acceptable = !(maxX - minX + 1 >= 4 && (neighbour.X <minX || neighbour.X> maxX)) || (maxY - minY + 1 >= 4 && (neighbour.Y <minY || neighbour.Y> maxY));
                                    retries--;
                                }while (!acceptable && retries > 0);

                                // If we used all retries and couldn't find acceptable block to add to tetromino,
                                // then don't, just finalize current tetromino and move on to next one
                                if (!acceptable)
                                {
                                    break;
                                }

                                AddBlockToTetromino(neighbour, isSubtractive);
                            }
                        }
                    }
                }

                // Method to update free blocks
                List <Block> GetFreeSectorBlocks() => sectors[i].Blocks.Where(x => x.Rule == null).ToList();
            }

            // Elimination rules
            #region Elimination
            num = rnd.NextDouble();
            int eliminatorsAmount = num > 0.8 ? (num > 0.97 ? 2 : 1) : 0;
            for (int i = 0; i < eliminatorsAmount; i++)
            {
                // Get sectors that have 1 or more free blocks
                var acceptableSectors = sectors.Where(x => x.Blocks.Where(z => z.Rule == null).Count() >= 1).ToList();
                if (acceptableSectors.Count == 0)
                {
                    break;
                }

                Sector sec             = acceptableSectors[rnd.Next(acceptableSectors.Count)];
                int    freeBlocksCount = sec.Blocks.Where(z => z.Rule == null).Count();
                bool   isHexagon       = freeBlocksCount > 1
                    ? rnd.NextDouble() > 0.9
                    : true;

                bool coloredEliminationSpawned = false;
                // Colored eliminator can spawn alongside the sun block and only if sector has exactly one colored square without suns of the same color
                // Sun will be colored like the square and eliminator will have different color
                if (freeBlocksCount >= 3 && colors.Count > 1 /* && rnd.NextDouble() > 0.5*/)
                {
                    var secBlocks       = sec.Blocks.Where(x => x.Rule == null).ToList();
                    var squareSecBlocks = secBlocks.Where(x => x.Rule is ColoredSquareRule).ToList();
                    if (squareSecBlocks.Count == 1)
                    {
                        Color squareCol = (squareSecBlocks[0].Rule as ColoredSquareRule).Color.Value;
                        if (secBlocks.Where(x => x.Rule is IColorable icol && icol.Color == squareCol).Count() == 1)
                        {
                            int indexA = rnd.Next(secBlocks.Count);
                            int indexB;
                            do
                            {
                                indexB = rnd.Next(secBlocks.Count);
                            }while (indexB == indexA);

                            Color elimCol;
                            do
                            {
                                elimCol = colors[rnd.Next(colors.Count)];
                            }while (elimCol == squareCol);

                            secBlocks[indexA].Rule    = new EliminationRule(elimCol);
                            secBlocks[indexB].Rule    = new SunPairRule(squareCol);
                            coloredEliminationSpawned = true;
                        }
                    }
                }

                if (!coloredEliminationSpawned)
                {
                    var secBlocks = sec.Blocks.Where(x => x.Rule == null).ToList();
                    int index     = rnd.Next(secBlocks.Count);
                    secBlocks[index].Rule = new EliminationRule();
                }

                if (isHexagon)
                {
                    bool isNode = rnd.NextDouble() > 0.5;
                    if (isNode)
                    {
                        var secNodes = sec.Blocks.SelectMany(x => x.Nodes).Where(x => x.State == NodeState.Normal).Distinct().Except(allSolutionNodes).ToList();
                        if (secNodes.Count > 0)
                        {
                            int index = rnd.Next(secNodes.Count);
                            secNodes[index].SetState(NodeState.Marked);
                        }
                        else
                        {
                            isNode = false;
                        }
                    }
                    if (!isNode)
                    {
                        var secEdges = sec.Blocks.SelectMany(x => x.Edges).Where(x => x.State == EdgeState.Normal).Distinct().Except(allSolutionEdges).ToList();
                        if (secEdges.Count > 0)
                        {
                            int index = rnd.Next(secEdges.Count);
                            secEdges[index].SetState(EdgeState.Marked);
                        }
                    }
                }
                else
                {
                    var secBlocks = sec.Blocks.Where(x => x.Rule == null).ToList();
                    int index     = rnd.Next(secBlocks.Count);
                    int ruleType  = rnd.Next(4);

                    Color?squareCol = sec.Blocks.Select(x => x.Rule).OfType <ColoredSquareRule>().FirstOrDefault()?.Color;
                    Color?sunCol    = sec.Blocks.Select(x => x.Rule).OfType <SunPairRule>().FirstOrDefault()?.Color;

                    // Create colored square
                    if (ruleType == 0)
                    {
                        if (squareCol != null && colors.Count > 1)
                        {
                            Color col;
                            do
                            {
                                col = colors[rnd.Next(colors.Count)];
                            }while (col == squareCol.Value);
                            secBlocks[index].Rule = new ColoredSquareRule(col);
                        }
                        else
                        {
                            ruleType++;
                        }
                    }
                    // Create sun
                    if (ruleType == 1)
                    {
                        if ((squareCol != null && sunCol != null && colors.Count > 2) || ((squareCol == null || sunCol == null) && colors.Count > 1))
                        {
                            Color col;
                            do
                            {
                                col = colors[rnd.Next(colors.Count)];
                            }while ((squareCol != null && col == squareCol) || (sunCol != null && col == sunCol));
                            secBlocks[index].Rule = new SunPairRule(col);
                        }
                        else
                        {
                            ruleType++;
                        }
                    }
                    // Create triangle
                    if (ruleType == 2)
                    {
                        int trianglePower = rnd.Next(1, 4);
                        // Make sure that we are not creating triangle that actually fit to solution line
                        if (secBlocks[index].Edges.Intersect(allSolutionEdges).Count() == trianglePower)
                        {
                            trianglePower = (trianglePower % 3) + 1;
                        }
                        secBlocks[index].Rule = new TriangleRule(trianglePower);
                    }
                    // Create tetris
                    if (ruleType == 3)
                    {
                        int tetrominoSize = rnd.Next(1, Math.Min(4, secBlocks.Count));
                        // If there are no tetrominos in sector, make sure that we are not creating tetromino that actually fit into sector
                        if (secBlocks.Where(x => x.Rule is TetrisRule).Count() == 0 && tetrominoSize == secBlocks.Count)
                        {
                            tetrominoSize = tetrominoSize == 1 ? 2 : tetrominoSize - 1;
                        }
                        int w = rnd.Next(1, tetrominoSize + 1);
                        int h = (int)Math.Ceiling((double)tetrominoSize / w);
                        bool[,] shape = new bool[w, h];
                        for (int k = 0; k < tetrominoSize; k++)
                        {
                            int x, y;
                            do
                            {
                                x = rnd.Next(0, w);
                                y = rnd.Next(0, h);
                            }while (shape[x, y] == true);
                            shape[x, y] = true;
                        }

                        bool rotattable  = rnd.NextDouble() > 0.9 && !TetrisRotatableRule.AreShapesIdentical(shape, TetrisRotatableRule.RotateShapeCW(shape));
                        bool subtractive = rnd.NextDouble() > 0.93;

                        if (rotattable)
                        {
                            secBlocks[index].Rule = new TetrisRotatableRule(shape, subtractive);
                        }
                        else
                        {
                            secBlocks[index].Rule = new TetrisRule(shape, subtractive);
                        }
                    }
                }
            }
            #endregion

            return(panel);

            #region == Methods ==

            int GetMirrorNodeID(int nodeID)
            {
                if (ySymmetry)
                {
                    return(maxNodeID - nodeID);
                }
                else
                {
                    return((nodeID / width1 * width1) * 2 + panelWidth - nodeID);
                }
            }

            List <int> GetBorderNodes()
            {
                List <int> res = new List <int>();

                for (int i = 0; i < width1; i++)
                {
                    for (int j = 0; j < panelHeight + 1; j++)
                    {
                        if (i == 0 || i == panelWidth || j == 0 || j == panelHeight)
                        {
                            res.Add(j * width1 + i);
                        }
                    }
                }
                return(res);
            }

            List <int> GetRandomSolutionLine(int startNode, int endNode)
            {
                // This is the solution line
                List <int> line = new List <int>();
                // This contains nodes from mirror line if present
                List <int> mirrorLine = new List <int>();
                // Alternative neighbour nodes of [i] node
                List <List <int> > forkRoad = new List <List <int> >();
                // If the search process will start taking too long in try to find longer solution, we will break and return this
                List <int> failsafeSolution = null;

                AddNodeToSolution(startNode);
                int iteration = 0;

                // Loop until we get our line to the end node (dont' accept too short lines)
                while (line[line.Count - 1] != endNode || line.Count <= 4 || (panelWidth * panelHeight >= 12 && line.Count <= 6))
                {
                    iteration++;
                    if (iteration > 5000 && failsafeSolution != null)
                    {
                        return(failsafeSolution);
                    }

                    int last = line[line.Count - 1];

                    // If last node is end node but we're still looping => line's too short, therefore delete last node and forks and go back
                    if (last == endNode)
                    {
                        if (failsafeSolution == null)
                        {
                            failsafeSolution = new List <int>(line);
                        }
                        RemoveLastNodeFromSolution();
                        forkRoad.RemoveAt(forkRoad.Count - 1);
                        continue;
                    }

                    // If the last node has forks, it means that we were here before and returned back to this node
                    if (forkRoad.Count == line.Count)
                    {
                        // If there are available forks => pick one randomly and try it
                        if (forkRoad[forkRoad.Count - 1].Count > 0)
                        {
                            int nextNodeIndex = rnd.Next(forkRoad[forkRoad.Count - 1].Count);
                            AddNodeToSolution(forkRoad[forkRoad.Count - 1][nextNodeIndex]);
                            forkRoad[forkRoad.Count - 1].RemoveAt(nextNodeIndex);
                            continue;
                        }
                        // If we've tried all the forks already => delete last node and its forks and go back to the previous node
                        else
                        {
                            RemoveLastNodeFromSolution();
                            forkRoad.RemoveAt(forkRoad.Count - 1);
                            continue;
                        }
                    }
                    // If the last node does not have forks yet => we're first time here
                    else
                    {
                        // Get all nodes we can go to from current last node
                        var neighbours = GetAllViableNeighbours(last);

                        // If we have options to move
                        if (neighbours.Count > 0)
                        {
                            // Randomly choose the next node
                            int nextNodeIndex = rnd.Next(neighbours.Count);
                            // Add it to the solution line
                            AddNodeToSolution(neighbours[nextNodeIndex]);
                            // Remove it from other neighbours and add them to the fork roads
                            neighbours.RemoveAt(nextNodeIndex);
                            forkRoad.Add(neighbours);
                            continue;
                        }
                        // If we are in dead end
                        else
                        {
                            // Delete the last node from the solution and move on to try other forks of the previous node
                            RemoveLastNodeFromSolution();
                            continue;
                        }
                    }
                }

                return(line);

                List <int> GetAllViableNeighbours(int nodeID)
                {
                    IEnumerable <int> neighbours = GetNodeNeighbours(nodeID);

                    neighbours = neighbours.Except(line).Except(mirrorLine);
                    if (symmetry)
                    {
                        neighbours = neighbours.Where(x => x != GetMirrorNodeID(x));
                    }
                    return(neighbours.ToList());
                }

                List <int> GetNodeNeighbours(int nodeID)
                {
                    List <int> neighbours = new List <int>();

                    if (nodeID > panelWidth)
                    {
                        neighbours.Add(nodeID - width1);
                    }
                    if (nodeID < panelHeight * width1)
                    {
                        neighbours.Add(nodeID + width1);
                    }
                    if (nodeID % width1 != 0)
                    {
                        neighbours.Add(nodeID - 1);
                    }
                    if ((nodeID + 1) % width1 != 0)
                    {
                        neighbours.Add(nodeID + 1);
                    }
                    return(neighbours);
                }

                void AddNodeToSolution(int nodeID)
                {
                    line.Add(nodeID);
                    if (symmetry)
                    {
                        mirrorLine.Add(GetMirrorNodeID(nodeID));
                    }
                }

                void RemoveLastNodeFromSolution()
                {
                    line.RemoveAt(line.Count - 1);
                    if (symmetry)
                    {
                        mirrorLine.RemoveAt(mirrorLine.Count - 1);
                    }
                }
            }

            #endregion
        }
Пример #2
0
        private IEnumerable <Error> CheckTetrisErrors()
        {
            List <Error> errorsList = new List <Error>();

            // Retrieve all eliminated Tetris rules
            var eliminatedTetrises = eliminatedParts.Where(x => x is Block block && block.Rule is TetrisRule)
                                     .Select(x => (x as Block).Rule as TetrisRule);

            // Retrieve all Tetris rules from sector blocks, excluding eliminated ones
            var tetrisRules = Blocks.Where(x => x.Rule is TetrisRule).Select(x => x.Rule as TetrisRule).Except(eliminatedTetrises);

            if (!tetrisRules.Any())
            {
                return(errorsList);
            }
            int sum = tetrisRules.Sum(x => x.TotalBlocks);

            // If net sum of all tetris blocks is not equal to total blocks of sector, then it's an error outright
            if (sum != TotalBlocks && sum != 0)
            {
                foreach (var tetromino in tetrisRules)
                {
                    errorsList.Add(new Error(tetromino.OwnerBlock));
                }
                return(errorsList);
            }

            // Create board where sector blocks are 0 and other blocks are 1
            // Complete board should have only Ones and no Zeroes
            int[,] baseBoard = new int[Panel.Width, Panel.Height];
            for (int j = 0; j < Panel.Height; j++)
            {
                for (int i = 0; i < Panel.Width; i++)
                {
                    if (Blocks.Contains(Panel.Grid[i, j]) && sum != 0)
                    {
                        baseBoard[i, j] = 0;
                    }
                    else
                    {
                        baseBoard[i, j] = 1;
                    }
                }
            }

            // All variations of board, created by different placement of subtractive shapes
            IEnumerable <int[, ]> allBoards;

            var rotatableTetrominos  = tetrisRules.OfType <TetrisRotatableRule>();
            var stationaryTetrominos = tetrisRules.Except(rotatableTetrominos);

            // If there are no subtracting tetrominos, then we are lucky to deal with only one version of board
            if (!tetrisRules.Any(x => x.IsSubtractive))
            {
                allBoards = new List <int[, ]> {
                    baseBoard
                }
            }
            ;
            // Otherwise we have to create variations of base board, where all subtractive tetrominos are placed in every possible combination
            else
            {
                // Extract subtractive tetrominos
                var rotatableSubtractive  = rotatableTetrominos.Where(x => x.IsSubtractive);
                var stationarySubtractive = stationaryTetrominos.Where(x => x.IsSubtractive);

                // Remove subtractive ones from regular ones
                rotatableTetrominos  = rotatableTetrominos.Except(rotatableSubtractive);
                stationaryTetrominos = stationaryTetrominos.Except(stationarySubtractive);

                // Get all combinations of rotations of subtractive shapes
                List <List <TetrisRule> > allSubtractiveCombinations = GetAllTetrominoCombinations(stationarySubtractive, rotatableSubtractive);

                // Now for every combination of rotations create all combinations of positions of every shape on board
                // Create a board version from each combination and compile them into all boards collection
                allBoards = GetAllBoards();

                IEnumerable <int[, ]> GetAllBoards()
                {
                    // For every rotations combination
                    foreach (var rotatCombination in allSubtractiveCombinations)
                    {
                        // Get all possible positions on board for every shape
                        // And try to place shapes according to these positions for every positions variant
                        foreach (var permut in GetPermutationsWithRepetitions(rotatCombination.Count, Panel.Width * Panel.Height))
                        {
                            int[,] boardCopy = baseBoard.Clone() as int[, ];
                            bool failedCombination = false;

                            for (int i = 0; i < rotatCombination.Count; i++)
                            {
                                int y = permut[i] / Panel.Width;
                                int x = permut[i] - y * Panel.Width;

                                // If shape can't be placed at specified position, this variant is invalid => skip
                                if (!ApplyShapeToBoard(boardCopy, rotatCombination[i].Shape, (x, y), true))
                                {
                                    failedCombination = true;
                                    break;
                                }
                            }

                            if (failedCombination)
                            {
                                continue;
                            }

                            // If all shapes in variant are placed on board, then return this board as one of board variants
                            yield return(boardCopy);
                        }
                    }
                }
            }

            // Get all combinations of rotations of tetrominos
            List <List <TetrisRule> > allTetrominoCombinations = GetAllTetrominoCombinations(stationaryTetrominos, rotatableTetrominos);

            // Now, for each board variation and each tetromino rotations combination we should generate all permutations
            // of tetrominos order and try to place tetrominos from given rotation variant onto board variant in given order

            // List of all possible orders. One order is a list of indices of shapes
            IEnumerable <IEnumerable <int> > tetrominosOrders = GetPermutations(allTetrominoCombinations.First().Count);

            // If we will find such combination of board/rotation/order that fits into board and makes it complete with 1's
            // Then we will set this flag and abort further calculations
            bool success = false;

            // For each board
            foreach (int[,] board in allBoards)
            {
                // For each rotations variant
                foreach (List <TetrisRule> rotationComb in allTetrominoCombinations)
                {
                    // For each order
                    foreach (IEnumerable <int> order in tetrominosOrders)
                    {
                        int[,] boardCopy = board.Clone() as int[, ];
                        bool allShapesPlaced = true;

                        // For each shape in order list
                        foreach (int shapeIndex in order)
                        {
                            // Flag indicates that shape was successfully placed on board
                            // If shape was not placed on board, then there's no need to try other shapes in this order
                            bool shapePlaced = false;

                            // Try to place a shape on board starting from top-left most position
                            for (int pos = 0; pos < Panel.Width * Panel.Height; pos++)
                            {
                                int y = pos / Panel.Width;
                                int x = pos - y * Panel.Width;

                                // Once current shape successfully placed on board
                                // break and go to the next shape in current order
                                if (true == ApplyShapeToBoard(boardCopy, rotationComb[shapeIndex].Shape, (x, y)))
                                {
                                    shapePlaced = true;
                                    break;
                                }
                            }

                            // If shape was not placed on board, there's no need to try other shapes in this order
                            if (!shapePlaced)
                            {
                                allShapesPlaced = false;
                                break;
                            }
                        }

                        // If we interrupted previous loop due to not placed shape, then there's no need to check if board is complete
                        if (!allShapesPlaced)
                        {
                            continue;
                        }

                        // Once all shapes are placed, we should check if board is complete (all blocks are 1)
                        // Let's assume that board is complete
                        success = true;
                        for (int i = 0; i < boardCopy.GetLength(0); i++)
                        {
                            for (int j = 0; j < boardCopy.GetLength(1); j++)
                            {
                                // If at least one block is not 1, then board is not complete
                                if (boardCopy[i, j] != 1)
                                {
                                    success = false; // BUSTED
                                    break;
                                }
                            }
                            if (!success)
                            {
                                break;
                            }
                        }

                        // Keep looping untill the success or the end
                        if (success)
                        {
                            break;
                        }
                    }

                    if (success)
                    {
                        break;
                    }
                }

                if (success)
                {
                    break;
                }
            }

            // If after all of this loops we couldn't find a right combination for solution
            // Then create errors for each Tetris rule block and finally return
            if (!success)
            {
                foreach (var tetrisRule in tetrisRules)
                {
                    errorsList.Add(new Error(tetrisRule.OwnerBlock));
                }
            }

            return(errorsList);

            // ==============================================
            // ============== Local methods =================
            bool ApplyShapeToBoard(int[,] board, bool[,] shape, (int x, int y) point, bool isSubtractive = false)
            {
                int[,] originalBoard = board.Clone() as int[, ];

                if (point.x + shape.GetLength(0) - 1 >= board.GetLength(0) ||
                    point.y + shape.GetLength(1) - 1 >= board.GetLength(1))
                {
                    return(false);
                }

                for (int i = 0; i < shape.GetLength(0); i++)
                {
                    for (int j = 0; j < shape.GetLength(1); j++)
                    {
                        if (shape[i, j])
                        {
                            if (board[point.x + i, point.y + j] < 1 || isSubtractive)
                            {
                                board[point.x + i, point.y + j] += isSubtractive ? -1 : 1;
                            }
                            // If shape is not subtractive and it's can't be fit onto board (not all blocks have 0 or less)
                            // Then restore board to it's original state and return fail
                            else
                            {
                                for (int w = 0; w < board.GetLength(0); w++)
                                {
                                    for (int z = 0; z < board.GetLength(1); z++)
                                    {
                                        board[w, z] = originalBoard[w, z];
                                    }
                                }
                                return(false);
                            }
                        }
                    }
                }

                return(true);
            }

            IEnumerable <List <int> > GetPermutationsWithRepetitions(int places, int options = 4)
            {
                int numericMax = (int)Math.Pow(options, places);

                for (int i = 0; i < numericMax; i++)
                {
                    List <int> li = new List <int>(places);
                    for (int digit = 0; digit < places; digit++)
                    {
                        li.Add((int)(i / Math.Pow(options, digit)) % options);
                    }
                    yield return(li);
                }
            }

            // Rotate every rotatable shape and create every possible combination of rotations of each shape
            // Combined with non-rotatable shapes
            List <List <TetrisRule> > GetAllTetrominoCombinations(IEnumerable <TetrisRule> stationary, IEnumerable <TetrisRotatableRule> rotatable)
            {
                // Shapes rotations - list of array of 4 rotations for every rotatable shape
                List <TetrisRotatableRule[]> shapeRotations = new List <TetrisRotatableRule[]>();

                foreach (var shape in rotatable)
                {
                    TetrisRotatableRule[] shapes = new TetrisRotatableRule[4];
                    shapes[0] = shape;
                    for (int i = 1; i < 4; i++)
                    {
                        shapes[i] = shapes[i - 1].RotateCW();
                    }
                    shapeRotations.Add(shapes);
                }

                List <List <TetrisRule> > allCombinations = new List <List <TetrisRule> >();

                // If there're no rotatable shapes, then there's only one combination of shape rotations
                if (shapeRotations.Count == 0)
                {
                    allCombinations.Add(new List <TetrisRule>(stationary));
                }
                // Otherwise get all combinations of rotations of every rotatable (and add non-rotatables to every combination)
                else
                {
                    foreach (var permut in GetPermutationsWithRepetitions(shapeRotations.Count, 4))
                    {
                        List <TetrisRule> combination = new List <TetrisRule>();
                        for (int i = 0; i < shapeRotations.Count; i++)
                        {
                            combination.Add(shapeRotations[i][permut[i]]);
                        }
                        combination.AddRange(stationary);

                        allCombinations.Add(combination);
                    }
                }

                return(allCombinations);
            }

            IEnumerable <IEnumerable <int> > GetPermutations(int places) => Enumerable.Range(0, places).GetPermutations(places);
        }
Пример #3
0
        private bool InitializePanel()
        {
            if (panel != null)
            {
                Walls.Clear();
                StartPoints.Clear();
                EndPoints.Clear();
                renderedTetrisTextures.Clear();

                PuzzleDimensions = new Point(panel.Width, panel.Height);

                // Calculation of puzzle sizes for current screen size
                int width  = screenSize.X;
                int height = screenSize.Y;

                int maxPuzzleDimension = Math.Max(PuzzleDimensions.X, PuzzleDimensions.Y);

                int screenMinSize = Math.Min(width, height);
                int puzzleMaxSize = (int)(screenMinSize * PuzzleSpaceRatio(maxPuzzleDimension));
                LineWidth          = (int)(puzzleMaxSize * LineSizeRatio(maxPuzzleDimension));
                BlockWidth         = (int)(puzzleMaxSize * BlockSizeRatio(maxPuzzleDimension));
                HalfLineWidthPoint = new Point(LineWidth / 2);
                LineWidthPoint     = new Point(LineWidth);
                BlockSizePoint     = new Point(BlockWidth);

                int endAppendixLength = (int)(BlockWidth * endPointLegth);

                int puzzleWidth  = LineWidth * (PuzzleDimensions.X + 1) + BlockWidth * PuzzleDimensions.X;
                int puzzleHeight = LineWidth * (PuzzleDimensions.Y + 1) + BlockWidth * PuzzleDimensions.Y;

                int   xMargin = (width - puzzleWidth) / 2;
                int   yMargin = (height - puzzleHeight) / 2;
                Point margins = new Point(xMargin, yMargin);

                PuzzleConfig = new Rectangle(xMargin, yMargin, puzzleWidth, puzzleHeight);

                NodePadding = LineWidth + BlockWidth;

                // Creating walls hitboxes
                CreateHorizontalWalls(false);   // Top walls
                CreateHorizontalWalls(true);    // Bottom walls
                CreateVerticalWalls(false);     // Left walls
                CreateVerticalWalls(true);      // Right walls

                for (int i = 0; i < PuzzleDimensions.X; i++)
                {
                    for (int j = 0; j < PuzzleDimensions.Y; j++)
                    {
                        Walls.Add(new Rectangle(xMargin + LineWidth * (i + 1) + BlockWidth * i, yMargin + LineWidth * (j + 1) + BlockWidth * j, BlockWidth, BlockWidth));
                    }
                }

                // Creating walls for broken edges
                var brokenEdges = panel.Edges.Where(x => x.State == EdgeState.Broken);
                foreach (Edge edge in brokenEdges)
                {
                    Point nodeA  = NodeIdToPoint(edge.Id % 100).Multiply(NodePadding) + margins;
                    Point nodeB  = NodeIdToPoint(edge.Id / 100).Multiply(NodePadding) + margins;
                    Point middle = (nodeA + nodeB).Divide(2);
                    Walls.Add(new Rectangle(middle, new Point(LineWidth)));
                }

                // Creating hitboxes for starting nodes
                foreach (Point start in GetStartNodes())
                {
                    StartPoints.Add(new Rectangle(xMargin + start.X * NodePadding - LineWidth, yMargin + start.Y * NodePadding - LineWidth, LineWidth * 3, LineWidth * 3));
                }

                // Generate textures for tetris rules
                CreateTetrisRuleTextures();

                #region === Inner methods region ===
                // Returns a coef k: Total free space in pixels * k = puzzle size in pixels
                float PuzzleSpaceRatio(float puzzleDimension) => (float)(-0.0005 * Math.Pow(puzzleDimension, 4) + 0.0082 * Math.Pow(puzzleDimension, 3) - 0.0439 * Math.Pow(puzzleDimension, 2) + 0.1011 * puzzleDimension + 0.6875);

                // Returns a coef k: Puzzle size in pixels * k = block size in pixels
                float BlockSizeRatio(float puzzleDimension) => (float)(0.8563 * Math.Pow(puzzleDimension, -1.134));

                // Returns a coef k: Puzzle size in pixels * k = line width in pixels
                float LineSizeRatio(float puzzleDimension) => - 0.0064f * puzzleDimension + 0.0859f;

                IEnumerable <Point> GetStartNodes() => panel.Nodes.Where(x => x.State == NodeState.Start).Select(x => NodeIdToPoint(x.Id));

                IEnumerable <Point> GetEndNodesTop()
                {
                    for (int i = 1; i < panel.Width; i++)
                    {
                        if (panel.Nodes[i].State == NodeState.Exit)
                        {
                            yield return(new Point(i, 0));
                        }
                    }
                }

                IEnumerable <Point> GetEndNodesBot()
                {
                    for (int i = 1; i < panel.Width; i++)
                    {
                        int index = panel.Height * (panel.Width + 1) + i;
                        if (panel.Nodes[index].State == NodeState.Exit)
                        {
                            yield return(new Point(i, panel.Height));
                        }
                    }
                }

                IEnumerable <Point> GetEndNodesLeft()
                {
                    for (int j = 0; j < panel.Height + 1; j++)
                    {
                        int index = j * (panel.Width + 1);
                        if (panel.Nodes[index].State == NodeState.Exit)
                        {
                            yield return(new Point(0, j));
                        }
                    }
                }

                IEnumerable <Point> GetEndNodesRight()
                {
                    for (int j = 0; j < panel.Height + 1; j++)
                    {
                        int index = j * (panel.Width + 1) + panel.Width;
                        if (panel.Nodes[index].State == NodeState.Exit)
                        {
                            yield return(new Point(panel.Width, j));
                        }
                    }
                }

                void CreateHorizontalWalls(bool isBottom)
                {
                    int yStartPoint = isBottom ? yMargin + puzzleHeight : 0;
                    var ends        = isBottom ? GetEndNodesBot() : GetEndNodesTop();

                    if (ends.Count() == 0)
                    {
                        Walls.Add(new Rectangle(0, yStartPoint, width, yMargin));
                    }
                    else
                    {
                        Walls.Add(new Rectangle(0, yStartPoint + (isBottom ? endAppendixLength : 0), width, yMargin - endAppendixLength));

                        int lastXPoint = 0;
                        foreach (Point endPoint in ends)
                        {
                            int x = xMargin + endPoint.X * (NodePadding);
                            Walls.Add(new Rectangle(lastXPoint, isBottom ? yStartPoint : (yMargin - endAppendixLength), x - lastXPoint, endAppendixLength));
                            EndPoints.Add(new EndPoint(new Rectangle(x, isBottom ? yStartPoint + endAppendixLength - LineWidth : (yMargin - endAppendixLength), LineWidth, LineWidth), isBottom ? Facing.Down : Facing.Up));
                            lastXPoint = x + LineWidth;
                        }
                        Walls.Add(new Rectangle(lastXPoint, isBottom ? yStartPoint : (yMargin - endAppendixLength), width - lastXPoint, endAppendixLength));
                    }
                }

                void CreateVerticalWalls(bool isRight)
                {
                    int xStartPoint = isRight ? xMargin + puzzleWidth : 0;
                    var ends        = isRight ? GetEndNodesRight() : GetEndNodesLeft();

                    if (ends.Count() == 0)
                    {
                        Walls.Add(new Rectangle(xStartPoint, 0, xMargin, height));
                    }
                    else
                    {
                        Walls.Add(new Rectangle(xStartPoint + (isRight ? endAppendixLength : 0), 0, xMargin - endAppendixLength, height));

                        int lastYPoint = 0;
                        foreach (Point endPoint in ends)
                        {
                            int y = yMargin + endPoint.Y * (NodePadding);
                            Walls.Add(new Rectangle(isRight ? xStartPoint : (xMargin - endAppendixLength), lastYPoint, endAppendixLength, y - lastYPoint));
                            EndPoints.Add(new EndPoint(new Rectangle(isRight ? xStartPoint + endAppendixLength - LineWidth : xMargin - endAppendixLength, y, LineWidth, LineWidth), isRight ? Facing.Right : Facing.Left));
                            lastYPoint = y + LineWidth;
                        }
                        Walls.Add(new Rectangle(isRight ? xStartPoint : (xMargin - endAppendixLength), lastYPoint, endAppendixLength, height - lastYPoint));
                    }
                }

                void CreateTetrisRuleTextures()
                {
                    // Render shape into texture for each tetris rule
                    var tetrisBlocks = panel.Blocks.Where(x => x.Rule is TetrisRule).ToList();

                    if (tetrisBlocks.Count > 0)
                    {
                        int maxDimension = tetrisBlocks.Select(x => x.Rule as TetrisRule).Select(x => Math.Max(x.Shape.GetLength(0), x.Shape.GetLength(1))).Max();
                        if (maxDimension < 3)
                        {
                            maxDimension++;
                        }
                        tetrisTextureSize = new Point((maxDimension + 1) * texTetris[0].Width);
                        foreach (Block block in tetrisBlocks)
                        {
                            TetrisRule rule = block.Rule as TetrisRule;
                            bool[,] shape = rule.Shape;
                            // If shape is rotatable, then rotate it randomly before render
                            if (rule is TetrisRotatableRule r)
                            {
                                shape = TetrisRotatableRule.RotateShapeCW(shape, rnd.Next(0, 4));
                            }
                            // Draw shape on a texture
                            RenderTarget2D texture = CreateTetrisTexture(shape, rule is TetrisRotatableRule, rule.IsSubtractive);
                            // Save it to the dictionary
                            renderedTetrisTextures.Add(block.Id, texture);
                        }
                    }
                }

                #endregion
                return(true);
            }
            return(false);
        }