Exemple #1
0
        /// <summary>
        /// Indicates whether the specified grid contains the digit.
        /// </summary>
        /// <param name="grid">The grid.</param>
        /// <param name="digit">The digit.</param>
        /// <param name="result">(<see langword="out"/> parameter) The result.</param>
        /// <returns>A <see cref="bool"/> value.</returns>
        public bool ContainsDigit(IReadOnlyGrid grid, int digit, out GridMap result)
        {
            result = GridMap.Empty;
            foreach (int cell in Map)
            {
                if ((grid.GetCandidatesReversal(cell) >> digit & 1) != 0)
                {
                    result.Add(cell);
                }
            }

            return(result.IsNotEmpty);
        }
        /// <summary>
        /// Get all invalid positions.
        /// </summary>
        /// <param name="grid">The grid.</param>
        /// <returns>The 9 maps for invalid positions of each digit.</returns>
        private static GridMap[] GetInvalidPos(IReadOnlyGrid grid)
        {
            var result     = new GridMap[9];
            var invalidPos = new GridMap[9];
            var mustPos    = new GridMap[9];

            for (int digit = 0; digit < 9; digit++)
            {
                for (int cell = 0; cell < 81; cell++)
                {
                    if ((grid.GetCandidatesReversal(cell) >> digit & 1) == 0)
                    {
                        invalidPos[digit].Add(cell);
                    }
                    else if (grid[cell] == digit)
                    {
                        mustPos[digit].Add(cell);
                    }
                }
            }

            for (int digit = 0; digit < 9; digit++)
            {
                foreach (var map in GetTemplates())
                {
                    if ((mustPos[digit] - map).IsNotEmpty || invalidPos[digit].Overlaps(map))
                    {
                        continue;
                    }

                    result[digit] |= map;
                }

                result[digit] = CandMaps[digit] - result[digit];
            }

            return(result);
        }
Exemple #3
0
        partial void CheckType1(
            IList <UrTechniqueInfo> accumulator, IReadOnlyGrid grid, int[] urCells, bool arMode,
            short comparer, int d1, int d2, int cornerCell, GridMap otherCellsMap)
        {
            //   ↓ cornerCell
            // (abc) ab
            //  ab   ab

            // Get the summary mask.
            short mask = 0;

            foreach (int cell in otherCellsMap)
            {
                mask |= grid.GetCandidatesReversal(cell);
            }

            if (mask != comparer)
            {
                return;
            }

            // Type 1 found. Now check elimination.
            var conclusions = new List <Conclusion>();

            if (grid.Exists(cornerCell, d1) is true)
            {
                conclusions.Add(new Conclusion(Elimination, cornerCell, d1));
            }
            if (grid.Exists(cornerCell, d2) is true)
            {
                conclusions.Add(new Conclusion(Elimination, cornerCell, d2));
            }
            if (conclusions.Count == 0)
            {
                return;
            }

            var candidateOffsets = new List <(int, int)>();

            foreach (int cell in otherCellsMap)
            {
                foreach (int digit in grid.GetCandidatesReversal(cell).GetAllSets())
                {
                    candidateOffsets.Add((0, cell * 9 + digit));
                }
            }
            if (!_allowIncompletedUr && (candidateOffsets.Count != 6 || conclusions.Count != 2))
            {
                return;
            }

            accumulator.Add(
                new UrType1TechniqueInfo(
                    conclusions,
                    views: new[]
            {
                new View(
                    cellOffsets: arMode ? GetHighlightCells(urCells) : null,
                    candidateOffsets: arMode ? null : candidateOffsets,
                    regionOffsets: null,
                    links: null)
            },
                    digit1: d1,
                    digit2: d2,
                    cells: urCells,
                    isAr: arMode));
        }
        /// <summary>
        /// Check diagonal symmetry.
        /// </summary>
        /// <param name="result">The result accumulator.</param>
        /// <param name="grid">The grid.</param>
        private void CheckDiagonal(IBag <TechniqueInfo> result, IReadOnlyGrid grid)
        {
            bool diagonalHasEmptyCell = false;

            for (int i = 0; i < 9; i++)
            {
                if (grid.GetStatus(i * 9 + i) == Empty)
                {
                    diagonalHasEmptyCell = true;
                    break;
                }
            }
            if (!diagonalHasEmptyCell)
            {
                // No conclusion.
                return;
            }

            int?[] mapping = new int?[9];
            for (int i = 0; i < 9; i++)
            {
                for (int j = 0; j < i; j++)
                {
                    int  c1        = i * 9 + j;
                    int  c2        = j * 9 + i;
                    bool condition = grid.GetStatus(c1) == Empty;
                    if (condition ^ grid.GetStatus(c2) == Empty)
                    {
                        // One of two cells is empty. Not this symmetry.
                        return;
                    }

                    if (condition)
                    {
                        continue;
                    }

                    int d1 = grid[c1], d2 = grid[c2];
                    if (d1 == d2)
                    {
                        int?o1 = mapping[d1];
                        if (o1 is null)
                        {
                            mapping[d1] = d1;
                            continue;
                        }

                        if (o1 != d1)
                        {
                            return;
                        }
                    }
                    else
                    {
                        int?o1 = mapping[d1], o2 = mapping[d2];
                        if (o1 is null ^ o2 is null)
                        {
                            return;
                        }

                        if (o1 is null && o2 is null)
                        {
                            mapping[d1] = d2;
                            mapping[d2] = d1;
                            continue;
                        }

                        // 'o1' and 'o2' are both not null.
                        if (o1 != d2 || o2 != d1)
                        {
                            return;
                        }
                    }
                }
            }

            var singleDigitList = new List <int>();

            for (int digit = 0; digit < 9; digit++)
            {
                int?mappingDigit = mapping[digit];
                if (mappingDigit is null || mappingDigit == digit)
                {
                    singleDigitList.Add(digit);
                }
            }

            var candidateOffsets = new List <(int, int)>();
            var conclusions      = new List <Conclusion>();

            for (int i = 0; i < 9; i++)
            {
                int cell = i * 9 + i;
                if (grid.GetStatus(cell) != Empty)
                {
                    continue;
                }

                foreach (int digit in grid.GetCandidatesReversal(cell).GetAllSets())
                {
                    if (singleDigitList.Contains(digit))
                    {
                        candidateOffsets.Add((0, cell * 9 + digit));
                        continue;
                    }

                    conclusions.Add(new Conclusion(Elimination, cell, digit));
                }
            }

            if (conclusions.Count == 0)
            {
                return;
            }

            result.Add(
                new GspTechniqueInfo(
                    conclusions,
                    views: new[]
            {
                new View(
                    cellOffsets: null,
                    candidateOffsets,
                    regionOffsets: null,
                    links: null)
            },
                    symmetryType: SymmetryType.Diagonal,
                    mappingTable: mapping));
        }
Exemple #5
0
        /// <inheritdoc/>
        public override void GetAll(IBag <TechniqueInfo> accumulator, IReadOnlyGrid grid)
        {
            var linkForEachRegion = (Span <short>) stackalloc short[27];
            var linkForEachDigit  = (Span <GridMap>) stackalloc GridMap[9];

            foreach (var pattern in Patterns)
            {
                var map = EmptyMap & pattern;
                if (pattern.Count < 12 && pattern.Count - map.Count > 1 || pattern.Count - map.Count > 2)
                {
                    continue;
                }

                int n = 0, count = map.Count;
                for (int digit = 0; digit < 9; digit++)
                {
                    ref var currentMap = ref linkForEachDigit[digit];
                    currentMap = CandMaps[digit] & map;
                    n         +=
                        Min(
                            currentMap.RowMask.CountSet(),
                            currentMap.ColumnMask.CountSet(),
                            currentMap.BlockMask.CountSet());
                }

                if (n == count)
                {
                    short canF             = 0;
                    var   canL             = new GridMap[9];
                    var   conclusions      = new List <Conclusion>();
                    var   candidateOffsets = new List <(int, int)>();
                    for (int digit = 0; digit < 9; digit++)
                    {
                        var   currentMap = linkForEachDigit[digit];
                        short rMask      = currentMap.RowMask;
                        short cMask      = currentMap.ColumnMask;
                        short bMask      = currentMap.BlockMask;
                        int   temp       = Min(rMask.CountSet(), cMask.CountSet(), bMask.CountSet());
                        var   elimMap    = GridMap.Empty;
                        int   check      = 0;
                        if (rMask.CountSet() == temp)
                        {
                            check++;
                            foreach (int i in rMask.GetAllSets())
                            {
                                int region = i + 9;
                                linkForEachRegion[region] |= (short)(1 << digit);
                                elimMap |= (CandMaps[digit] & RegionMaps[region] & map).PeerIntersection;
                            }
                        }
                        if (cMask.CountSet() == temp)
                        {
                            check++;
                            foreach (int i in cMask.GetAllSets())
                            {
                                int region = i + 18;
                                linkForEachRegion[region] |= (short)(1 << digit);
                                elimMap |= (CandMaps[digit] & RegionMaps[region] & map).PeerIntersection;
                            }
                        }
                        if (bMask.CountSet() == temp)
                        {
                            check++;
                            foreach (int i in bMask.GetAllSets())
                            {
                                linkForEachRegion[i] |= (short)(1 << digit);
                                elimMap |= (CandMaps[digit] & RegionMaps[i] & map).PeerIntersection;
                            }
                        }

                        if (check > 1)
                        {
                            canF |= (short)(1 << digit);
                        }

                        elimMap &= CandMaps[digit];
                        if (elimMap.IsEmpty)
                        {
                            continue;
                        }

                        foreach (int cell in elimMap)
                        {
                            if (map[cell])
                            {
                                canL[digit].Add(cell);
                            }

                            conclusions.Add(new Conclusion(Elimination, cell, digit));
                        }
                    }

                    if (conclusions.Count == 0)
                    {
                        continue;
                    }

                    for (int region = 0; region < 27; region++)
                    {
                        short linkMask = linkForEachRegion[region];
                        if (linkMask == 0)
                        {
                            continue;
                        }

                        foreach (int cell in map& RegionMaps[region])
                        {
                            short cands = (short)(grid.GetCandidatesReversal(cell) & linkMask);
                            if (cands == 0)
                            {
                                continue;
                            }

                            foreach (int cand in cands.GetAllSets())
                            {
                                if (canL[cand][cell])
                                {
                                    continue;
                                }

                                candidateOffsets.Add((
                                                         true switch
                                {
                                    _ when region < 9 => 2,
                                    _ when region < 18 => 0,
                                    _ => 1
                                }, cell * 9 + cand));
Exemple #6
0
        /// <inheritdoc/>
        public override void GetAll(IBag <TechniqueInfo> accumulator, IReadOnlyGrid grid)
        {
            var compatibleCellsPlayground = (Span <int>) stackalloc int[4];
            var cover = (Span <int>) stackalloc int[8];

            foreach (var exocet in Exocets)
            {
                var(baseMap, targetMap, _) = exocet;
                var(b1, b2, tq1, tq2, tr1, tr2, s, mq1, mq2, mr1, mr2) = exocet;
                if (grid.GetCandidatesReversal(b1).CountSet() < 2 ||
                    grid.GetCandidatesReversal(b2).CountSet() < 2)
                {
                    continue;
                }

                var baseCellsMap = new GridMap {
                    b1, b2
                };
                bool isRow            = baseCellsMap.CoveredLine < 18;
                var  tempCrosslineMap = new GridMap(s)
                {
                    tq1, tq2, tr1, tr2
                };
                short baseCandidatesMask = (short)(grid.GetCandidatesReversal(b1) | grid.GetCandidatesReversal(b2));

                int i = 0;
                int r = GetRegion(b1, RegionLabel.Row) - 9, c = GetRegion(b1, RegionLabel.Column) - 18;
                foreach (int pos in ((short)(511 & ~(1 << (isRow ? r : c)))).GetAllSets())
                {
                    cover[i++] = isRow ? pos : pos + 9;
                }

                i = 0;
                var temp = default(GridMap);
                foreach (int digit in baseCandidatesMask.GetAllSets())
                {
                    if (i++ == 0)
                    {
                        temp = ValueMaps[digit];
                    }
                    else
                    {
                        temp |= ValueMaps[digit];
                    }
                }
                temp &= tempCrosslineMap;

                var tempTarget = new List <int>();
                for (i = 0; i < 8; i++)
                {
                    var check = temp & RegionMaps[cover[i] + 9];
                    if (check.Count != 1)
                    {
                        continue;
                    }

                    tempTarget.Add(check.SetAt(0));
                }
                if (tempTarget.Count == 0)
                {
                    continue;
                }

                int borT = isRow ? b1 / 9 / 3 : b1 % 9 / 3;                 // Base or target (B or T).
                foreach (int[] combination in GetCombinationsOfArray(tempTarget.ToArray(), 2))
                {
                    if (isRow
                                                ? combination[0] / 9 / 3 == borT && combination[1] / 9 / 3 == borT
                                                : combination[0] % 9 / 3 == borT && combination[1] % 9 / 3 == borT)
                    {
                        continue;
                    }

                    int row1    = GetRegion(combination[0], RegionLabel.Row);
                    int column1 = GetRegion(combination[0], RegionLabel.Column);
                    int row2    = GetRegion(combination[1], RegionLabel.Row);
                    int column2 = GetRegion(combination[1], RegionLabel.Column);
                    if (isRow ? column1 == column2 : row1 == row2)
                    {
                        continue;
                    }

                    short elimDigits = (short)((
                                                   grid.GetCandidatesReversal(combination[0])
                                                   | grid.GetCandidatesReversal(combination[1])) & ~baseCandidatesMask);
                    if (!CheckCrossline(
                            /*baseCellsMap, */ tempCrosslineMap, ValueMaps, baseCandidatesMask,
                            combination[0], combination[1], isRow, out int extraRegionsMask))
                    {
                        continue;
                    }

                    var   targetElims = new TargetEliminations();
                    short cands       = (short)(elimDigits & grid.GetCandidatesReversal(combination[0]));
                    if (cands != 0)
                    {
                        foreach (int digit in cands.GetAllSets())
                        {
                            targetElims.Add(new Conclusion(Elimination, combination[0], digit));
                        }
                    }
                    cands = (short)(elimDigits & grid.GetCandidatesReversal(combination[1]));
                    if (cands != 0)
                    {
                        foreach (int digit in cands.GetAllSets())
                        {
                            targetElims.Add(new Conclusion(Elimination, combination[1], digit));
                        }
                    }

                    short tbCands = 0;
                    for (int j = 0; j < 2; j++)
                    {
                        if (grid.GetCandidatesReversal(combination[j]).CountSet() == 1)
                        {
                            tbCands |= grid.GetCandidatesReversal(combination[j]);
                        }
                    }

                    var trueBaseElims = new TrueBaseEliminations();
                    if (tbCands != 0 &&
                        (grid.GetStatus(combination[0]) != Empty || grid.GetStatus(combination[1]) != Empty))
                    {
                        for (int j = 0; j < 2; j++)
                        {
                            if (grid.GetStatus(combination[j]) != Empty)
                            {
                                continue;
                            }

                            if ((cands = (short)(grid.GetCandidatesReversal(combination[j]) & tbCands)) == 0)
                            {
                                continue;
                            }

                            foreach (int digit in cands.GetAllSets())
                            {
                                trueBaseElims.Add(new Conclusion(Elimination, combination[j], digit));
                            }
                        }
                    }

                    if (tbCands != 0)
                    {
                        foreach (int digit in tbCands.GetAllSets())
                        {
                            var elimMap = (baseCellsMap & CandMaps[digit]).PeerIntersection & CandMaps[digit];
                            if (elimMap.IsEmpty)
                            {
                                continue;
                            }

                            foreach (int cell in elimMap)
                            {
                                trueBaseElims.Add(new Conclusion(Elimination, cell, digit));
                            }
                        }
                    }

                    var mirrorElims        = new MirrorEliminations();
                    var compatibilityElims = new CompatibilityTestEliminations();
                    int target             = -1;
                    var mir = default(GridMap);

                    var cellOffsets = new List <(int, int)> {
                        (0, b1), (0, b2)
                    };
                    foreach (int cell in tempCrosslineMap)
                    {
                        cellOffsets.Add((cell == combination[0] || cell == combination[1] ? 1 : 2, cell));
                    }
                    var candidateOffsets = new List <(int, int)>();
                    if (_checkAdvanced)
                    {
                        for (int k = 0; k < 2; k++)
                        {
                            if (combination[k] == tq1 && (baseCandidatesMask & grid.GetCandidatesReversal(tr2)) == 0)
                            {
                                target = combination[k];
                                mir    = mq1;
                            }
                            if (combination[k] == tq2 && (baseCandidatesMask & grid.GetCandidatesReversal(tr1)) == 0)
                            {
                                target = combination[k];
                                mir    = mq2;
                            }
                            if (combination[k] == tr1 && (baseCandidatesMask & grid.GetCandidatesReversal(tq2)) == 0)
                            {
                                target = combination[k];
                                mir    = mr1;
                            }
                            if (combination[k] == tr2 && (baseCandidatesMask & grid.GetCandidatesReversal(tq1)) == 0)
                            {
                                target = combination[k];
                                mir    = mr2;
                            }
                        }

                        if (target != -1)
                        {
                            var(tempTargetElims, tempMirrorElims) = CheckMirror(
                                grid, target, combination[target == combination[0] ? 1 : 0], 0,
                                baseCandidatesMask, mir, 0, -1, cellOffsets, candidateOffsets);
                            targetElims = TargetEliminations.MergeAll(targetElims, tempTargetElims);
                            mirrorElims = MirrorEliminations.MergeAll(mirrorElims, tempMirrorElims);
                        }

                        short incompatible = CompatibilityTest(
                            baseCandidatesMask, ValueMaps, tempCrosslineMap, baseCellsMap,
                            combination[0], combination[1]);
                        if (incompatible != 0)
                        {
                            compatibleCellsPlayground[0] = b1;
                            compatibleCellsPlayground[1] = b2;
                            compatibleCellsPlayground[2] = combination[0];
                            compatibleCellsPlayground[3] = combination[1];

                            for (int k = 0; k < 4; k++)
                            {
                                cands = (short)(incompatible & grid.GetCandidatesReversal(compatibleCellsPlayground[k]));
                                if (cands == 0)
                                {
                                    continue;
                                }

                                foreach (int digit in cands.GetAllSets())
                                {
                                    compatibilityElims.Add(
                                        new Conclusion(Elimination, compatibleCellsPlayground[k], digit));
                                }
                            }
                        }

                        CompatibilityTest2(
                            grid, ref compatibilityElims, baseCellsMap, baseCandidatesMask,
                            combination[0], combination[1]);
                    }

                    if (_checkAdvanced
                                                ? mirrorElims.Count == 0 && compatibilityElims.Count == 0 &&
                        targetElims.Count == 0 && trueBaseElims.Count == 0
                                                : mirrorElims.Count == 0 && compatibilityElims.Count == 0)
                    {
                        continue;
                    }

                    int   endoTargetCell = combination[s[combination[0]] ? 0 : 1];
                    short m1             = grid.GetCandidatesReversal(b1);
                    short m2             = grid.GetCandidatesReversal(b2);
                    short m = (short)(m1 | m2);
                    foreach (int digit in m1.GetAllSets())
                    {
                        candidateOffsets.Add((0, b1 * 9 + digit));
                    }
                    foreach (int digit in m2.GetAllSets())
                    {
                        candidateOffsets.Add((0, b2 * 9 + digit));
                    }

                    // Record extra region cells (mutant exocets).
                    foreach (int region in extraRegionsMask.GetAllSets())
                    {
                        foreach (int cell in RegionCells[region])
                        {
                            if (tempCrosslineMap[cell] || b1 == cell || b2 == cell)
                            {
                                continue;
                            }

                            cellOffsets.Add((2, cell));
                        }
                    }

                    accumulator.Add(
                        new SeniorExocetTechniqueInfo(
                            conclusions: new List <Conclusion>(),
                            views: new[]
                    {
                        new View(
                            cellOffsets,
                            candidateOffsets,
                            regionOffsets: null,
                            links: null)
                    },
                            exocet,
                            digits: m.GetAllSets(),
                            endoTargetCell,
                            targetEliminations: targetElims,
                            trueBaseEliminations: trueBaseElims,
                            mirrorEliminations: mirrorElims,
                            compatibilityEliminations: compatibilityElims));
                }
            }
        }
        public override void GetAll(IBag <TechniqueInfo> accumulator, IReadOnlyGrid grid)
        {
            var rccs  = new List <(Als _left, Als _right, short _mask)>();
            var alses = Als.GetAllAlses(grid).ToArray();

            // Gather all RCCs.
            for (int i = 0, length = alses.Length; i < length - 1; i++)
            {
                var als1 = alses[i];
                var(_, _, mask1, map1, _, _) = als1;
                for (int j = i + 1; j < length; j++)
                {
                    var als2 = alses[j];
                    var(_, _, mask2, map2, _, _) = als2;
                    var map = map1 | map2;
                    if (map.AllSetsAreInOneRegion(out _) || map1.Overlaps(map2))
                    {
                        continue;
                    }

                    short mask = (short)(mask1 & mask2);
                    if (mask == 0)
                    {
                        continue;
                    }

                    short rccMask = 0;
                    foreach (int digit in mask.GetAllSets())
                    {
                        if ((map & CandMaps[digit]).AllSetsAreInOneRegion(out _))
                        {
                            rccMask |= (short)(1 << digit);
                        }
                    }
                    if (rccMask == 0)
                    {
                        continue;
                    }

                    rccs.Add((als1, als2, rccMask));
                }
            }

            // Now check them.
            for (int i = 0, count = rccs.Count; i < count - 1; i++)
            {
                var(als11, als12, mask1) = rccs[i];
                for (int j = i + 1; j < count; j++)
                {
                    var(als21, als22, mask2) = rccs[j];
                    if (mask1 == mask2 && mask1.IsPowerOfTwo() && mask2.IsPowerOfTwo())
                    {
                        // Cannot form a XY-Wing.
                        continue;
                    }

                    if (!(als11 == als21 ^ als12 == als22 || als11 == als22 ^ als12 == als21))
                    {
                        continue;
                    }

                    // Get the logical order of three ALSes.
                    var(a, b, c) = true switch
                    {
                        _ when als11 == als21 => (als12, als22, als11),
                        _ when als11 == als22 => (als12, als21, als11),
                        _ when als12 == als21 => (als11, als22, als12),
                        _ => (als11, als21, als12)
                    };

                    var(_, aRegion, aMask, aMap, _, _) = a;
                    var(_, bRegion, bMask, bMap, _, _) = b;
                    var(_, cRegion, _, cMap, _, _)     = c;
                    var map = aMap | bMap;
                    if (map == aMap || map == bMap)
                    {
                        continue;
                    }

                    if (!_allowOverlapping && (aMap.Overlaps(bMap) || aMap.Overlaps(cMap) || bMap.Overlaps(cMap)))
                    {
                        continue;
                    }

                    foreach (int digit1 in mask1.GetAllSets())
                    {
                        foreach (int digit2 in mask2.GetAllSets())
                        {
                            if (digit1 == digit2)
                            {
                                continue;
                            }

                            short finalX     = (short)(1 << digit1);
                            short finalY     = (short)(1 << digit2);
                            short digitsMask = (short)(aMask & bMask & ~(finalX | finalY));
                            if (digitsMask == 0)
                            {
                                continue;
                            }

                            // Gather eliminations.
                            short finalZ      = 0;
                            var   conclusions = new List <Conclusion>();
                            foreach (int digit in digitsMask.GetAllSets())
                            {
                                var elimMap = (
                                    ((aMap | bMap) & CandMaps[digit]).PeerIntersection
                                    & CandMaps[digit]) - (aMap | bMap | cMap);
                                if (elimMap.IsEmpty)
                                {
                                    continue;
                                }

                                finalZ |= (short)(1 << digit);
                                foreach (int cell in elimMap)
                                {
                                    conclusions.Add(new Conclusion(Elimination, cell, digit));
                                }
                            }
                            if (conclusions.Count == 0)
                            {
                                continue;
                            }

                            // Record highlight candidates and cells.
                            var cellOffsets      = new List <(int, int)>();
                            var candidateOffsets = new List <(int, int)>();
                            foreach (int cell in aMap)
                            {
                                short mask          = grid.GetCandidatesReversal(cell);
                                short alsDigitsMask = (short)(mask & ~(finalX | finalZ));
                                short xDigitsMask   = (short)(mask & (finalX));
                                short zDigitsMask   = (short)(mask & finalZ);
                                foreach (int digit in alsDigitsMask.GetAllSets())
                                {
                                    candidateOffsets.Add((-1, cell * 9 + digit));
                                }
                                foreach (int digit in xDigitsMask.GetAllSets())
                                {
                                    candidateOffsets.Add((1, cell * 9 + digit));
                                }
                                foreach (int digit in zDigitsMask.GetAllSets())
                                {
                                    candidateOffsets.Add((2, cell * 9 + digit));
                                }
                            }
                            foreach (int cell in bMap)
                            {
                                short mask          = grid.GetCandidatesReversal(cell);
                                short alsDigitsMask = (short)(mask & ~(finalY | finalZ));
                                short yDigitsMask   = (short)(mask & finalY);
                                short zDigitsMask   = (short)(mask & finalZ);
                                foreach (int digit in alsDigitsMask.GetAllSets())
                                {
                                    candidateOffsets.Add((-1, cell * 9 + digit));
                                }
                                foreach (int digit in yDigitsMask.GetAllSets())
                                {
                                    candidateOffsets.Add((1, cell * 9 + digit));
                                }
                                foreach (int digit in zDigitsMask.GetAllSets())
                                {
                                    candidateOffsets.Add((2, cell * 9 + digit));
                                }
                            }
                            foreach (int cell in cMap)
                            {
                                short mask          = grid.GetCandidatesReversal(cell);
                                short alsDigitsMask = (short)(mask & ~(finalX | finalY));
                                short xyDigitsMask  = (short)(mask & (finalX | finalY));
                                foreach (int digit in alsDigitsMask.GetAllSets())
                                {
                                    candidateOffsets.Add((-3, cell * 9 + digit));
                                }
                                foreach (int digit in xyDigitsMask.GetAllSets())
                                {
                                    candidateOffsets.Add((1, cell * 9 + digit));
                                }
                            }

                            accumulator.Add(
                                new AlsXyWingTechniqueInfo(
                                    conclusions,
                                    views: new[]
        private void CheckType1(
            IBag <TechniqueInfo> accumulator, IReadOnlyGrid grid, Pattern pattern, short cornerMask1,
            short cornerMask2, short centerMask, GridMap map)
        {
            short orMask = (short)((short)(cornerMask1 | cornerMask2) | centerMask);

            if (orMask.CountSet() != (pattern.IsHeptagon ? 4 : 5))
            {
                return;
            }

            // Iterate on each combination.
            foreach (int[] digits in GetCombinationsOfArray(orMask.GetAllSets().ToArray(), pattern.IsHeptagon ? 3 : 4))
            {
                short tempMask = 0;
                foreach (int digit in digits)
                {
                    tempMask |= (short)(1 << digit);
                }

                int otherDigit             = (orMask & ~tempMask).FindFirstSet();
                var mapContainingThatDigit = map & CandMaps[otherDigit];
                if (mapContainingThatDigit.Count != 1)
                {
                    continue;
                }

                int   elimCell = mapContainingThatDigit.SetAt(0);
                short elimMask = (short)(grid.GetCandidatesReversal(elimCell) & tempMask);
                if (elimMask == 0)
                {
                    continue;
                }

                var conclusions = new List <Conclusion>();
                foreach (int digit in elimMask.GetAllSets())
                {
                    conclusions.Add(new Conclusion(Elimination, elimCell, digit));
                }

                var candidateOffsets = new List <(int, int)>();
                foreach (int cell in map)
                {
                    if (mapContainingThatDigit[cell])
                    {
                        continue;
                    }

                    foreach (int digit in grid.GetCandidatesReversal(cell).GetAllSets())
                    {
                        candidateOffsets.Add((0, cell * 9 + digit));
                    }
                }

                accumulator.Add(
                    new BdpType1TechniqueInfo(
                        conclusions,
                        views: new[]
                {
                    new View(
                        cellOffsets: null,
                        candidateOffsets,
                        regionOffsets: null,
                        links: null)
                },
                        digitsMask: tempMask,
                        map));
            }
        }
        private void CheckType3(
            IBag <TechniqueInfo> accumulator, IReadOnlyGrid grid, Pattern pattern, short cornerMask1,
            short cornerMask2, short centerMask, GridMap map)
        {
            short orMask = (short)((short)(cornerMask1 | cornerMask2) | centerMask);

            foreach (int region in map.Regions)
            {
                var   currentMap    = RegionMaps[region] & map;
                var   otherCellsMap = map - currentMap;
                short currentMask   = BitwiseOrMasks(grid, currentMap);
                short otherMask     = BitwiseOrMasks(grid, otherCellsMap);

                foreach (int[] digits in
                         GetCombinationsOfArray(orMask.GetAllSets().ToArray(), pattern.IsHeptagon ? 3 : 4))
                {
                    short tempMask = 0;
                    foreach (int digit in digits)
                    {
                        tempMask |= (short)(1 << digit);
                    }
                    if (otherMask != tempMask)
                    {
                        continue;
                    }

                    // Iterate on the cells by the specified size.
                    var   iterationCellsMap = (RegionMaps[region] - currentMap) & EmptyMap;
                    int[] iterationCells    = iterationCellsMap.ToArray();
                    short otherDigitsMask   = (short)(orMask & ~tempMask);
                    for (int size = otherDigitsMask.CountSet() - 1; size < iterationCellsMap.Count; size++)
                    {
                        foreach (int[] combination in GetCombinationsOfArray(iterationCells, size))
                        {
                            short comparer = 0;
                            foreach (int cell in combination)
                            {
                                comparer |= grid.GetCandidatesReversal(cell);
                            }
                            if ((tempMask & comparer) != 0 || tempMask.CountSet() - 1 != size ||
                                (tempMask & otherDigitsMask) != otherDigitsMask)
                            {
                                continue;
                            }

                            // Type 3 found.
                            // Now check eliminations.
                            var conclusions = new List <Conclusion>();
                            foreach (int digit in comparer.GetAllSets())
                            {
                                var cells = iterationCellsMap & CandMaps[digit];
                                if (cells.IsEmpty)
                                {
                                    continue;
                                }

                                foreach (int cell in cells)
                                {
                                    conclusions.Add(new Conclusion(Elimination, cell, digit));
                                }
                            }

                            if (conclusions.Count == 0)
                            {
                                continue;
                            }

                            var candidateOffsets = new List <(int, int)>();
                            foreach (int cell in currentMap)
                            {
                                foreach (int digit in grid.GetCandidatesReversal(cell).GetAllSets())
                                {
                                    candidateOffsets.Add(((tempMask >> digit & 1) != 0 ? 1 : 0, cell * 9 + digit));
                                }
                            }
                            foreach (int cell in otherCellsMap)
                            {
                                foreach (int digit in grid.GetCandidatesReversal(cell).GetAllSets())
                                {
                                    candidateOffsets.Add((0, cell * 9 + digit));
                                }
                            }
                            foreach (int cell in combination)
                            {
                                foreach (int digit in grid.GetCandidatesReversal(cell).GetAllSets())
                                {
                                    candidateOffsets.Add((1, cell * 9 + digit));
                                }
                            }

                            accumulator.Add(
                                new BdpType3TechniqueInfo(
                                    conclusions,
                                    views: new[]
Exemple #10
0
        /// <summary>
        /// To search for all ALSes in the specified grid.
        /// </summary>
        /// <param name="grid">The grid.</param>
        /// <returns>All ALSes searched.</returns>
        public static IEnumerable <Als> GetAllAlses(IReadOnlyGrid grid)
        {
            var bivalueMap = TechniqueSearcher.BivalueMap;
            var emptyMap   = TechniqueSearcher.EmptyMap;

            // Get all bi-value-cell ALSes.
            foreach (int cell in bivalueMap)
            {
                yield return(new Als(
                                 grid.GetCandidatesReversal(cell), new GridMap {
                    cell
                }, new GridMap(cell, false) & emptyMap));
            }

            // Get all non-bi-value-cell ALSes.
            var list = new List <int>();

            for (int region = 0; region < 27; region++)
            {
                var regionMap = RegionMaps[region];
                var tempMap   = regionMap & emptyMap;
                if (tempMap.Count < 3)
                {
                    continue;
                }

                int[] emptyCells = tempMap.ToArray();
                list.Clear();
                list.AddRange(emptyCells);
                for (int size = 2; size <= emptyCells.Length - 1; size++)
                {
                    foreach (int[] cells in Algorithms.GetCombinationsOfArray(list.ToArray(), size))
                    {
                        var map = new GridMap(cells);
                        if (map.BlockMask.CountSet() == 1 && region >= 9)
                        {
                            // All ALS cells lying on a box-row or a box-column
                            // will be processed as a block ALS.
                            continue;
                        }

                        // Get all candidates in these cells.
                        short digitsMask = 0;
                        foreach (int cell in cells)
                        {
                            digitsMask |= grid.GetCandidatesReversal(cell);
                        }
                        if (digitsMask.CountSet() - 1 != size)
                        {
                            continue;
                        }

                        int coveredLine = map.CoveredLine;
                        yield return(new Als(
                                         digitsMask,
                                         map,
                                         region < 9 && coveredLine >= 9
                                                                ? ((regionMap | RegionMaps[coveredLine]) & emptyMap) - map
                                                                : tempMap - map));
                    }
                }
            }
        }
Exemple #11
0
        /// <inheritdoc/>
        public override void GetAll(IBag <TechniqueInfo> accumulator, IReadOnlyGrid grid)
        {
            var house = (Span <int>) stackalloc int[2];
            var alses = Als.GetAllAlses(grid).ToArray();

            for (int i = 0, length = alses.Length; i < length - 1; i++)
            {
                var als1 = alses[i];
                var(isBivalue1, region1, mask1, map1, possibleElimMap1, _) = als1;
                for (int j = i + 1; j < length; j++)
                {
                    var als2 = alses[j];
                    var(isBivalue2, region2, mask2, map2, possibleElimMap2, _) = als2;
                    short xzMask     = (short)(mask1 & mask2);
                    var   map        = map1 | map2;
                    var   overlapMap = map1 & map2;
                    if (!_allowOverlapping && overlapMap.IsNotEmpty)
                    {
                        continue;
                    }

                    // Remove all digits to satisfy that the RCC digit cannot appear
                    // in the intersection of two ALSes.
                    if (overlapMap.IsNotEmpty)
                    {
                        foreach (int cell in overlapMap)
                        {
                            xzMask &= (short)~grid.GetCandidatesReversal(cell);
                        }
                    }

                    // If the number of digits that both two ALSes contain is only one (or zero),
                    // the ALS-XZ will not be formed.
                    if (xzMask.CountSet() < 2 || map.AllSetsAreInOneRegion(out int region))
                    {
                        continue;
                    }

                    short rccMask = 0, z = 0;
                    int   nh = 0;
                    foreach (int digit in xzMask.GetAllSets())
                    {
                        if ((map & CandMaps[digit]).AllSetsAreInOneRegion(out region))
                        {
                            // 'digit' is the RCC digit.
                            rccMask    |= (short)(1 << digit);
                            house[nh++] = region;
                        }
                        else
                        {
                            // 'digit' is the eliminating digit.
                            z |= (short)(1 << digit);
                        }
                    }

                    if (rccMask == 0 || rccMask.IsPowerOfTwo() && z == 0)
                    {
                        continue;
                    }

                    // Check basic eliminations.
                    bool? isDoublyLinked = false;
                    short finalZ         = 0;
                    var   conclusions    = new List <Conclusion>();
                    foreach (int elimDigit in z.GetAllSets())
                    {
                        var elimMap = (CandMaps[elimDigit] & map).PeerIntersection & CandMaps[elimDigit];
                        if (elimMap.IsEmpty)
                        {
                            continue;
                        }

                        foreach (int cell in elimMap)
                        {
                            conclusions.Add(new Conclusion(Elimination, cell, elimDigit));
                        }

                        finalZ |= (short)(1 << elimDigit);
                    }

                    if (_allowAlsCycles && rccMask.CountSet() == 2)
                    {
                        // Doubly linked ALS-XZ.
                        isDoublyLinked = true;
                        foreach (int elimDigit in (z & ~rccMask).GetAllSets())
                        {
                            var zMap = CandMaps[elimDigit] & map1;
                            if (zMap.IsEmpty)
                            {
                                continue;
                            }

                            var elimMap = zMap.PeerIntersection & CandMaps[elimDigit] & map2;
                            if (elimMap.IsEmpty)
                            {
                                continue;
                            }

                            finalZ |= (short)(1 << elimDigit);
                        }

                        // RCC digit 2 eliminations.
                        int k = 0;
                        foreach (int digit in rccMask.GetAllSets())
                        {
                            var elimMap = (RegionMaps[house[k]] & CandMaps[digit]) - map;
                            if (elimMap.IsNotEmpty)
                            {
                                foreach (int cell in elimMap)
                                {
                                    if (grid.Exists(cell, digit) is true)
                                    {
                                        conclusions.Add(new Conclusion(Elimination, cell, digit));
                                    }
                                }
                            }

                            k++;
                        }

                        // Possible eliminations.
                        var tempMap = map;
                        tempMap = CandMaps[mask1.FindFirstSet()];
                        for (k = 1; k < mask1.CountSet(); k++)
                        {
                            tempMap |= CandMaps[mask1.SetAt(k)];
                        }
                        tempMap &= possibleElimMap1;
                        if (tempMap.IsNotEmpty)
                        {
                            foreach (int cell in tempMap)
                            {
                                foreach (int digit in
                                         (grid.GetCandidatesReversal(cell) & (mask1 & ~rccMask)).GetAllSets())
                                {
                                    conclusions.Add(new Conclusion(Elimination, cell, digit));
                                }
                            }
                        }
                        tempMap = CandMaps[mask2.FindFirstSet()];
                        for (k = 1; k < mask2.CountSet(); k++)
                        {
                            tempMap |= CandMaps[mask2.SetAt(k)];
                        }
                        tempMap &= possibleElimMap2;
                        if (tempMap.IsNotEmpty)
                        {
                            foreach (int cell in tempMap)
                            {
                                foreach (int digit in
                                         (grid.GetCandidatesReversal(cell) & (mask2 & ~rccMask)).GetAllSets())
                                {
                                    conclusions.Add(new Conclusion(Elimination, cell, digit));
                                }
                            }
                        }
                    }

                    if (conclusions.Count == 0)
                    {
                        continue;
                    }

                    // Now record highlight elements.
                    bool isEsp            = als1.IsBivalueCell || als2.IsBivalueCell;
                    var  candidateOffsets = new List <(int, int)>();
                    var  cellOffsets      = new List <(int, int)>();
                    if (isEsp)
                    {
                        foreach (int cell in map)
                        {
                            foreach (int digit in grid.GetCandidatesReversal(cell).GetAllSets())
                            {
                                candidateOffsets.Add((finalZ >> digit & 1, cell * 9 + digit));
                            }
                        }
                    }
                    else
                    {
                        foreach (int cell in map1)
                        {
                            short mask             = grid.GetCandidatesReversal(cell);
                            short alsDigitsMask    = (short)(mask & ~(finalZ | rccMask));
                            short targetDigitsMask = (short)(mask & finalZ);
                            short rccDigitsMask    = (short)(mask & rccMask);
                            foreach (int digit in alsDigitsMask.GetAllSets())
                            {
                                candidateOffsets.Add((-1, cell * 9 + digit));
                            }
                            foreach (int digit in targetDigitsMask.GetAllSets())
                            {
                                candidateOffsets.Add((2, cell * 9 + digit));
                            }
                            foreach (int digit in rccDigitsMask.GetAllSets())
                            {
                                candidateOffsets.Add((1, cell * 9 + digit));
                            }
                        }
                        foreach (int cell in map2)
                        {
                            short mask             = grid.GetCandidatesReversal(cell);
                            short alsDigitsMask    = (short)(mask & ~(finalZ | rccMask));
                            short targetDigitsMask = (short)(mask & finalZ);
                            short rccDigitsMask    = (short)(mask & rccMask);
                            foreach (int digit in alsDigitsMask.GetAllSets())
                            {
                                candidateOffsets.Add((-2, cell * 9 + digit));
                            }
                            foreach (int digit in targetDigitsMask.GetAllSets())
                            {
                                candidateOffsets.Add((2, cell * 9 + digit));
                            }
                            foreach (int digit in rccDigitsMask.GetAllSets())
                            {
                                candidateOffsets.Add((1, cell * 9 + digit));
                            }
                        }
                    }

                    accumulator.Add(
                        new AlsXzTechniqueInfo(
                            conclusions,
                            views: new[]
        /// <inheritdoc/>
        public override void GetAll(IBag <TechniqueInfo> accumulator, IReadOnlyGrid grid)
        {
            var pairs      = (Span <short>) stackalloc short[8];
            var tempLink   = (Span <short>) stackalloc short[8];
            var linkRegion = (Span <int>) stackalloc int[8];

            foreach (int[] cells in SkLoopTable)
            {
                int n = 0, candidateCount = 0, i = 0;
                for (i = 0; i < 8; i++)
                {
                    pairs[i]      = default;
                    linkRegion[i] = default;
                }
                for (i = 0; i < 8; i++)
                {
                    if (grid.GetStatus(cells[i << 1]) != Empty)
                    {
                        n++;
                    }
                    else
                    {
                        pairs[i] |= grid.GetCandidatesReversal(cells[i << 1]);
                    }

                    if (grid.GetStatus(cells[(i << 1) + 1]) != Empty)
                    {
                        n++;
                    }
                    else
                    {
                        pairs[i] |= grid.GetCandidatesReversal(cells[(i << 1) + 1]);
                    }

                    if (n > 4 || pairs[i].CountSet() > 5 || pairs[i] == 0)
                    {
                        break;
                    }

                    candidateCount += pairs[i].CountSet();
                }

                if (i < 8 || candidateCount > 32 - (n << 1))
                {
                    continue;
                }

                short candidateMask = (short)(pairs[0] & pairs[1]);
                if (candidateMask == 0)
                {
                    continue;
                }

                short[] masks       = GetCombinations(candidateMask).ToArray();
                for (int j = masks.Length - 1; j >= 0; j--)
                {
                    short mask = masks[j];
                    if (mask == 0)
                    {
                        continue;
                    }

                    for (int p = 0; p < 8; p++)
                    {
                        tempLink[p] = default;
                    }

                    int linkCount = (tempLink[0] = mask).CountSet();
                    int k         = 1;
                    for (; k < 8; k++)
                    {
                        candidateMask = (short)(tempLink[k - 1] ^ pairs[k]);
                        if ((candidateMask & pairs[(k + 1) % 8]) != candidateMask)
                        {
                            break;
                        }

                        linkCount += (tempLink[k] = candidateMask).CountSet();
                    }

                    if (k < 8 || linkCount != 16 - n)
                    {
                        continue;
                    }

                    linkRegion[0] = GetRegion(cells[0], RegionLabel.Row);
                    linkRegion[1] = GetRegion(cells[2], RegionLabel.Block);
                    linkRegion[2] = GetRegion(cells[4], RegionLabel.Column);
                    linkRegion[3] = GetRegion(cells[6], RegionLabel.Block);
                    linkRegion[4] = GetRegion(cells[8], RegionLabel.Row);
                    linkRegion[5] = GetRegion(cells[10], RegionLabel.Block);
                    linkRegion[6] = GetRegion(cells[12], RegionLabel.Column);
                    linkRegion[7] = GetRegion(cells[14], RegionLabel.Block);

                    var conclusions = new List <Conclusion>();
                    var map         = new GridMap(cells) & EmptyMap;
                    for (k = 0; k < 8; k++)
                    {
                        var elimMap = (RegionMaps[linkRegion[k]] & EmptyMap) - map;
                        if (elimMap.IsEmpty)
                        {
                            continue;
                        }

                        foreach (int cell in elimMap)
                        {
                            short cands = (short)(grid.GetCandidatesReversal(cell) & tempLink[k]);
                            if (cands != 0)
                            {
                                foreach (int digit in cands.GetAllSets())
                                {
                                    conclusions.Add(new Conclusion(Elimination, cell, digit));
                                }
                            }
                        }
                    }

                    if (conclusions.Count == 0)
                    {
                        continue;
                    }

                    var     candidateOffsets = new List <(int, int)>();
                    short[] link             = new short[27];
                    for (k = 0; k < 8; k++)
                    {
                        link[linkRegion[k]] = tempLink[k];
                        foreach (int cell in map& RegionMaps[linkRegion[k]])
                        {
                            short cands = (short)(grid.GetCandidatesReversal(cell) & tempLink[k]);
                            if (cands == 0)
                            {
                                continue;
                            }

                            foreach (int digit in cands.GetAllSets())
                            {
                                candidateOffsets.Add((
                                                         true switch
                                {
                                    _ when(k & 3) == 0 => 1,
                                    _ when(k & 1) == 1 => 2,
                                    _ => 0
                                }, cell * 9 + digit));
Exemple #13
0
        /// <inheritdoc/>
        public override void GetAll(IBag <TechniqueInfo> accumulator, IReadOnlyGrid grid)
        {
            var alses = Als.GetAllAlses(grid).ToArray();

            // Gather all conjugate pairs.
            var conjugatePairs = new ICollection <ConjugatePair>?[9];

            for (int digit = 0; digit < 9; digit++)
            {
                for (int region = 0; region < 27; region++)
                {
                    var temp = RegionMaps[region] & CandMaps[digit];
                    if (temp.Count != 2)
                    {
                        continue;
                    }

                    (conjugatePairs[digit] ??= new List <ConjugatePair>()).Add(new ConjugatePair(temp, digit));
                }
            }

            // Iterate on each ALS.
            for (int i = 0, length = alses.Length; i < length - 1; i++)
            {
                var als1 = alses[i];
                var(_, region1, mask1, map1, _, _) = als1;
                for (int j = i + 1; j < length; j++)
                {
                    var als2 = alses[j];
                    var(_, region2, mask2, map2, _, _) = als2;
                    if (map1.Overlaps(map2) || (map1 | map2).AllSetsAreInOneRegion(out _))
                    {
                        continue;
                    }

                    short mask = (short)(mask1 & mask2);
                    if (mask.CountSet() < 2)
                    {
                        continue;
                    }

                    foreach (int x in mask.GetAllSets())
                    {
                        if ((conjugatePairs[x]?.Count ?? 0) == 0)
                        {
                            continue;
                        }

                        var p1 = (map1 & CandMaps[x]).PeerIntersection & CandMaps[x];
                        var p2 = (map2 & CandMaps[x]).PeerIntersection & CandMaps[x];
                        if (p1.IsEmpty || p2.IsEmpty)
                        {
                            // At least one of two ALSes cannot see the node of the conjugate pair.
                            continue;
                        }

                        // Iterate on each conjugate pair.
                        short wDigitsMask = 0;
                        var   conclusions = new List <Conclusion>();
                        foreach (var conjugatePair in conjugatePairs[x] ?? Array.Empty <ConjugatePair>())
                        {
                            var cpMap = conjugatePair.Map;
                            if (cpMap.Overlaps(map1) || cpMap.Overlaps(map2))
                            {
                                // Conjugate pair cannot overlap with the ALS structure.
                                continue;
                            }

                            if ((cpMap & p1).Count != 1 || (cpMap & p2).Count != 1 || ((p1 | p2) & cpMap).Count != 2)
                            {
                                continue;
                            }

                            foreach (int w in (mask & ~(1 << x)).GetAllSets())
                            {
                                var tempMap = ((map1 | map2) & CandMaps[w]).PeerIntersection & CandMaps[w];
                                if (tempMap.IsEmpty)
                                {
                                    continue;
                                }

                                wDigitsMask |= (short)(1 << w);
                                foreach (int cell in tempMap)
                                {
                                    conclusions.Add(new Conclusion(Elimination, cell, w));
                                }
                            }

                            if (conclusions.Count == 0)
                            {
                                continue;
                            }

                            // Record highlight cell and candidate offsets.
                            var cellOffsets = new List <(int, int)>();
                            cellOffsets.AddRange(from cell in map1 select(-1, cell));
                            cellOffsets.AddRange(from cell in map2 select(-2, cell));

                            var candidateOffsets = new List <(int, int)>
                            {
                                (0, cpMap.SetAt(0) * 9 + x), (0, cpMap.SetAt(1) * 9 + x)
                            };
                            foreach (int cell in map1)
                            {
                                foreach (int digit in grid.GetCandidatesReversal(cell).GetAllSets())
                                {
                                    candidateOffsets.Add((
                                                             true switch
                                    {
                                        _ when digit == x => 1,
                                        _ when(wDigitsMask >> digit & 1) != 0 => 2,
                                        _ => - 1
                                    },
        /// <inheritdoc/>
        public override void GetAll(IBag <TechniqueInfo> accumulator, IReadOnlyGrid grid)
        {
            // Iterate on each region pair.
            for (int index = 0; index < 18; index++)
            {
                // Iterate on each size.
                var(r1, r2) = (Regions[index, 0], Regions[index, 1]);
                foreach (var(size, masks) in Combinations)
                {
                    // Iterate on each combination.
                    foreach (short mask in masks)
                    {
                        var positions   = mask.GetAllSets();
                        var allCellsMap = GridMap.Empty;
                        var pairs       = new List <(int, int)>();
                        foreach (int pos in positions)
                        {
                            int c1 = RegionCells[r1][pos];
                            int c2 = RegionCells[r2][pos];
                            allCellsMap.Add(c1);
                            allCellsMap.Add(c2);
                            pairs.Add((c1, c2));
                        }

                        // Check each pair.
                        // Ensures all pairs should contains same digits
                        // and the kind of digits must be greater than 2.
                        bool checkKindsFlag = true;
                        foreach (var(l, r) in pairs)
                        {
                            if ((grid.GetCandidatesReversal(l) & grid.GetCandidatesReversal(r)).CountSet() < 2)
                            {
                                checkKindsFlag = false;
                                break;
                            }
                        }
                        if (!checkKindsFlag)
                        {
                            // Failed to check.
                            continue;
                        }

                        // Check the mask of cells from two regions.
                        short m1 = 0, m2 = 0;
                        foreach (var(l, r) in pairs)
                        {
                            m1 |= grid.GetCandidatesReversal(l);
                            m2 |= grid.GetCandidatesReversal(r);
                        }

                        int resultMask   = m1 | m2;
                        var normalDigits = new List <int>();
                        var extraDigits  = new List <int>();
                        var digits       = resultMask.GetAllSets();
                        foreach (int digit in digits)
                        {
                            int count = 0;
                            foreach (var(l, r) in pairs)
                            {
                                if (((grid.GetCandidatesReversal(l) & grid.GetCandidatesReversal(r)) >> digit & 1) != 0)
                                {
                                    // Both two cells contain same digit.
                                    count++;
                                }
                            }

                            if (count >= 2)
                            {
                                // This candidate must be in the structure.
                                normalDigits.Add(digit);
                            }
                            else
                            {
                                // This candidate must be the extra digit.
                                extraDigits.Add(digit);
                            }
                        }

                        if (normalDigits.Count != size)
                        {
                            // The number of normal digits are not enough.
                            continue;
                        }

                        if (resultMask.CountSet() == size + 1)
                        {
                            // Possible type 1 or 2 found.
                            // Now check extra cells.
                            var extraCells = new List <int>();
                            foreach (int cell in allCellsMap)
                            {
                                if ((grid.GetCandidatesReversal(cell) >> extraDigits[0] & 1) != 0)
                                {
                                    extraCells.Add(cell);
                                }
                            }

                            var extraCellsMap = new GridMap(extraCells) & EmptyMap;
                            if (extraCellsMap.IsEmpty)
                            {
                                continue;
                            }

                            // Get all eliminations and highlight candidates.
                            int extraDigit       = extraDigits[0];
                            var conclusions      = new List <Conclusion>();
                            var candidateOffsets = new List <(int, int)>();
                            if (extraCellsMap.Count == 1)
                            {
                                // Type 1.
                                foreach (int cell in allCellsMap)
                                {
                                    if (cell == extraCells[0])
                                    {
                                        foreach (int digit in grid.GetCandidatesReversal(cell).GetAllSets())
                                        {
                                            if (digit == extraDigit)
                                            {
                                                continue;
                                            }

                                            conclusions.Add(new Conclusion(Elimination, cell, digit));
                                        }
                                    }
                                    else
                                    {
                                        foreach (int digit in grid.GetCandidatesReversal(cell).GetAllSets())
                                        {
                                            candidateOffsets.Add((0, cell * 9 + digit));
                                        }
                                    }
                                }

                                if (conclusions.Count == 0)
                                {
                                    continue;
                                }

                                accumulator.Add(
                                    new XrType1TechniqueInfo(
                                        conclusions,
                                        views: new[]
                                {
                                    new View(
                                        cellOffsets: null,
                                        candidateOffsets,
                                        regionOffsets: null,
                                        links: null)
                                },
                                        typeCode: 1,
                                        typeName: "Type 1",
                                        cells: allCellsMap.ToArray(),
                                        digits: normalDigits));
                            }
                            else
                            {
                                // Type 2.
                                // Check eliminations.
                                var elimMap = new GridMap(extraCells, ProcessPeersWithoutItself);
                                foreach (int cell in elimMap)
                                {
                                    if (!(grid.Exists(cell, extraDigit) is true))
                                    {
                                        continue;
                                    }

                                    conclusions.Add(new Conclusion(Elimination, cell, extraDigit));
                                }

                                if (conclusions.Count == 0)
                                {
                                    continue;
                                }

                                // Record all highlight candidates.
                                foreach (int cell in allCellsMap)
                                {
                                    foreach (int digit in grid.GetCandidatesReversal(cell).GetAllSets())
                                    {
                                        candidateOffsets.Add((digit == extraDigit ? 1 : 0, cell * 9 + digit));
                                    }
                                }

                                accumulator.Add(
                                    new XrType2TechniqueInfo(
                                        conclusions,
                                        views: new[]
                                {
                                    new View(
                                        cellOffsets: null,
                                        candidateOffsets,
                                        regionOffsets: null,
                                        links: null)
                                },
                                        typeCode: 2,
                                        typeName: "Type 2",
                                        cells: allCellsMap.ToArray(),
                                        digits: normalDigits,
                                        extraDigit));
                            }
                        }
                        else
                        {
                            // Check type 3 or 4.
                            for (int subsetSize = 2; subsetSize <= 8 - size; subsetSize++)
                            {
                                CheckType3Naked(
                                    accumulator, grid, allCellsMap, subsetSize, r1, r2, pairs,
                                    normalDigits, extraDigits);
                            }

                            CheckType14(accumulator, grid, allCellsMap, normalDigits, extraDigits);
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Check type 1 or 4.
        /// </summary>
        /// <param name="accumulator">The accumulator.</param>
        /// <param name="grid">The grid.</param>
        /// <param name="allCellsMap">All cells map.</param>
        /// <param name="normalDigits">The normal digits.</param>
        /// <param name="extraDigits">The extra digits.</param>
        private void CheckType14(
            IBag <TechniqueInfo> accumulator, IReadOnlyGrid grid, GridMap allCellsMap,
            IReadOnlyList <int> normalDigits, IReadOnlyList <int> extraDigits)
        {
            // Now check extra cells.
            var extraCells = new List <int>();

            foreach (int cell in allCellsMap)
            {
                foreach (int digit in extraDigits)
                {
                    if ((grid.GetCandidatesReversal(cell) >> digit & 1) != 0)
                    {
                        extraCells.Add(cell);
                    }
                }
            }

            int extraCellsCount = extraCells.Count;

            if (extraCellsCount == 1)
            {
                // Type 1 found.
                // Check eliminations.
                var conclusions = new List <Conclusion>();
                int extraCell   = extraCells[0];
                foreach (int digit in normalDigits)
                {
                    if (!(grid.Exists(extraCell, digit) is true))
                    {
                        continue;
                    }

                    conclusions.Add(new Conclusion(Elimination, extraCell, digit));
                }

                if (conclusions.Count == 0)
                {
                    return;
                }

                // Record all highlight candidates.
                var candidateOffsets = new List <(int, int)>();
                foreach (int cell in allCellsMap)
                {
                    if (cell == extraCell)
                    {
                        continue;
                    }

                    foreach (int digit in grid.GetCandidatesReversal(cell).GetAllSets())
                    {
                        candidateOffsets.Add((0, cell * 9 + digit));
                    }
                }

                accumulator.Add(
                    new XrType1TechniqueInfo(
                        conclusions,
                        views: new[]
                {
                    new View(
                        cellOffsets: null,
                        candidateOffsets,
                        regionOffsets: null,
                        links: null)
                },
                        typeCode: 1,
                        typeName: "Type 1",
                        cells: allCellsMap.ToArray(),
                        digits: normalDigits));
            }
            else
            {
                // TODO: Check XR type 4.
            }
        }