/// <summary>Attempts to solve a Sudoku puzzle.</summary> /// <param name="state">The state of the puzzle to be solved.</param> /// <param name="options">Options to use for solving.</param> /// <returns>The result of the solve attempt.</returns> /// <remarks>No changes are made to the parameter state.</remarks> public static SolverResults Solve(PuzzleState state, SolverOptions options) { // Validate parameters if (state == null) { throw new ArgumentNullException("state"); } if (options == null) { throw new ArgumentNullException("options"); } options = options.Clone(); if (options.EliminationTechniques.Count == 0) { options.EliminationTechniques.Add(new NakedSingleTechnique()); } // Turn off the raising of changed events while solving, // though it probably doesn't matter as the first thing // SolveInternal does is make a clone, and RaiseStateChangedEvent // is not cloned (on purpose). bool raiseChangedEvent = state.RaiseStateChangedEvent; state.RaiseStateChangedEvent = false; // Attempt to solve the puzzle SolverResults results = SolveInternal(state, options); // Reset whether changed events should be raised state.RaiseStateChangedEvent = raiseChangedEvent; // Return the solver results return(results); }
/// <summary>Uses brute-force search techniques to solve the puzzle.</summary> /// <param name="state">The state to be solved.</param> /// <param name="options">The options to use in solving.</param> /// <param name="possibleNumbers">The possible numbers off of which to base the search.</param> /// <returns>The result of the solve attempt.</returns> /// <remarks>Changes may be made to the parameter state.</remarks> private static SolverResults BruteForceSolve(PuzzleState state, SolverOptions options, FastBitArray [][] possibleNumbers) { // A standard brute-force search would take way, way, way too long to solve a Sudoku puzzle. // Luckily, there are ways to significantly trim the search tree to a point where // brute-force is not only possible, but also very fast. The idea is that not every number // can be put into every cell. In fact, using elimination techniques, we can narrow down the list // of numbers for each cell, such that only those need be are tried. Moreover, every time a new number // is entered into a cell, other cell's possible numbers decrease. It's in our best interest // to start the search with a cell that has the least possible number of options, thereby increasing // our chances of "guessing" the right number sooner. To this end, I find the cell in the grid // that is empty and that has the least number of possible numbers that can go in it. If there's more // than one cell with the same number of possibilities, I choose randomly among them. This random // choice allows the solver to be used for puzzle generation. List <Point> bestGuessCells = new List <Point>(); int bestNumberOfPossibilities = state.GridSize + 1; for (int i = 0; i < state.GridSize; i++) { for (int j = 0; j < state.GridSize; j++) { int count = possibleNumbers[i][j].CountSet; if (!state[i, j].HasValue) { if (count < bestNumberOfPossibilities) { bestNumberOfPossibilities = count; bestGuessCells.Clear(); bestGuessCells.Add(new Point(i, j)); } else if (count == bestNumberOfPossibilities) { bestGuessCells.Add(new Point(i, j)); } } } } // If there are no cells available to fill, there's nothing we can do // to make forward progress. If there are cells available, which should // always be the case when this method is called, go through each of the possible // numbers in the cell and try to solve the puzzle with that number in it. SolverResults results = null; if (bestGuessCells.Count > 0) { // Choose a random cell from amongst the possibilities found above Point bestGuessCell = bestGuessCells[RandomHelper.GetRandomNumber(bestGuessCells.Count)]; // Get the possible numbers for that cell. For each possible number, // fill that number into the cell and recursively call to Solve. FastBitArray possibleNumbersForBestCell = possibleNumbers[bestGuessCell.X][bestGuessCell.Y]; for (byte p = 0; p < possibleNumbersForBestCell.Length; p++) { if (possibleNumbersForBestCell[p]) { PuzzleState newState = state; // Fill in the cell and solve the puzzle newState[bestGuessCell] = p; SolverOptions tempOptions = options.Clone(); if (results != null) { tempOptions.MaximumSolutionsToFind = (uint)(tempOptions.MaximumSolutionsToFind.Value - results.Puzzles.Count); } SolverResults tempResults = SolveInternal(newState, tempOptions); // If it could be solved, update information about the solving process // and return the solution. Only if the user wants to find multiple // solutions will the search continue. if (tempResults.Status == PuzzleStatus.Solved) { if (results != null && results.Puzzles.Count > 0) { results.Puzzles.AddRange(tempResults.Puzzles); } else { results = tempResults; results.NumberOfDecisionPoints++; } if (options.MaximumSolutionsToFind.HasValue && results.Puzzles.Count >= options.MaximumSolutionsToFind.Value) { return(results); } } // If we're not cloning, we need to cancel out the change newState[bestGuessCell] = null; } } } // We'll get here if the requested number of solutions could not be found, or if no // solutions at all could be found. Either return a solution if we did get at least one, // or return that none could be found. return(results != null ? results : new SolverResults(PuzzleStatus.CannotBeSolved, state, 0, null)); }