Esempio n. 1
0
        public static bool IsBivalueCell(this IReadOnlyGrid @this, int cellOffset, out short mask)
        {
            if (@this.GetStatus(cellOffset) != Empty)
            {
                mask = 0;
                return(false);
            }

            mask = @this.GetCandidatesReversal(cellOffset);
            return(mask.CountSet() == 2);
        }
Esempio n. 2
0
        /// <summary>
        /// To find all backdoors in a sudoku grid.
        /// </summary>
        /// <param name="result">The result list.</param>
        /// <param name="grid">A sudoku grid to search backdoors.</param>
        /// <param name="depth">The depth to search.</param>
        /// <exception cref="InvalidOperationException">
        /// Throws when the grid is invalid (has no solution or multiple solutions).
        /// </exception>
        private static void SearchForBackdoors(
            IList <IReadOnlyList <Conclusion> > result, IReadOnlyGrid grid, int depth)
        {
            if (!grid.IsValid(out var solution))
            {
                throw new InvalidOperationException("The puzzle does not have unique solution.");
            }

            var tempGrid = grid.Clone();

            if (depth == 0)
            {
                // Search backdoors (Assignments).
                if (TestSolver.CanSolve(tempGrid))
                {
                    // All candidates will be marked.
                    for (int c = 0; c < 81; c++)
                    {
                        for (int d = 0, z = solution[c]; d < 9; d++)
                        {
                            result.Add(new[] { new Conclusion(d == z ? Assignment : Elimination, c, d) });
                        }
                    }
                }
                else
                {
                    for (int cell = 0; cell < 81; cell++)
                    {
                        if (tempGrid.GetStatus(cell) != Empty)
                        {
                            continue;
                        }

                        int digit = solution[cell];
                        tempGrid[cell] = digit;

                        if (TestSolver.CanSolve(tempGrid))
                        {
                            // Solve successfully.
                            result.Add(new[] { new Conclusion(Assignment, cell, digit) });
                        }

                        // Restore data.
                        // Simply assigning to trigger the event to re-compute all candidates.
                        tempGrid[cell] = -1;
                    }
                }

                return;
            }

            // Store all incorrect candidates to prepare for search elimination backdoors.
            var incorrectCandidates = (
                from cell in Enumerable.Range(0, 81)
                where grid.GetStatus(cell) == Empty
                let Value = solution[cell]
                            from digit in Enumerable.Range(0, 9)
                            where !grid[cell, digit] && Value != digit
                            select cell * 9 + digit).ToArray();

            // Search backdoors (Eliminations).
            for (int i1 = 0, count = incorrectCandidates.Length; i1 < count + 1 - depth; i1++)
            {
                int c1 = incorrectCandidates[i1];
                tempGrid[c1 / 9, c1 % 9] = true;

                if (depth == 1)
                {
                    if (TestSolver.CanSolve(tempGrid))
                    {
                        result.Add(new[] { new Conclusion(Elimination, c1) });
                    }
                }
                else                 // depth > 1
                {
                    for (int i2 = i1 + 1; i2 < count + 2 - depth; i2++)
                    {
                        int c2 = incorrectCandidates[i2];
                        tempGrid[c2 / 9, c2 % 9] = true;

                        if (depth == 2)
                        {
                            if (TestSolver.CanSolve(tempGrid))
                            {
                                result.Add(
                                    new[]
                                {
                                    new Conclusion(Elimination, c1),
                                    new Conclusion(Elimination, c2)
                                });
                            }
                        }
                        else                         // depth == 3
                        {
                            for (int i3 = i2 + 1; i3 < count + 3 - depth; i3++)
                            {
                                int c3 = incorrectCandidates[i3];
                                tempGrid[c3 / 9, c3 % 9] = true;

                                if (TestSolver.CanSolve(tempGrid))
                                {
                                    result.Add(
                                        new[]
                                    {
                                        new Conclusion(Elimination, c1),
                                        new Conclusion(Elimination, c2),
                                        new Conclusion(Elimination, c3)
                                    });
                                }

                                tempGrid[c3 / 9, c3 % 9] = false;
                            }
                        }

                        tempGrid[c2 / 9, c2 % 9] = false;
                    }
                }

                tempGrid[c1 / 9, c1 % 9] = false;
            }
        }
        /// <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));
        }
        /// <summary>
        /// Check central symmetry.
        /// </summary>
        /// <param name="result">The result accumulator.</param>
        /// <param name="grid">The grid.</param>
        private static void CheckCentral(IBag <TechniqueInfo> result, IReadOnlyGrid grid)
        {
            if (grid.GetStatus(40) != Empty)
            {
                // Has no conclusion even though the grid may be symmetrical.
                return;
            }

            int?[] mapping = new int?[9];
            for (int cell = 0; cell < 40; cell++)
            {
                int  anotherCell = 80 - cell;
                bool condition   = grid.GetStatus(cell) == Empty;
                if (condition ^ grid.GetStatus(anotherCell) == Empty)
                {
                    // One of two cell is empty, not central symmetry type.
                    return;
                }

                if (condition)
                {
                    continue;
                }

                int d1 = grid[cell], d2 = grid[anotherCell];
                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;
                    }
                }
            }

            for (int digit = 0; digit < 9; digit++)
            {
                if (mapping[digit] == digit || mapping[digit] is null)
                {
                    result.Add(
                        new GspTechniqueInfo(
                            conclusions: new[] { new Conclusion(Assignment, 40, digit) },
                            views: View.DefaultViews,
                            symmetryType: SymmetryType.Central,
                            mappingTable: mapping));

                    return;
                }
            }
        }
Esempio n. 5
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));
                }
            }
        }
        /// <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));
Esempio n. 7
0
 public static bool?Exists(this IReadOnlyGrid @this, int cellOffset, int digit) =>
 @this.GetStatus(cellOffset) switch
 {