internal override List <int> GetPencilmarkList(NakedHiddenFinder hiddenFinder) { List <int> returnValue = hiddenFinder.ActionBits .BitMerge() .ListOfBits(); return(returnValue); }
internal override List <int> GetLocationList(NakedHiddenFinder hiddenFinder) { int mask = 0; hiddenFinder.ActionIndexes .Select(i => hiddenFinder.MapBits[i]) .ToList() .ForEach(pmsign => mask |= pmsign); List <int> returnValue = mask.ListOfBits(); return(returnValue); }
internal override List <int> GetLocationList(NakedHiddenFinder hiddenFinder) { return(hiddenFinder.ActionIndexes); }
internal override List <int> GetPencilmarkList(NakedHiddenFinder hiddenFinder) { return(hiddenFinder.ActionIndexes); }
public override bool FindPattern(GameBoard board) { // The algorithm is the same for finding naked or hidden values. For naked: // an array of integers where each integer is a set of bits for the pencilmarks. // (e.g. 01 0110 0010 == 0x162 which mean pencilmark 1, 5, 6, and 8 are set. The // LSB is position 0), and the index in the array is a cell position in the line. // For Hidden: Swap the meaning: The bits are the positions in the line and the // index is the pencilmark. // Start from the beginning in the array. Its content is a cumulative mask. // If the cumulative mask has more than the goal bits, it is thrown away and the // next one is considered. If N bits are found in a mask using N entries in the // array, then there are N of N which is what is being searched for. Once found, // a "final approval method" is invoked to check if the set is good. The approval // is determined to be good if there are pencilmarks to turn off. (for naked, in // other positions in the line. for hidden: more pencilmarks in the same cells // (other than the N found (e.g. found 1,2,3 but cell also has 2,7 which can be // turned off. Since there is a large number of combinations the best approach // to try promising ones and discard useless ones quickly is to use recursion. // Since a board has a small number of cells (9 or so) the recursion depth is not // an issue. Board = board; for (int coord = Coord.ROWS; coord <= Coord.SECTIONS; coord++) { List <List <Cell> > allLines = Board.GetAllLines(coord); foreach (List <Cell> line in allLines) { var hiddenFinder = new NakedHiddenFinder(); int dim = line.Count + 1; hiddenFinder.MapBits = CreateMapBits(line); hiddenFinder.MapLength = hiddenFinder.MapBits.Count; // Step 2: Search for N of N values int maxGoal = hiddenFinder.MapBits.Count(t => t != 0); if (maxGoal == 0) { continue; } int goal = 1; for (; goal < maxGoal; goal++) { if (hiddenFinder.Search(goal)) { break; } } // Finally set the information as related to cells, and information if (goal == maxGoal) { continue; } LineCoordinate = coord; int indexLine = line[0].GetCoordinate(LineCoordinate); List <int> locationList = GetLocationList(hiddenFinder); patternCells = locationList.Select(i => line[i]).ToList(); PencilmarkList = GetPencilmarkList(hiddenFinder); Solution = new SolveInfo(); int pmMask = GetPencilmarkMask(); int patternPMs = 0; patternCells.ForEach(cell => patternPMs |= cell.PMSignature); locationList .Select(loc => line[loc]) .ToList() .ForEach(cell => Solution.AddAction(cell, CellRole.Pattern, GetPmFindings(cell, patternPMs, PMRole.Pattern))); List <int> solutionPMs = pmMask.ListOfBits(); CalculateAffectedCells(line); affectedCells.ForEach(affCell => Solution.AddAction(affCell, AffectedCellRole, GetPmFindings(affCell, new int[] { pmMask, patternPMs }, new PMRole[] { PMRole.Remove, PMRole.Pattern }))); line .Except(patternCells) .Except(affectedCells) .ToList() .ForEach(cell => Solution.AddAction(cell, CellRole.InvolvedLine)); CreateRuleInformation(); return(true); } } return(false); }
internal abstract List <int> GetPencilmarkList(NakedHiddenFinder hiddenFinder);
internal abstract List <int> GetLocationList(NakedHiddenFinder hiddenFinder);
private bool MakesUniqueRectangle(List <Cell> firstTwo) { int keySignature = firstTwo[0].PMSignature; string csvAffected, csvPattern, csvHalf2; int commonCoord; int lineIndex; if ((lineIndex = firstTwo[0].RowIndex) == firstTwo[1].RowIndex) { commonCoord = Coord.ROW; } else if ((lineIndex = firstTwo[0].ColIndex) == firstTwo[1].ColIndex) { commonCoord = Coord.COL; } else { return(false); } int diffCoord = 1 - commonCoord; List <int> cellIndexes = firstTwo .Select(cell => cell.GetCoordinate(diffCoord)) .ToList(); List <List <Cell> > workLine = (commonCoord == Coord.ROW) ? allRows : allColumns; List <List <Cell> > secondHalfLineCandidates = workLine .Where(line => line[0].GetCoordinate(commonCoord) != lineIndex && (line[cellIndexes[0]].PMSignature & keySignature) == keySignature && (line[cellIndexes[1]].PMSignature & keySignature) == keySignature) .ToList(); foreach (List <Cell> half2Line in secondHalfLineCandidates) { int half2PencilmarkCount = 0; List <Cell> half2 = new List <Cell> { half2Line[cellIndexes[0]], half2Line[cellIndexes[1]] }; if (firstTwo.Union(half2) .Select(cell => cell.GetCoordinate(Coord.SECTION)) .Distinct() .Count() != 2) { continue; } List <Cell> half2ManyPMs = half2.Where(cell => cell.PencilMarkCount > 2).ToList(); half2.ForEach(cell => half2PencilmarkCount += cell.PencilMarkCount); int pmOtherMask; // Unique Rectangle - Type 1 // 3 cells in the rectangle have 2 options, and the 4th cell has more. // In the 4th cell, the 2 common options should be turned off. if (half2ManyPMs.Count == 1) { Solution = new SolveInfo(); Cell solutionCell = half2ManyPMs[0]; patternCells = new List <Cell>(firstTwo); patternCells.AddRange(half2.Except(half2ManyPMs).ToList()); patternCells.ForEach(cell => Solution.AddAction(cell, CellRole.Pattern, GetPmFindings(cell, keySignature, PMRole.Pattern))); affectedCells = half2ManyPMs; Solution.AddAction(solutionCell, CellRole.Pattern, GetPmFindings(solutionCell, keySignature, PMRole.Remove)); patternCells.Add(solutionCell); // to include in description csvPattern = CellsInCsvFormat(patternCells); csvAffected = CellsInCsvFormat(affectedCells); string csvValues = PMsInCsvFormat(keySignature.ListOfBits()); string csvSolutionCell = CellsInCsvFormat(solutionCell); Solution.Description = $"Unique Rectangle - Type 1 : Cell {csvAffected} cannot " + $"have values {csvValues}. Otherwise a deadly pattern would form in cells {csvPattern}. " + $"Therefore these values can be ruled out for that cell."; return(true); } // Possible case of Unique Rectangle - Type 2 // 2 cells have 3 pencilmarks set (the same 3 for both), so one of the // 2 cells must be the 3rd value. No other cells in the line (and section) // can hold that value. if (half2PencilmarkCount == 6 && half2[0].PMSignature == half2[1].PMSignature) { Solution = new SolveInfo(); int mask = half2[0].PMSignature ^ keySignature; affectedCells = half2Line.Except(half2) .Where(cell => (cell.PMSignature & mask) != 0) .ToList(); if (affectedCells.Count != 0) { List <Cell> affectedInGroup = Board .GetLine(Coord.SECTION, half2[0].GetCoordinate(Coord.SECTION)) .Except(half2) .Where(cell => (cell.PMSignature & mask) != 0) .ToList(); affectedCells.AddRange(affectedInGroup); patternCells = new List <Cell>(firstTwo); patternCells.AddRange(half2); affectedCells .ForEach(cell => Solution.AddAction(cell, CellRole.Affected, GetPmFindings(cell, mask, PMRole.Remove))); Board.GetLine(commonCoord, lineIndex) .Except(affectedCells) .ToList() .ForEach(cell => Solution.AddAction(cell, CellRole.InvolvedLine)); int iValue = mask.ListOfBits()[0]; csvPattern = CellsInCsvFormat(patternCells); csvAffected = CellsInCsvFormat(affectedCells); csvHalf2 = CellsInCsvFormat(half2); Solution.Description = $"Unique Rectangle - Type 2: Value {iValue} has to " + $"be present in one of cells {csvHalf2} to avoid a deadly pattern (where the puzzle " + $"would have 2 solutions). Therefore, value 2 cannot be present in any of " + $"cells {csvAffected}."; return(true); } } // Possible case of Unique Rectangle - Type 3 // 2 cells have 3 or 4 pencilmarks. The extra pencilmarks are treated as if // they were all in a single cell, and a naked pattern is searched for in the line // using these extra values. // Example: 4 cells have pencilmarks 3,9 - 3,9 - 1,5,3,9 - 1,5,3,9 . The extra // values (1 and 5) are treated as being in a single cell, and a naked pattern is // searched for. If 2 other cells have (1,4) and (4,5), then the 2 cells and the // virtual cell form a naked cell group of size 3 with values 1, 4, 5, and the naked // rule is applied accordingly on the line. if (half2PencilmarkCount >= 6 && half2PencilmarkCount <= 8) { do { int pmsVirtualCell = 0; half2.ForEach(cell => pmsVirtualCell |= cell.PMSignature); pmsVirtualCell ^= firstTwo[0].PMSignature; if (pmsVirtualCell.NumberOfBitsSet() != 2) { continue; } // clear one of the virtual cells completely List <Cell> virtualLine = half2Line.Select(t => t.Clone()).ToList(); // change the pencilmarks in the other cell to match the virtual definition virtualLine[cellIndexes[0]].SetValue(0); // clear pencilmarks virtualLine[cellIndexes[1]].SetValue(0); virtualLine[cellIndexes[1]].SetMultiplePencilMarks(pmsVirtualCell); // Now perform a search for a naked rule set NakedHiddenFinder finder = new NakedHiddenFinder(); finder.MapBits = NakedValuesRule.CreateMapBitsFromLine(virtualLine); finder.MapLength = virtualLine.Count; if (!finder.Search(2 /*goal*/) && !finder.Search(3 /*goal*/)) { continue; } int pmsVirtualGroup = finder.ActionIndexes .Select(i => half2Line[i].PMSignature) .ToList() .BitMerge(); affectedCells = new List <Cell>(); Solution = new SolveInfo(); pmOtherMask = finder.ActionBits.BitMerge(); affectedCells = half2Line .Except(finder.ActionIndexes.Select(i => half2Line[i]).ToList()) .Except(half2) .Where(cell => (cell.PMSignature & pmOtherMask) != 0) .ToList(); affectedCells.ForEach(cell => Solution.AddAction(cell, CellRole.Affected, GetPmFindings(cell, pmOtherMask, PMRole.Remove))); int deadlySignature = firstTwo[0].PMSignature; firstTwo.ForEach(cell => Solution.AddAction(cell, CellRole.Pattern, GetPmFindings(cell, deadlySignature, PMRole.Pattern))); half2.ForEach(cell => Solution.AddAction(cell, CellRole.Pattern, GetPmFindings(cell, new int[] { deadlySignature, pmsVirtualCell }, new PMRole[] { PMRole.Pattern, PMRole.ChainColor1 }))); List <Cell> virtualGroup = finder.ActionIndexes .Except(cellIndexes) .Select(i => half2Line[i]) .ToList(); virtualGroup.ForEach(cell => Solution.AddAction(cell, CellRole.InvolvedLine, GetPmFindings(cell, pmsVirtualGroup, PMRole.ChainColor1))); List <Cell> deadlyCells = firstTwo.Union(half2).ToList(); patternCells = deadlyCells .Union(finder.ActionIndexes.Select(indexCell => half2Line[indexCell])) .ToList(); List <Cell> line = Board.GetLine(commonCoord, half2[0].GetCoordinate(commonCoord)); line .Except(affectedCells) .Except(half2) .Except(virtualGroup) .ToList() .ForEach(cell => Solution.AddAction(cell, CellRole.InvolvedLine)); csvAffected = CellsInCsvFormat(affectedCells); csvPattern = CellsInCsvFormat(patternCells); csvHalf2 = CellsInCsvFormat(half2); string csvDeadly = CellsInCsvFormat(deadlyCells); string csvValues = PMsInCsvFormat(pmOtherMask.ListOfBits()); string csvPmVirtual = PMsInCsvFormat(pmsVirtualCell.ListOfBits()); string commonCoordName = (commonCoord == Coord.ROW) ? "row" : "column"; string csvNakedGroup = PMsInCsvFormat((pmOtherMask | pmsVirtualCell).ListOfBits()); Solution.Description = string.Format("Unique Rectangle Type 3: Cells {0} form a single " + "virtual cell with pencilmarks {1}. A deadly pattern in cells {2} cannot take " + "place. One of values {1} has to be present in cells {0} (Naked rule applied " + "using virtual cell for pencilmarks {3}) . Options {5} can be removed from cells " + "{6}.", csvHalf2, csvPmVirtual, csvDeadly, csvNakedGroup, commonCoordName, csvValues, csvAffected); return(true); }while (false); // builds a common exit point } // Last case: Unique Rectangle - Type 4 // 2 cells have many more pencilmarks set. One of the extra values HAS to be // the solution for one of the cells. Then the 4th cell cannot hold the other // value (of the deadly rectangle) or else a deadly pattern would form if (half2PencilmarkCount < 6) { continue; } List <Cell> analysisCells = half2Line .Except(half2) .ToList(); List <int> pmAnalysis = keySignature.ListOfBits(); int pmWork = pmAnalysis.FirstOrDefault(pm => analysisCells.FirstOrDefault( cell => cell.IsPencilmarkSet(pm)) == null); if (pmWork == 0) { continue; } Solution = new SolveInfo(); int pmOther = pmAnalysis.FirstOrDefault(t => t != pmWork); affectedCells = half2.Where(cell => cell.IsPencilmarkSet(pmOther)).ToList(); patternCells = firstTwo.Union(half2).Except(affectedCells).ToList(); pmOtherMask = 1 << pmOther; int pmWorkMask = 1 << pmWork; affectedCells.ForEach(cell => Solution.AddAction(cell, CellRole.Pattern, GetPmFindings(cell, new int[] { pmOtherMask, pmWorkMask }, new PMRole[] { PMRole.Remove, PMRole.Pattern }))); patternCells.ForEach(cell => Solution.AddAction(cell, CellRole.Pattern, GetPmFindings(cell, pmWorkMask | pmOtherMask, PMRole.Pattern))); lineIndex = half2[0].GetCoordinate(commonCoord); Board.GetLine(commonCoord, lineIndex) .Except(affectedCells) .ToList() .ForEach(cell => Solution.AddAction(cell, CellRole.InvolvedLine)); patternCells.AddRange(affectedCells); // to build description csvAffected = CellsInCsvFormat(affectedCells); csvPattern = CellsInCsvFormat(patternCells); csvHalf2 = CellsInCsvFormat(half2); string lineName = (commonCoord == Coord.ROW) ? "row" : "column"; Solution.Description = string.Format("Unique Rectangle - Type 4: Either one of cells {0}" + "must have value {1}. If the other cell of the two had value {2}, the 4 cells {3} " + "would form a deadly pattern. Since this cannot happen, cell {4} cannot hold value {2}.", csvHalf2, pmWork, pmOther, csvPattern, csvAffected, lineName); return(true); } return(false); }