/// <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); }
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)); }
/// <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));
/// <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[]
/// <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)); } } } }
/// <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));
/// <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. } }