Пример #1
0
        /// <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);
        }
Пример #2
0
        /// <summary>Evaluates the difficulty level of a particular puzzle.</summary>
        /// <param name="state">The puzzle to evaluate.</param>
        /// <returns>The perceived difficulty level of the puzzle.</returns>
        public static PuzzleDifficulty EvaluateDifficulty(PuzzleState state)
        {
            if (state == null)
            {
                throw new ArgumentNullException("state");
            }

            SolverOptions options = new SolverOptions();

            foreach (PuzzleDifficulty diff in new PuzzleDifficulty[] { PuzzleDifficulty.Easy, PuzzleDifficulty.Medium, PuzzleDifficulty.Hard, PuzzleDifficulty.VeryHard })
            {
                GeneratorOptions go = GeneratorOptions.Create(diff);
                if (state.NumberOfFilledCells < go.MinimumFilledCells)
                {
                    continue;
                }

                options.AllowBruteForce        = !go.MaximumNumberOfDecisionPoints.HasValue;
                options.EliminationTechniques  = go.Techniques;
                options.MaximumSolutionsToFind = options.AllowBruteForce ? 2u : 1u;
                SolverResults results = Solver.Solve(state, options);
                if (results.Status == PuzzleStatus.Solved &&
                    results.Puzzles.Count == 1)
                {
                    return(diff);
                }
            }
            return(PuzzleDifficulty.Invalid);
        }
Пример #3
0
        /// <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>
        private static SolverResults SolveInternal(PuzzleState state, SolverOptions options)
        {
            // First, make a copy of the state and work on that copy.  That way, the original
            // instance passed to us by the client remains unmodified.
            state = state.Clone();

            // Fill cells using logic and analysis techniques until no more
            // can be filled.
            var totalUseOfTechniques = new Dictionary <EliminationTechnique, int>();

            FastBitArray [][] possibleNumbers = FillCellsWithSolePossibleNumber(state, options.EliminationTechniques, ref totalUseOfTechniques);

            // Analyze the current state of the board
            switch (state.Status)
            {
            // If the puzzle is now solved, return it. If the puzzle is in an inconsistent state (such as
            // two of the same number in the same row), return that as well as there's nothing more to be done.
            case PuzzleStatus.Solved:
            case PuzzleStatus.CannotBeSolved:
                return(new SolverResults(state.Status, state, 0, totalUseOfTechniques));

            // If the puzzle is still in progress, it means no more cells
            // can be filled by elimination alone, so do a brute-force step.
            // BruteForceSolve recursively calls back to this method.
            default:
                if (options.AllowBruteForce)
                {
                    SolverResults results = BruteForceSolve(state, options, possibleNumbers);
                    if (results.Status == PuzzleStatus.Solved)
                    {
                        AddTechniqueUsageTables(results.UseOfTechniques, totalUseOfTechniques);
                    }
                    return(results);
                }
                else
                {
                    return(new SolverResults(PuzzleStatus.CannotBeSolved, state, 0, null));
                }
            }
        }
Пример #4
0
        /// <summary>
        /// Based on the specified GeneratorOptions, determines whether the SolverResults
        /// created by solving a puzzle with a particular cell value removed represents a valid
        /// new state.
        /// </summary>
        /// <param name="state">The puzzle state being validated.</param>
        /// <param name="results">The SolverResults to be verified.</param>
        /// <returns>true if the removal that led to this call is valid; false, otherwise.</returns>
        private static bool IsValidRemoval(PuzzleState state, SolverResults results, GeneratorOptions options)
        {
            // Make sure we have a puzzle with one and only one solution
            if (results.Status != PuzzleStatus.Solved || results.Puzzles.Count != 1)
            {
                return(false);
            }

            // Make sure we don't have too few cells
            if (state.NumberOfFilledCells < options.MinimumFilledCells)
            {
                return(false);
            }

            // Now check to see if too many decision points were involved
            if (options.MaximumNumberOfDecisionPoints.HasValue &&
                results.NumberOfDecisionPoints > options.MaximumNumberOfDecisionPoints)
            {
                return(false);
            }

            // Otherwise, it's a valid removal.
            return(true);
        }
Пример #5
0
        /// <summary>Generates a random Sudoku puzzle.</summary>
        /// <returns>The generated results.</returns>
        private static SolverResults GenerateOne(GeneratorOptions options)
        {
            // Generate a full solution randomly, using the solver to solve a completely empty grid.
            // For this, we'll use the elimination techniques that yield fast solving.
            PuzzleState   solvedState   = new PuzzleState();
            SolverOptions solverOptions = new SolverOptions();

            solverOptions.MaximumSolutionsToFind = 1;
            solverOptions.EliminationTechniques  = new List <EliminationTechnique> {
                new NakedSingleTechnique()
            };
            solverOptions.AllowBruteForce = true;
            SolverResults newSolution = Solver.Solve(solvedState, solverOptions);

            // Create options to use for removing filled cells from the complete solution.
            // MaximumSolutionsToFind is set to 2 so that we look for more than 1, but there's no
            // need in continuing once we know there's more than 1, so 2 is a find value to use.
            solverOptions.MaximumSolutionsToFind = 2;
            solverOptions.AllowBruteForce        =
                !options.MaximumNumberOfDecisionPoints.HasValue || options.MaximumNumberOfDecisionPoints > 0;
            solverOptions.EliminationTechniques = solverOptions.AllowBruteForce ?
                                                  new List <EliminationTechnique> {
                new NakedSingleTechnique()
            } : options.Techniques;                                                              // For perf: if brute force is allowed, techniques don't matter!

            // Now that we have a full solution, we want to randomly remove values from cells
            // until we get to a point where there is not a unique solution for the puzzle.  The
            // last puzzle state that did have a unique solution can then be used.
            PuzzleState newPuzzle = newSolution.Puzzle;

            // Get a random ordering of the cells in which to test their removal
            Point [] filledCells = GetRandomCellOrdering(newPuzzle);

            // Do we want to ensure symmetry?
            int filledCellCount = options.EnsureSymmetry && (filledCells.Length % 2 != 0) ? filledCells.Length - 1 : filledCells.Length;

            // If ensuring symmetry...
            if (options.EnsureSymmetry)
            {
                // Find the middle cell and put it at the end of the ordering
                for (int i = 0; i < filledCells.Length - 1; i++)
                {
                    Point p = filledCells[i];
                    if (p.X == newPuzzle.GridSize - p.X - 1 &&
                        p.Y == newPuzzle.GridSize - p.Y - 1)
                    {
                        Point temp = filledCells[i];
                        filledCells[i] = filledCells[filledCells.Length - 1];
                        filledCells[filledCells.Length - 1] = temp;
                    }
                }

                // Modify the random ordering so that paired symmetric cells are next to each other
                // i.e. filledCells[i] and filledCells[i+1] are symmetric pairs
                for (int i = 0; i < filledCells.Length - 1; i += 2)
                {
                    Point p  = filledCells[i];
                    Point sp = new Point(newPuzzle.GridSize - p.X - 1, newPuzzle.GridSize - p.Y - 1);
                    for (int j = i + 1; j < filledCells.Length; j++)
                    {
                        if (filledCells[j].Equals(sp))
                        {
                            Point temp = filledCells[i + 1];
                            filledCells[i + 1] = filledCells[j];
                            filledCells[j]     = temp;
                            break;
                        }
                    }
                }

                // In the order of the array, try to remove each pair from the puzzle and see if it's
                // still solvable and in a valid way.  If it is, greedily leave those cells out of the puzzle.
                // Otherwise, skip them.
                byte [] oldValues = new byte[2];
                for (int filledCellNum = 0;
                     filledCellNum < filledCellCount && newPuzzle.NumberOfFilledCells > options.MinimumFilledCells;
                     filledCellNum += 2)
                {
                    // Store the old value so we can put it back if necessary,
                    // then wipe it out of the cell
                    oldValues[0] = newPuzzle[filledCells[filledCellNum]].Value;
                    oldValues[1] = newPuzzle[filledCells[filledCellNum + 1]].Value;
                    newPuzzle[filledCells[filledCellNum]]     = null;
                    newPuzzle[filledCells[filledCellNum + 1]] = null;

                    // Check to see whether removing it left us in a good position (i.e. a
                    // single-solution puzzle that doesn't violate any of the generation options)
                    SolverResults newResults = Solver.Solve(newPuzzle, solverOptions);
                    if (!IsValidRemoval(newPuzzle, newResults, options))
                    {
                        newPuzzle[filledCells[filledCellNum]]     = oldValues[0];
                        newPuzzle[filledCells[filledCellNum + 1]] = oldValues[1];
                    }
                }

                // If there are an odd number of cells in the puzzle (which there will be
                // as everything we're doing is 9x9, 81 cells), try to remove the odd
                // cell that doesn't have a pairing.  This will be the middle cell.
                if (filledCells.Length % 2 != 0)
                {
                    // Store the old value so we can put it back if necessary,
                    // then wipe it out of the cell
                    int  filledCellNum = filledCells.Length - 1;
                    byte oldValue      = newPuzzle[filledCells[filledCellNum]].Value;
                    newPuzzle[filledCells[filledCellNum]] = null;

                    // Check to see whether removing it left us in a good position (i.e. a
                    // single-solution puzzle that doesn't violate any of the generation options)
                    SolverResults newResults = Solver.Solve(newPuzzle, solverOptions);
                    if (!IsValidRemoval(newPuzzle, newResults, options))
                    {
                        newPuzzle[filledCells[filledCellNum]] = oldValue;
                    }
                }
            }
            // otherwise, it's much easier.
            else
            {
                // Look at each cell in the random ordering.  Try to remove it.
                // If it works to remove it, do so greedily.  Otherwise, skip it.
                for (int filledCellNum = 0;
                     filledCellNum < filledCellCount && newPuzzle.NumberOfFilledCells > options.MinimumFilledCells;
                     filledCellNum++)
                {
                    // Store the old value so we can put it back if necessary,
                    // then wipe it out of the cell
                    byte oldValue = newPuzzle[filledCells[filledCellNum]].Value;
                    newPuzzle[filledCells[filledCellNum]] = null;

                    // Check to see whether removing it left us in a good position (i.e. a
                    // single-solution puzzle that doesn't violate any of the generation options)
                    SolverResults newResults = Solver.Solve(newPuzzle, solverOptions);
                    if (!IsValidRemoval(newPuzzle, newResults, options))
                    {
                        newPuzzle[filledCells[filledCellNum]] = oldValue;
                    }
                }
            }

            // Make sure to now use the techniques specified by the user to score this thing
            solverOptions.EliminationTechniques = options.Techniques;
            SolverResults finalResult = Solver.Solve(newPuzzle, solverOptions);

            // Return the best puzzle we could come up with
            newPuzzle.Difficulty = options.Difficulty;
            return(new SolverResults(PuzzleStatus.Solved, newPuzzle, finalResult.NumberOfDecisionPoints, finalResult.UseOfTechniques));
        }
Пример #6
0
        /// <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));
        }