/// <summary> /// To check whether the specified solver can solve the puzzle. /// </summary> /// <param name="grid">The puzzle.</param> /// <returns> /// A <see cref="bool"/> value indicating whether the solver /// solved the puzzle successfully. /// </returns> public bool CanSolve(IReadOnlyGrid grid) { var cloneation = grid.Clone(); var steps = new List <TechniqueInfo>(); var searcher = new SingleTechniqueSearcher(false, false); var bag = new Bag <TechniqueInfo>(); while (!cloneation.HasSolved) { searcher.GetAll(bag, cloneation); if (bag.Count == 0) { break; } foreach (var step in bag) { if (RecordTechnique(steps, step, cloneation)) { return(true); } } bag.Clear(); } return(false); }
/// <summary> /// Swap to regions. /// </summary> /// <param name="this">(<see langword="this"/> parameter) The grid.</param> /// <param name="region1">The region 1.</param> /// <param name="region2">The region 2.</param> /// <returns>The result.</returns> /// <exception cref="ArgumentException"> /// Throws when two specified region argument is not in valid range (0..27) /// or two regions are not in same region type. /// </exception> public static Grid SwapTwoRegions(this IReadOnlyGrid @this, int region1, int region2) { if (region1 < 0 || region1 >= 18) { throw new ArgumentException("The specified argument is out of valid range.", nameof(region1)); } if (region2 < 0 || region2 >= 18) { throw new ArgumentException("The specified argument is out of valid range.", nameof(region2)); } if (region1 / 9 != region2 / 9) { throw new ArgumentException("Two region should be the same region type."); } var result = @this.Clone(); for (int i = 0; i < 9; i++) { int c1 = RegionCells[region1][i]; int c2 = RegionCells[region2][i]; short temp = result.GetMask(c1); result.SetMask(c1, result.GetMask(c2)); result.SetMask(c2, temp); } return(result); }
/// <summary> /// Rotate the grid <c><see cref="Math.PI"/></c> degrees. /// </summary> /// <param name="this">(<see langword="this"/> parameter) The grid.</param> /// <returns>The result.</returns> public static Grid RotatePi(this IReadOnlyGrid @this) { var result = @this.Clone(); for (int i = 0; i < 81; i++) { int z = PiRotateTable[i]; short temp = result.GetMask(i); result.SetMask(i, result.GetMask(z)); result.SetMask(z, temp); } return(result); }
/// <inheritdoc/> public override AnalysisResult Solve(IReadOnlyGrid grid) { if (grid.IsValid(out var solution, out bool?sukaku)) { // Solve the puzzle. try { return(AnalyzeDifficultyStrictly ? SolveWithStrictDifficultyRating( grid, grid.Clone(), new List <TechniqueInfo>(), solution, sukaku.Value) : SolveNaively(grid, grid.Clone(), new List <TechniqueInfo>(), solution, sukaku.Value)); } catch (WrongHandlingException ex) { return(new AnalysisResult( puzzle: grid, solverName: SolverName, hasSolved: false, solution: null, elapsedTime: TimeSpan.Zero, solvingList: null, additional: ex.Message, stepGrids: null)); } } else { return(new AnalysisResult( puzzle: grid, solverName: SolverName, hasSolved: false, solution: null, elapsedTime: TimeSpan.Zero, solvingList: null, additional: "The puzzle does not have a unique solution (multiple solutions or no solution).", stepGrids: null)); } }
/// <summary> /// Mirror top-bottom the grid. /// </summary> /// <param name="this">(<see langword="this"/> parameter) The grid.</param> /// <returns>The result grid.</returns> public static Grid MirrorTopBottom(this IReadOnlyGrid @this) { var result = @this.Clone(); for (int i = 0; i < 9; i++) { for (int j = 0; j < 4; j++) { short temp = result.GetMask(i * 9 + j); result.SetMask(i * 9 + j, result.GetMask((8 - i) * 9 + j)); result.SetMask((8 - i) * 9 + j, temp); } } return(result); }
/// <summary> /// Mirror left-right the grid. /// </summary> /// <param name="this">(<see langword="this"/> parameter) The grid.</param> /// <returns>The result grid.</returns> public static Grid MirrorLeftRight(this IReadOnlyGrid @this) { var result = @this.Clone(); for (int i = 0; i < 4; i++) { for (int j = 0; j < 9; j++) { short temp = result.GetMask(i * 9 + j); result.SetMask(i * 9 + j, result.GetMask(i * 9 + (8 - j))); result.SetMask(i * 9 + (8 - j), temp); } } return(result); }
/// <summary> /// Mirror anti-diagonal the grid. /// </summary> /// <param name="this">(<see langword="this"/> parameter) The grid.</param> /// <returns>The result grid.</returns> public static Grid MirrorAntidiagonal(this IReadOnlyGrid @this) { var result = @this.Clone(); for (int i = 0; i < 9; i++) { for (int j = 0; j < 8 - i; j++) { short temp = result.GetMask(i * 9 + j); result.SetMask(i * 9 + j, result.GetMask((8 - j) * 9 + (8 - i))); result.SetMask((8 - j) * 9 + (8 - i), temp); } } return(result); }
/// <summary> /// Mirror diagonal the grid. /// </summary> /// <param name="this">(<see langword="this"/> parameter) The grid.</param> /// <returns>The result grid.</returns> public static Grid MirrorDiagonal(this IReadOnlyGrid @this) { var result = @this.Clone(); for (int i = 1; i < 9; i++) { for (int j = 0; j < i; j++) { short temp = result.GetMask(i * 9 + j); result.SetMask(i * 9 + j, result.GetMask(j * 9 + i)); result.SetMask(j * 9 + i, temp); } } return(result); }
/// <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; } }