private static void PrintTile(StatefulTile tileDiagA)
 {
     PrintFace(tileDiagA, 0);
     Console.Write("| ");
     PrintFace(tileDiagA, 1);
     Console.WriteLine();
 }
        private static RowNode CopyAndRemove(RowNode row, StatefulTile tile)
        {
            var result = CopyRow(row);

            result.Row.Remove(tile);
            return(result);
        }
        private static void SwapRow(ref StatefulTile[,] board, int a, int b)
        {
            var temp = new StatefulTile[4];

            for (var k = 0; k < 4; k++)
            {
                temp[k]     = board[b, k];
                board[b, k] = board[a, k];
                board[a, k] = temp[k];
            }
        }
        private static void SwapCol(ref StatefulTile[,] board, int a, int b)
        {
            var temp = new StatefulTile[4];

            for (var k = 0; k < 4; k++)
            {
                temp[k]     = board[k, b];
                board[k, b] = board[k, a];
                board[k, a] = temp[k];
            }
        }
 private static (int, int) GetTileLocation(StatefulTile[,] board, StatefulTile tile)
 {
     for (var i = 0; i < 4; i++)
     {
         for (var j = 0; j < 4; j++)
         {
             if (board[i, j] == tile)
             {
                 return(i, j);
             }
         }
     }
     return(-1, -1);
 }
        private static IList <StatefulTile> GetAllStatefulTiles(IList <Tile> allTiles)
        {
            var allStatefulTiles = new List <StatefulTile>();

            foreach (var tile in allTiles)
            {
                var a = new StatefulTile(tile, new List <int> {
                    0, 1
                });
                var b = new StatefulTile(tile, new List <int> {
                    1, 0
                });
                a.StatefulTileTwin = b;
                b.StatefulTileTwin = a;
                allStatefulTiles.Add(a);
                allStatefulTiles.Add(b);
            }
            return(allStatefulTiles);
        }
 private static void PrintFace(StatefulTile tile, int i) =>
 Console.Write(tile.GetFaceValue(i).ToString().PadRight(3));
 private static bool IsSameTile(StatefulTile a, StatefulTile b) =>
 a == b || a == b.StatefulTileTwin;
        private static void GroupsOfFour(IList <Tile> allTiles)
        {
            // get all stateful tiles
            Console.WriteLine("Getting all stateful tiles...");
            var allStatefullTiles = GetAllStatefulTiles(allTiles);

            Console.WriteLine($"Got {allStatefullTiles.Count()} stateful tiles.");
            // get all rows
            Console.WriteLine("Getting rows...");
            var validRows = new List <RowNode>();

            // for each possible row combination
            foreach (var statefulTileCombination in new Combinations <StatefulTile>(allStatefullTiles, 4))
            {
                if (ValidRow(statefulTileCombination))
                {
                    // row is valid
                    // create row node
                    var rowNode = new RowNode(statefulTileCombination);
                    // add row node to list of valid rows
                    validRows.Add(rowNode);
                    foreach (var tile in rowNode.Row)
                    {
                        // link tile back to row that it is in
                        tile.RowNodes.Add(rowNode);
                    }
                }
            }
            Console.WriteLine($"Got {validRows.Count} rows.");
            // find valid grids
            Console.WriteLine("Finding valid grids...\n");
            // for each valid row...
            foreach (var rowA in validRows)
            {
                // choose a tile for col A
                var tileA = rowA.Row[0];
                // find all possible rows that could be the col for this tile
                // of the rows that include tile A...
                var validColAs = tileA.RowNodes.Where(x =>
                                                      // where row is not row A and its not the case that...
                                                      x != rowA && !x.Row.Any(y =>
                                                      // there is a tile other than tile A that is in both row A and the col A
                                                                              y != tileA &&
                                                                              rowA.Row.Any(z => IsSameTile(y, z))));
                foreach (var colA in validColAs)
                {
                    // do the same for another tile from row A
                    var tileB      = rowA.Row[1];
                    var validColBs = tileB.RowNodes.Where(x =>
                                                          // row must now also not be col A
                                                          x != rowA && x != colA && !x.Row.Any(y =>
                                                                                               y != tileB
                                                                                               // also check col A for matching tiles
                                                                                               && (rowA.Row.Any(z => IsSameTile(y, z)) ||
                                                                                                   colA.Row.Any(z => IsSameTile(y, z)))));
                    foreach (var colB in validColBs)
                    {
                        // third tile
                        var tileC      = rowA.Row[2];
                        var validColCs = tileC.RowNodes.Where(x =>
                                                              x != rowA && x != colA && x != colB && !x.Row.Any(y =>
                                                                                                                y != tileC &&
                                                                                                                (rowA.Row.Any(z => IsSameTile(y, z)) ||
                                                                                                                 colA.Row.Any(z => IsSameTile(y, z)) ||
                                                                                                                 colB.Row.Any(z => IsSameTile(y, z)))));
                        foreach (var colC in validColCs)
                        {
                            // fourth and last tile
                            var tileD      = rowA.Row[3];
                            var validColDs = tileD.RowNodes.Where(x =>
                                                                  x != rowA && x != colA && x != colB && x != colC && !x.Row.Any(y =>
                                                                                                                                 y != tileD &&
                                                                                                                                 (rowA.Row.Any(z => IsSameTile(y, z)) ||
                                                                                                                                  colA.Row.Any(z => IsSameTile(y, z)) ||
                                                                                                                                  colB.Row.Any(z => IsSameTile(y, z)) ||
                                                                                                                                  colC.Row.Any(z => IsSameTile(y, z)))));
                            foreach (var colD in validColDs)
                            {
                                // create new objects and remove row A from the cols
                                var colARowB   = CopyAndRemove(colA, tileA);
                                var colBRowB   = CopyAndRemove(colB, tileB);
                                var colCRowB   = CopyAndRemove(colC, tileC);
                                var colDRowB   = CopyAndRemove(colD, tileD);
                                var validRowBs = validRows.Except(
                                    new List <RowNode> {
                                    rowA, colA, colB, colC, colD
                                })
                                                 .Where(x => colARowB.Row.Any(y => x.Row.Contains(y)) &&
                                                        colBRowB.Row.Any(y => x.Row.Contains(y)) &&
                                                        colCRowB.Row.Any(y => x.Row.Contains(y)) &&
                                                        colDRowB.Row.Any(y => x.Row.Contains(y)));
                                foreach (var rowB in validRowBs)
                                {
                                    var colARowC = CopyRow(colARowB);
                                    var colBRowC = CopyRow(colBRowB);
                                    var colCRowC = CopyRow(colCRowB);
                                    var colDRowC = CopyRow(colDRowB);
                                    foreach (var tile in rowB.Row)
                                    {
                                        colARowC.Row.Remove(tile);
                                        colBRowC.Row.Remove(tile);
                                        colCRowC.Row.Remove(tile);
                                        colDRowC.Row.Remove(tile);
                                    }
                                    var validRowCs = validRows.Except(
                                        new List <RowNode> {
                                        rowA, rowB, colA, colB, colC, colD
                                    })
                                                     .Where(x => colARowC.Row.Any(y => x.Row.Contains(y)) &&
                                                            colBRowC.Row.Any(y => x.Row.Contains(y)) &&
                                                            colCRowC.Row.Any(y => x.Row.Contains(y)) &&
                                                            colDRowC.Row.Any(y => x.Row.Contains(y)));
                                    foreach (var rowC in validRowCs)
                                    {
                                        var colARowD = CopyRow(colARowC);
                                        var colBRowD = CopyRow(colBRowC);
                                        var colCRowD = CopyRow(colCRowC);
                                        var colDRowD = CopyRow(colDRowC);
                                        foreach (var tile in rowC.Row)
                                        {
                                            colARowD.Row.Remove(tile);
                                            colBRowD.Row.Remove(tile);
                                            colCRowD.Row.Remove(tile);
                                            colDRowD.Row.Remove(tile);
                                        }
                                        var rowD = validRows.Except(
                                            new List <RowNode> {
                                            rowA, rowB, rowC, colA, colB, colC, colD
                                        })
                                                   .SingleOrDefault(x => colARowD.Row.Any(y => x.Row.Contains(y)) &&
                                                                    colBRowD.Row.Any(y => x.Row.Contains(y)) &&
                                                                    colCRowD.Row.Any(y => x.Row.Contains(y)) &&
                                                                    colDRowD.Row.Any(y => x.Row.Contains(y)));
                                        if (rowD != null)
                                        {
                                            // create and organise board as 2D array
                                            var board = new StatefulTile[4, 4];
                                            var i     = 0;
                                            // apply row A
                                            foreach (var tile in rowA.Row)
                                            {
                                                board[0, i++] = tile;
                                            }
                                            // apply row B
                                            board[1, 0] = colA.Row.Single(x => rowB.Row.Contains(x));
                                            board[1, 1] = colB.Row.Single(x => rowB.Row.Contains(x));
                                            board[1, 2] = colC.Row.Single(x => rowB.Row.Contains(x));
                                            board[1, 3] = colD.Row.Single(x => rowB.Row.Contains(x));
                                            // apply row C
                                            board[2, 0] = colA.Row.Single(x => rowC.Row.Contains(x));
                                            board[2, 1] = colB.Row.Single(x => rowC.Row.Contains(x));
                                            board[2, 2] = colC.Row.Single(x => rowC.Row.Contains(x));
                                            board[2, 3] = colD.Row.Single(x => rowC.Row.Contains(x));
                                            // apply row D
                                            board[3, 0] = colA.Row.Single(x => rowD.Row.Contains(x));
                                            board[3, 1] = colB.Row.Single(x => rowD.Row.Contains(x));
                                            board[3, 2] = colC.Row.Single(x => rowD.Row.Contains(x));
                                            board[3, 3] = colD.Row.Single(x => rowD.Row.Contains(x));
                                            //PrintBoard(board);
                                            var possibleDiagAs = validRows.Except(new List <RowNode>
                                            {
                                                rowA, rowB, rowC, rowD, colA, colB, colC, colD
                                            });
                                            foreach (var possibleDiagA in possibleDiagAs)
                                            {
                                                CheckDiag(
                                                    validRows,
                                                    rowA,
                                                    rowB,
                                                    rowC,
                                                    rowD,
                                                    colA,
                                                    colB,
                                                    colC,
                                                    colD,
                                                    possibleDiagA,
                                                    (StatefulTile[, ])board.Clone());
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            Console.WriteLine($"Found {validSolutions} valid grids.");
        }