/// <summary> /// Solves the grid in-place. /// </summary> /// <param name="grid"> /// The grid to solve. This grid will be modified to contain the solution to /// the sudoku puzzle, if it exists. /// </param> /// <returns> /// If the puzzle can be unambiguously solved, Solve() will return true. /// If the puzzle cannot be solved or has more than one solution, /// this will return false. /// </returns> public bool Solve(Grid grid) { int numSolutions = 0; Grid solutionGrid = grid.Copy(); if (FindErrors(grid).Count > 0) { // fail fast! return(false); } // this bit here GREATLY simplifies DepthFirstSearch. // Using exceptions and a lambda to terminate after two // solutions frees DepthFirstSearch from worrying about // all that logic at the cost of a closure and tricky // exception handling. // all of this is to avoid C#'s lack of generators, buh try { DepthFirstSearch(grid, () => { // this lambda gets run on each solution numSolutions++; solutionGrid = grid.Copy(); if (numSolutions > 1) { throw new StopIterationException(); } }); } catch (StopIterationException e) { } // (just pretend that empty catch block is a generator) grid.CopyFrom(solutionGrid); return(numSolutions == 1); }
/// <summary> /// When the File -> Save Game Unsolved menu item is chosen /// </summary> private void FileSaveGameUnsolvedClick(object sender, EventArgs e) { Grid unsolvedGrid = grid.Copy(); unsolvedGrid.ForEachSquare((row, col, val) => { if (unsolvedGrid.IsEditable(row, col)) { unsolvedGrid.Clear(row, col); } }); GameManager.SaveGame(unsolvedGrid); }
/// <summary> /// When the player transitions from "edit grid" mode to "play this game" mode /// </summary> internal static void PlayThisPuzzle(Grid oldGrid, Form form) { // Set all the squares as non-editable Grid grid = oldGrid.Copy(); grid.ForEachSquare((row, col, val) => { if (val != 0) { grid.SetEditable(false, row, col); } }); // Tries to solve the grid. If the grid has no unique solution, warn first. Grid copyGrid = grid.Copy(); // yes another copy; the solver will mess with this one. bool result = (new Solver().Solve(copyGrid)); if (result) { SaveGame(grid); form.Hide(); GameForm gform = new GameForm(grid, true); gform.ShowDialog(); form.Close(); } else { if (MessageBox.Show(copyGrid.IsFull()? "This puzzle can be solved in more than one way. Play anyway?" : "This puzzle cannot be solved. Play anyway?", "Unsolvable Puzzle", MessageBoxButtons.YesNo) == DialogResult.Yes) { SaveGame(grid); form.Hide(); GameForm gform = new GameForm(grid, true); gform.ShowDialog(); form.Close(); } } }
/// <summary> /// Creates a new form for playing the game /// </summary> /// <param name="grid">The grid to start with</param> /// <param name="playingMode">If true: We're "playing" the game. If false: We're "editing" the game.</param> public GameForm(Grid grid, bool playingMode) { InitializeComponent(); this.grid = grid; this.isPlaying = playingMode; hintBarText.Text = ""; if (isPlaying) { solveButton.Text = "&Solve"; } else { solveButton.Text = "&Enter Puzzle"; } // make the grid control and put it somewhere this.gcontrol = new SudokuGridControl(grid); this.gridPanel.Controls.Add(gcontrol); gcontrol.Dock = DockStyle.Fill; // When the cell is cleared, mark/unmark errors and hints gcontrol.CellClear += (int row, int col) => { nagAboutErrors = true; nagAboutWonGame = true; RecalculateErrors(); RecalculateHints(row, col); ShowOrHideHintBar(); }; // When the cell's value is changed, mark/unmark errors and hints gcontrol.CellChange += (int row, int col) => { MaybeTryGameOver(); RecalculateErrors(); RecalculateHints(row, col); ShowOrHideHintBar(); }; // When the user selects a different cell, mark/unmark errors and hints gcontrol.CellFocused += (int row, int col) => { ShowOrHideHintBar(); if (isPlaying) { RecalculateHints(row, col); } }; // Add a drop-down context menu to each textbox gcontrol.ForEachTextBox((TextBox tbox, int row, int col) => { tbox.ContextMenu = new ContextMenu(); if (isPlaying) { // The context menu should only appear when in "Playing" mode tbox.ContextMenu.MenuItems.Add(new MenuItem("Show &Hints", (s, e) => { hintBarText.Show(); // the hints bar will disappear on next call to ShowOrHideHints() })); tbox.ContextMenu.MenuItems.Add(new MenuItem("&Solve This Square", (s, e) => { // solve the grid and copy the value if (grid.IsEditable(row, col)) { Grid solvedGrid = grid.Copy(); solver.Solve(solvedGrid); if (!solvedGrid.IsFull()) { MessageBox.Show("This square cannot be solved because there are errors in your puzzle. Please erase some of your work and try again."); } else { grid.Set(solvedGrid.Get(row, col), true, row, col); } gcontrol.UpdateGridView(); RecalculateErrors(); } })); } }); // initial setup: start the timer and optionally show errors. gameTimerTick(this, new EventArgs()); RecalculateErrors(); ShowOrHideHintBar(); }
/// <summary> /// Overwrite this grid's elements with elements from another grid. /// The two grids will share nothing. /// </summary> public void CopyFrom(Grid other) { this.elts = other.Copy().elts; }
/// <summary> /// Generates a grid with the given difficulty level. /// </summary> public Grid Generate(DifficultyLevel difficulty) { // Generates stuff // 1. Randomnly fill in the top and left part of the grid. // 2. Try to solve it // 3. Until we have enough blanks: // remove some blanks // if it isn't uniquely solvable, add those two blanks again. Grid grid = GenerateBlankGrid(); Grid result; // Top row List <int> row = Enumerable.Range(1, 9).OrderBy((i) => rand.Next()).ToList(); for (int i = 0; i < 9; i++) { grid.Set(row[i], false, i, 0); } // Top column row = Enumerable.Range(1, 9).OrderBy((i) => rand.Next()).ToList(); row.Remove(grid.Get(0, 0)); for (int i = 0; i < 8; i++) { grid.Set(row[i], false, 0, i + 1); } if (solver.FindErrors(grid).Count > 0) { // This is not enough to guarantee correctness. // If we get an invalid grid, try try again! result = Generate(difficulty); } else { solver.Solve(grid); // How many blanks do we need? int targetBlanks = 0; switch (difficulty) { case DifficultyLevel.Easy: targetBlanks = 30; break; case DifficultyLevel.Medium: targetBlanks = 45; break; case DifficultyLevel.Hard: targetBlanks = 50; break; } // Remove squares until we have the right number of blanks. int tries = 0; while (tries < 100 && CountBlank(grid) < targetBlanks) { Grid saveCopy = grid.Copy(); // Solving is expensive. Blanking squares is easy! // When the grid is mostly full, you can blank squares // in relative safety without generating a non-unique // puzzle. When the puzzle gets more sparse, you // need to be more careful. // That's what we're doing here. Quick optimization. for (int i = 0; i < (targetBlanks - CountBlank(grid)) / 2 + 1; i++) { MaybeRandomBlank(grid); } if (!solver.Solve(grid.Copy())) { // it failed grid = saveCopy; } tries++; } //Console.WriteLine("Generated puzzle in " + tries + " tries with "+CountBlank(grid)+" blanks"); // Finally, set every square to be not editable grid.ForEachSquare((r, c, val) => { if (val != 0) { grid.SetEditable(false, r, c); } }); result = grid; } return(result); }
/// <summary> /// Solves the grid in-place. /// </summary> /// <param name="grid"> /// The grid to solve. This grid will be modified to contain the solution to /// the sudoku puzzle, if it exists. /// </param> /// <returns> /// If the puzzle can be unambiguously solved, Solve() will return true. /// If the puzzle cannot be solved or has more than one solution, /// this will return false. /// </returns> public bool Solve(Grid grid) { int numSolutions = 0; Grid solutionGrid = grid.Copy(); if (FindErrors(grid).Count > 0) { // fail fast! return false; } // this bit here GREATLY simplifies DepthFirstSearch. // Using exceptions and a lambda to terminate after two // solutions frees DepthFirstSearch from worrying about // all that logic at the cost of a closure and tricky // exception handling. // all of this is to avoid C#'s lack of generators, buh try { DepthFirstSearch(grid, () => { // this lambda gets run on each solution numSolutions++; solutionGrid = grid.Copy(); if (numSolutions > 1) { throw new StopIterationException(); } }); } catch (StopIterationException e) { } // (just pretend that empty catch block is a generator) grid.CopyFrom(solutionGrid); return numSolutions == 1; }