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