static bool FindYWings(Region region)
        {
            var points = region.Points.Where(p => puzzle[p].Candidates.Count == 2).ToArray();

            if (points.Length > 1)
            {
                for (int i = 0; i < points.Length; i++)
                {
                    SPoint p1 = points[i];
                    for (int j = i + 1; j < points.Length; j++)
                    {
                        SPoint p2    = points[j];
                        var    inter = puzzle[p1].Candidates.Intersect(puzzle[p2].Candidates);
                        if (inter.Count() != 1)
                        {
                            continue;
                        }

                        int other1 = puzzle[p1].Candidates.Except(inter).ElementAt(0),
                            other2 = puzzle[p2].Candidates.Except(inter).ElementAt(0);

                        SPoint[] a = { p1, p2 };
                        foreach (SPoint point in a)
                        {
                            var p3a = puzzle[point].GetCanSeePoints().Except(points).Where(p => puzzle[p].Candidates.Count == 2 && puzzle[p].Candidates.Intersect(new int[] { other1, other2 }).Count() == 2);
                            if (p3a.Count() == 1) // Example: p1 and p3 see each other, so remove similarities from p2 and p3
                            {
                                SPoint p3     = p3a.ElementAt(0);
                                SPoint pOther = a.Single(p => p != point);
                                var    common = puzzle[pOther].GetCanSeePoints().Intersect(puzzle[p3].GetCanSeePoints());
                                var    cand   = puzzle[pOther].Candidates.Intersect(puzzle[p3].Candidates); // Will just be 1 candidate
                                if (puzzle.ChangeCandidates(common, cand))
                                {
                                    Logger.Log("Y-Wing", new SPoint[] { p1, p2, p3 }, cand);
                                    return(true);
                                }
                            }
                        }
                    }
                }
            }
            return(false);
        }
        static bool FindXYZWings(Region region)
        {
            bool changed = false;
            var  points2 = region.Points.Where(p => puzzle[p].Candidates.Count == 2).ToArray();
            var  points3 = region.Points.Where(p => puzzle[p].Candidates.Count == 3).ToArray();

            if (points2.Length > 0 && points3.Length > 0)
            {
                for (int i = 0; i < points2.Length; i++)
                {
                    SPoint p2 = points2[i];
                    for (int j = 0; j < points3.Length; j++)
                    {
                        SPoint p3 = points3[j];
                        if (puzzle[p2].Candidates.Intersect(puzzle[p3].Candidates).Count() != 2)
                        {
                            continue;
                        }

                        var p3Sees = puzzle[p3].GetCanSeePoints().Except(region.Points)
                                     .Where(p => puzzle[p].Candidates.Count == 2 && // If it has 2 candidates
                                            puzzle[p].Candidates.Intersect(puzzle[p3].Candidates).Count() == 2 && // Shares them both with p3
                                            puzzle[p].Candidates.Intersect(puzzle[p2].Candidates).Count() == 1); // And shares one with p2
                        foreach (SPoint p2_2 in p3Sees)
                        {
                            var allSee  = puzzle[p2].GetCanSeePoints().Intersect(puzzle[p3].GetCanSeePoints()).Intersect(puzzle[p2_2].GetCanSeePoints());
                            var allHave = puzzle[p2].Candidates.Intersect(puzzle[p3].Candidates).Intersect(puzzle[p2_2].Candidates); // Will be 1 Length
                            if (puzzle.ChangeCandidates(allSee, allHave))
                            {
                                Logger.Log("XYZ-Wing", new SPoint[] { p2, p3, p2_2 }, allHave);
                                changed = true;
                            }
                        }
                    }
                }
            }
            return(changed);
        }
        static bool FindLockedCandidates(bool doRows, int rc, int value)
        {
            var with = (doRows ? Puzzle.Rows : Puzzle.Columns)[rc].GetPointsWithCandidates(value);

            // Even if a block only has these candidates for this "k" value, it'd be slower to check that before cancelling "BlacklistCandidates"
            if (with.Count() == 3 || with.Count() == 2)
            {
                var blocks = with.Select(p => puzzle[p].Block).Distinct();
                if (blocks.Count() == 1)
                {
                    if (puzzle.ChangeCandidates(Puzzle.Blocks[blocks.ElementAt(0)].Points.Except(with), new int[] { value }))
                    {
                        Logger.Log("Locked candidate", with, "{4} {0} locks within block {1}: {2}: {3}", doRows ? SPoint.RowL(rc) : (rc + 1).ToString(), blocks.ElementAt(0) + 1, with.Print(), value, doRows ? "Row" : "Column");
                        return(true);
                    }
                }
            }
            return(false);
        }
 static bool PointingTuple()
 {
     for (int i = 0; i < 3; i++)
     {
         SPoint[][] blockrow = new SPoint[3][], blockcol = new SPoint[3][];
         for (int r = 0; r < 3; r++)
         {
             blockrow[r] = Puzzle.Blocks[r + (i * 3)].Points;
             blockcol[r] = Puzzle.Blocks[i + (r * 3)].Points;
         }
         for (int r = 0; r < 3; r++) // 3 blocks in a blockrow/blockcolumn
         {
             int[][] rowCand = new int[3][], colCand = new int[3][];
             for (int j = 0; j < 3; j++) // 3 rows/columns in block
             {
                 // The 3 cells' candidates in a block's row/column
                 rowCand[j] = blockrow[r].GetRow(j).Select(p => puzzle[p].Candidates).UniteAll().ToArray();
                 colCand[j] = blockcol[r].GetColumn(j).Select(p => puzzle[p].Candidates).UniteAll().ToArray();
             }
             // Now check if a row has a distinct candidate
             var zero_distinct = rowCand[0].Except(rowCand[1]).Except(rowCand[2]);
             if (zero_distinct.Count() > 0)
             {
                 if (RemovePointingTuple(blockrow, true, i, r, 0, zero_distinct))
                 {
                     return(true);
                 }
             }
             var one_distinct = rowCand[1].Except(rowCand[0]).Except(rowCand[2]);
             if (one_distinct.Count() > 0)
             {
                 if (RemovePointingTuple(blockrow, true, i, r, 1, one_distinct))
                 {
                     return(true);
                 }
             }
             var two_distinct = rowCand[2].Except(rowCand[0]).Except(rowCand[1]);
             if (two_distinct.Count() > 0)
             {
                 if (RemovePointingTuple(blockrow, true, i, r, 2, two_distinct))
                 {
                     return(true);
                 }
             }
             // Now check if a column has a distinct candidate
             zero_distinct = colCand[0].Except(colCand[1]).Except(colCand[2]);
             if (zero_distinct.Count() > 0)
             {
                 if (RemovePointingTuple(blockcol, false, i, r, 0, zero_distinct))
                 {
                     return(true);
                 }
             }
             one_distinct = colCand[1].Except(colCand[0]).Except(colCand[2]);
             if (one_distinct.Count() > 0)
             {
                 if (RemovePointingTuple(blockcol, false, i, r, 1, one_distinct))
                 {
                     return(true);
                 }
             }
             two_distinct = colCand[2].Except(colCand[0]).Except(colCand[1]);
             if (two_distinct.Count() > 0)
             {
                 if (RemovePointingTuple(blockcol, false, i, r, 2, two_distinct))
                 {
                     return(true);
                 }
             }
         }
     }
     return(false);
 }