private static bool BruteForceRecursion(Board work, ConstraintData data, int Index) { if (Index == 81) return true; if (work[Index] > 0) return BruteForceRecursion(work, data, Index + 1); var loc = new Location(Index); for (int i = 1; i < 10; i++) { if (!data.DigitInRow[i, loc.Row] && !data.DigitInColumn[i, loc.Column] && !data.DigitInZone[i, loc.Zone]) { work[loc] = i; data.DigitInRow[i, loc.Row] = data.DigitInColumn[i, loc.Column] = data.DigitInZone[i, loc.Zone] = true; if (BruteForceRecursion(work, data, Index + 1)) return true; data.DigitInRow[i, loc.Row] = data.DigitInColumn[i, loc.Column] = data.DigitInZone[i, loc.Zone] = false; } } work[Index] = 0; return false; }
/// <summary> /// Tries each filled location, in order. If the result has more than one solution, it is filled back in /// </summary> /// <returns></returns> public Board AllSingles() { var result = new Board(_parent); var Filled = _parent.Find.FilledLocations(); foreach (var loc in Filled) { int test = result[loc]; result[loc] = 0; var possible = result.Find.Candidates(loc); if (possible.Count > 1) { int count = 0; foreach (var item in possible) { result[loc] = item; if (result.Fill.Sequential() != null) count++; } if (count > 1) result[loc] = test; else result[loc] = 0; } } return result; }
public void CreatePuzzle() { for (int iter = 0; iter < Iterations; iter++) { var Work = Factory.Solution(rnd); for (int i = 0; i < 3; i++) { var step = new Board(Work); for (int j = 0; j < 3; j++) { Location loc = rnd.Next(81); step[loc] = 0; step[loc.FlipHorizontal()] = 0; step[loc.FlipVertical()] = 0; step[loc.FlipVertical().FlipHorizontal()] = 0; } if (step.ExistsUniqueSolution()) Work = step; } givens[0].Add(Work.Find.FilledLocations().Count()); } WriteStatistics("Givens in generated puzzles: ", givens[0]); }
private static bool RandomRecursion(Board work, ConstraintData data, List<int> Digits, int Index) { if (Index == 81) return true; var loc = new Location(Index); if (work[loc] > 0) return RandomRecursion(work, data, Digits, Index + 1); foreach (int test in Digits) { if (!data.DigitInRow[test, loc.Row] && !data.DigitInColumn[test, loc.Column] && !data.DigitInZone[test, loc.Zone]) { work[loc] = test; data.DigitInRow[test, loc.Row] = data.DigitInColumn[test, loc.Column] = data.DigitInZone[test, loc.Zone] = true; if (RandomRecursion(work, data, Digits, Index + 1)) return true; data.DigitInRow[test, loc.Row] = data.DigitInColumn[test, loc.Column] = data.DigitInZone[test, loc.Zone] = false; } } work[loc] = 0; return false; }
public void TestFill() { var b = new SudokuSharp.Board(Data); foreach (var idx in Location.All) { Assert.AreEqual(b[idx], Data[idx]); } }
public ConstraintData(Board Src) { foreach (var loc in Location.All) { DigitInRow[Src[loc], loc.Row] = DigitInColumn[Src[loc], loc.Column] = DigitInZone[Src[loc], loc.Zone] = true; } }
/// <summary> /// Provides a completely filled, randomly generated, Sudoku <see cref="Board"/> /// </summary> /// <param name="Stream">An existing <see cref="Random"/> number generator</param> /// <returns><see cref="Board"/></returns> public static Board Solution(Random Stream) { Board work = null; do { work = new Board().Fill.Randomized(Stream); } while (work == null); return work; }
/// <summary> /// Attempts to fill the calling <see cref="Board"/> instance with numbers. /// The original instance remains unchanged /// </summary> /// <returns>Either a new instance of <see cref="Board"/> or, if unsuccessful, null</returns> public Board Sequential() { var work = new Board(_parent); ConstraintData data = new ConstraintData(work); if (BruteForceRecursion(work, data, 0)) return work; return null; }
/// <summary> /// Calls Cut.Quad, .Pair, and .Single the specified number of times on the provided <see cref="Board"/> /// </summary> /// <param name="Source">The <see cref="Board"/> to be modified</param> /// <param name="Stream">An existing <see cref="Random"/> number generator</param> /// <param name="QuadsToCut">The number of times to call Cut.Quad</param> /// <param name="PairsToCut">The number of times to call Cut.Pair</param> /// <param name="SinglesToCut">The number of times to call Cut.Single</param> /// <returns></returns> public static Board Puzzle(Board Source, Random Stream, int QuadsToCut, int PairsToCut, int SinglesToCut) { var work = new Board(Source); for (int i = 0; i < QuadsToCut; i++) work = work.Cut.Quad(Stream); for (int i = 0; i < PairsToCut; i++) work = work.Cut.Pair(Stream); for (int i = 0; i < SinglesToCut; i++) work = work.Cut.Single(Stream); return work; }
/// <summary> /// Attempts to fill the calling <see cref="Board"/> instance with numbers. /// The original instance remains unchanged /// </summary> /// <param name="Stream">If you already have a <see cref="Random"/> stream, you may provide it here</param> /// <returns>Either a new instance of <see cref="Board"/> or, if unsuccessful, null</returns> public Board Randomized(Random Stream) { var result = new Board(_parent); ConstraintData data = new ConstraintData(result); var digits = new List<int>(); for (int i = 1; i < 10; i++) digits.Insert(Stream.Next(digits.Count), i); if (RandomRecursion(result, data, digits, 0)) return result; return null; }
public void FindEmptyLocations() { for (int iter = 0; iter < Iterations; iter++) { var src = Factory.Solution(rnd); for (int i = 0; i < NumBatches; i++) { var work = new Board(src); for (int cut = 0; cut < 60; cut++) work[rnd.Next(81)] = 0; for (int j = 0; j < BatchSize; j++) { var result = work.Find.EmptyLocations(); } } } }
private static int CountRecursion(Board work, int idx) { if (idx == 81) // using int instead of Location because Location CAN'T have a value of 81 return 1; if (work[idx] > 0) return CountRecursion(work, idx + 1); var possible = work.Find.Candidates(idx); if (possible.Count == 0) return 0; int count = 0; foreach (var item in possible) { work[idx] = item; count += CountRecursion(work, idx + 1); } work[idx] = 0; return count; }
/// <summary> /// Verifies the existance of a unique solution /// </summary> /// <value> /// <c>true</c> if [a unique solution exists]; otherwise, <c>false</c>. /// </value> public bool ExistsUniqueSolution() { if (IsSolved) return true; if (!IsValid) return false; for (int i = 0; i < 81; i++) { if (GetCell(i) == 0) { // Only test against empty cells var Candidates = Find.Candidates(i); if (Candidates.Count > 1) { // Only test where there's more than one option bool foundSolution = false; var working = new Board(this); foreach (int test in Candidates) { working[i] = test; if (working.Fill.Sequential() != null) { // We just found a solution. If we have already found a solution, then multiple exist and we may quit. if (foundSolution) return false; foundSolution = true; } } } } } return true; }
/// <summary> /// Attempts to count all possible solutions /// </summary> /// <returns></returns> public int CountSolutions() { Board work = new Board(this); // fill everything that has definite answers var mustFill = work.Find.AllSingles().Union(work.Find.LockedCandidates()); while (mustFill.Count() > 0) { foreach (var item in mustFill) work[item.Key] = item.Value; mustFill = work.Find.LockedCandidates(); } if (IsSolved) return 1; return CountRecursion(work, 0); }
/// <summary> /// Hack to allow a namespace inside a class. /// </summary> public _CutClass(Board Parent) { _parent = Parent; }
/// <summary> /// Attempts to cut a single location /// </summary> /// <param name="Stream">An existing <see cref="Random"/> number generator</param> /// <returns>If the result has a unique solution, then the new <see cref="Board"/>. Otherwise, the original</returns> public Board Single(Random Stream) { var result = new Board(_parent); var Filled = _parent.Find.FilledLocations().ToList(); if (Filled.Count > 0) { result[Filled[Stream.Next(Filled.Count)]] = 0; if (result.ExistsUniqueSolution()) return result; } return _parent; }
/// <summary> /// Attempts to cut 4 locations from the current board, mirrored about both horizontal and vertical axes. /// </summary> /// <param name="Stream">An existing <see cref="Random"/> number generator</param> /// <returns>If the result has a unique solution, then the new <see cref="Board"/>. Otherwise, the original</returns> public Board Quad(Random Stream) { var result = new Board(_parent); var Filled = _parent.Find.FilledLocations().ToList(); if (Filled.Count > 0) { Location loc = new Location(Filled[Stream.Next(Filled.Count)]); result[loc] = 0; result[loc.FlipHorizontal()] = 0; result[loc.FlipVertical()] = 0; result[loc.FlipHorizontal().FlipVertical()] = 0; if (result.ExistsUniqueSolution()) return result; } return _parent; }
/// <summary> /// Copies an instance of the <see cref="Board"/> class. /// </summary> /// <param name="src">The source.</param> public Board(Board src) { Array.Copy(src.data, this.data, 81); }
/// <summary> /// Hack to allow a namespace inside a class. /// </summary> public _FillClass(Board Parent) { _parent = Parent; }