/// <summary> /// Solve a Sudoku with the specified initial state (using the standard dot/number representation) /// Example: "...84...9..1.....58...2146.7.8....9...........5....3.1.2491...79.....5..3...84..." /// </summary> public List <SudokuBoard> Solve(string sdnot) { var solutions = new List <SudokuBoard>(); int i, j, c, r, r2, dir, cand, min, hints = 0; // dir=1: forward; dir=-1: backtrack var sr = new sbyte[729]; var cr = new sbyte[81]; // sr[r]: # times the row is forbidden by others; cr[i]: row chosen at step i var sc = new byte[324]; // bit 1-7: # allowed choices; bit 8: the constraint has been used or not var cc = new short[81]; // cc[i]: col chosen at step i var sdnot_out = new char[81]; for (r = 0; r < 729; ++r) { sr[r] = 0; // no row is forbidden } for (c = 0; c < 324; ++c) { sc[c] = unchecked (0 << 7 | 9); // 9 allowed choices; no constraint has been used } for (i = 0; i < 81; ++i) { int a = sdnot[i] >= '1' && sdnot[i] <= '9' ? sdnot[i] - '1' : -1; // number from -1 to 8 if (a >= 0) { UpdateStateVectors(sr, sc, i * 9 + a, 1); // set the choice } if (a >= 0) { ++hints; // count the number of hints } cr[i] = -1; cc[i] = -1; sdnot_out[i] = sdnot[i]; } for (i = 0, dir = 1, cand = 10 << 16 | 0; ;) { while (i >= 0 && i < 81 - hints) { // maximum 81-hints steps if (dir == 1) { min = cand >> 16; cc[i] = unchecked ((short)(cand & 0xffff)); if (min > 1) { for (c = 0; c < 324; ++c) { if (sc[c] < min) { min = sc[c]; cc[i] = (short)c; // choose the top constraint if (min <= 1) { break; // this is for acceleration; slower without this line } } } } if (min == 0 || min == 10) { cr[i--] = unchecked ((sbyte)(dir = -1)); // backtrack } } c = cc[i]; if (dir == -1 && cr[i] >= 0) { UpdateStateVectors(sr, sc, _aux.r[c, cr[i]], -1); // revert the choice } for (r2 = cr[i] + 1; r2 < 9; ++r2) // search for the choice to make { if (sr[_aux.r[c, r2]] == 0) { break; // found if the state equals 0 } } if (r2 < 9) { cand = UpdateStateVectors(sr, sc, _aux.r[c, r2], 1); // set the choice cr[i++] = unchecked ((sbyte)r2); dir = 1; // moving forward } else { cr[i--] = unchecked ((sbyte)(dir = -1)); // backtrack } } if (i < 0) { break; } for (j = 0; j < i; ++j) { r = _aux.r[cc[j], cr[j]]; sdnot_out[r / 9] = (char)(r % 9 + '1'); // print } var solution = new SudokuBoard(new string(sdnot_out)); solutions.Add(solution); if (solutions.Count >= _maxSolutions) { break; } --i; dir = -1; // backtrack } return(solutions); // return the number of solutions }
/// <summary> /// Create a game with the specified number of given cells. /// There is no quarantee that a low number of givens is achievable. /// You can increase the probability to get a low number of givens, by increasing the maximum number of attempts. /// The actual number of givens can be retrieved from the returned game object. /// </summary> public static SudokuGame Create(int givensCount, int maxAttempts = 2000) { // Start with a random finished board // and use it as the initial board var solution = RandomFinishedBoard(); var initialBoard = new SudokuBoard(solution); // start with finished var cells = initialBoard.Cells; // Create a solver that returns 2 solutions at most var solver = new SudokuSolver(maxSolutions: 2); // Start Removing Numbers one by one from the cells of the initial board // A higher number of attempts will end up removing more numbers from the grid //Potentially resulting in more difficiult grids to solve! var rnd = new Random(); var cellsFilled = 81; int attempt = 0; while (true) { // Select a random cell that is not already empty var cell = rnd.Next(0, cellsFilled); int i = 0; var row = -1; var col = -1; for (int r = 0; r < 9; r++) { for (int c = 0; c < 9; c++) { if (cells[r, c] != 0) { if (cell == i++) { row = r; col = c; break; } } } } if (row == -1) { // no empty cell to fill return(new SudokuGame(initialBoard, solution)); } // Remember its cell value in case we need to put it back var backup = cells[row, col]; cells[row, col] = 0; cellsFilled--; // Count the number of solutions that this grid has (using a backtracking approach implemented Solve()) // If the number of solution is different from 1 then we need to cancel the change // by putting the value we took away back in the grid if (solver.Solve(initialBoard).Count != 1) { cells[row, col] = backup; cellsFilled++; } if (cellsFilled <= givensCount) { break; } attempt++; if (attempt >= maxAttempts) { break; } } Console.WriteLine($"Generated in {attempt} attempts, with {cellsFilled} cells left"); return(new SudokuGame(initialBoard, solution)); }
/// <summary> /// Solve a Sudoku with the specified initial state /// </summary> public List <SudokuBoard> Solve(SudokuBoard initialBoard) { return(Solve(initialBoard.SudokuNotation)); }
public SudokuGame(SudokuBoard initialBoard, SudokuBoard finishedBoard) { InitialBoard = initialBoard; Solution = finishedBoard; }