/// <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)); }
/// <summary>Clones the solver options.</summary> /// <returns>A clone of the options.</returns> public GeneratorOptions Clone() { GeneratorOptions options = new GeneratorOptions(); options._difficulty = _difficulty; options._ensureSymmetry = _ensureSymmetry; options._maximumNumberOfDecisionPoints = _maximumNumberOfDecisionPoints; options._minimumFilledCells = _minimumFilledCells; options._numberOfPuzzles = _numberOfPuzzles; options.ParallelGeneration = ParallelGeneration; options.SpeculativeGeneration = SpeculativeGeneration; foreach (EliminationTechnique technique in _eliminationTechniques) { options._eliminationTechniques.Add(technique.Clone()); } return options; }