/// <summary> /// Search for floors. /// </summary> /// <param name="result">The result accumulator.</param> /// <param name="grid">The grid.</param> private void SearchForFloors(IBag <TechniqueInfo> result, IReadOnlyGrid grid) { var series = (Span <int>) stackalloc int[27]; for (int i = 0; i < 3; i++) { // Initialize. for (int o = 0; o < 27; o++) { // All elements in 'series' is in range 0 to 9, // where 0 is for empty cell. series[o] = grid[i * 27 + o] + 1; } // Get all partial solutions. var solutions = new List <int[]>(); GetAllPartialSolutionsForFloorsRecursively(series.ToArray(), solutions, grid, i, 0); // Set all possible candidates. var bitmap = new BitArray(27 * 9); foreach (int[] solution in solutions) { for (int index = 0; index < 27; index++) { int digit = solution[index] - 1; bitmap[index * 9 + digit] = true; } } // Record all eliminations. var conclusions = new List <Conclusion>(); int z = 0; foreach (bool?v in bitmap) { int cell = i * 27 + z / 9; int digit = z % 9; if (!(v ?? true) && grid.Exists(cell, digit) is true) { conclusions.Add(new Conclusion(ConclusionType.Elimination, cell, digit)); } z++; } if (conclusions.Count == 0) { goto Label_GC; } result.Add( new CccTechniqueInfo( conclusions, views: new[]
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)); }
/// <inheritdoc/> public override void GetAll(IBag <TechniqueInfo> accumulator, IReadOnlyGrid grid) { for (int digit = 0; digit < 9; digit++) { for (int block = 0; block < 9; block++) { // Check the empty rectangle occupies more than 2 cells. // and the structure forms an empty rectangle. var erMap = CandMaps[digit] & RegionMaps[block]; if (erMap.Count < 2 || !IsEmptyRectangle(erMap, block, out int row, out int column)) { continue; } // Search for conjugate pair. for (int i = 0; i < 12; i++) { var linkMap = CandMaps[digit] & RegionMaps[LinkIds[block, i]]; if (linkMap.Count != 2) { continue; } if (linkMap.BlockMask.IsPowerOfTwo() || i < 6 && (linkMap & RegionMaps[column]).IsEmpty || i >= 6 && (linkMap & RegionMaps[row]).IsEmpty) { continue; } int[] t = (linkMap - (i < 6 ? RegionMaps[column] : RegionMaps[row])).ToArray(); int elimRegion = i < 6 ? t[0] % 9 + 18 : t[0] / 9 + 9; var elimCellMap = i < 6 ? CandMaps[digit] & RegionMaps[elimRegion] & RegionMaps[row] : CandMaps[digit] & RegionMaps[elimRegion] & RegionMaps[column]; if (elimCellMap.IsEmpty) { continue; } int elimCell = elimCellMap.SetAt(0); if (!(grid.Exists(elimCell, digit) is true)) { continue; } // Record all highlight candidates. var candidateOffsets = new List <(int, int)>(); var cpCells = new List <int>(2); foreach (int cell in RegionMaps[block] & CandMaps[digit]) { candidateOffsets.Add((1, cell * 9 + digit)); } foreach (int cell in linkMap) { candidateOffsets.Add((0, cell * 9 + digit)); cpCells.Add(cell); } // Empty rectangle. accumulator.Add( new EmptyRectangleTechniqueInfo( conclusions: new[] { new Conclusion(Elimination, elimCell, digit) }, views: new[]
/// <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) { // 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. } }