/// <summary> /// Takes as input a 9x9 integer grid array and a file name, /// and returns true if successful in writing the named file. /// </summary> public bool WriteFile(Grid grid, string fileName) { try { // Write the string to a file. System.IO.StreamWriter file = new System.IO.StreamWriter(fileName); for (int i = 1; i < 10; i++) { string line = ""; for (int j = 1; j < 10; j++) { line += Math.Abs(grid.Get(i-1, j-1)); if (grid.IsEditable(i - 1, j - 1)) { line += '+'; } else{ line += '-'; } } file.WriteLine(line); } file.Close(); return true; } catch (Exception e) { // Let the user know what went wrong. MessageBox.Show("The file could not be written: '"+e.Message+"'"); return false; } }
/// <summary> /// Takes as input a 9x9 integer grid array and a file name, /// and returns true if successful in reading the named file. /// </summary> public bool ReadFile(Grid grid, string fileName) { try { // Create an instance of StreamReader to read from a file. // The using statement also closes the StreamReader. using (StreamReader sr = new StreamReader(fileName)) { String line; // Read and display lines from the file until the end of // the file is reached. for (int i=0;i<9;i++) { line= sr.ReadLine(); for (int j = 1; j < 10; j++) { if (line[2 * (j - 1) + 1] == '-') { grid.Set((0-(line[2 * (j - 1)] - 48)), false, i, j-1); } else { grid.Set(line[2 * (j - 1)] - 48, true, i, j-1); } } } } return true; } catch (Exception e) { // Let the user know what went wrong. MessageBox.Show("The file could not be read: '"+e.Message+"'"); return false; } }
/// <summary> /// This control is a sudoku grid control. It displays a sudoku grid /// in an attractive way. /// </summary> public SudokuGridControl(Grid bgrid) { this.Resize += new EventHandler(FixFontsAndSize); this.ResizeRedraw = false; InitializeComponent(); this.grid = bgrid; ResetTextboxen(); UpdateGridView(); FixFontsAndSize(new Object(), new EventArgs()); }
/// <summary> /// Shows a dialog for saving the grid. /// </summary> /// <param name="grid">The grid to be saved</param> /// <returns>Whether the save succeeded. False if the user canceled.</returns> public static bool SaveGame(Grid grid) { bool result = false; SaveFileDialog dialog = new SaveFileDialog(); dialog.Filter = "Sudoku Game|*.sud"; dialog.Title = "Save Game"; dialog.ShowDialog(); if (dialog.FileName != "") { new File().WriteFile(grid, dialog.FileName); result = true; } return result; }
/// <summary> /// Loads a game from disk (showing load dialog) and then shows the game form. /// </summary> /// <param name="form">This form will be hidden.</param> public static bool LoadGame(Form form) { bool result = false; File fileOpener = new File(); OpenFileDialog dialog = new OpenFileDialog(); dialog.Filter = "Sudoku Game|*.sud"; dialog.Title = "Load Game"; dialog.ShowDialog(); if (dialog.FileName != "") { form.Hide(); Grid newGrid = new Grid(); fileOpener.ReadFile(newGrid, dialog.FileName); GameForm gform = new GameForm(newGrid, true); gform.ShowDialog(); form.Close(); result = true; } return result; }
/// <summary> /// The actual meat of the depthfirst search goes here. /// (solve is mostly bookkeeping) /// </summary> /// <param name="eachSolutionAction"> /// This lambda will be run once for each solution. /// </param> /// <param name="grid"> /// This grid will be left in an undefined state /// after DepthFirstSearch() terminates. /// If you want a good solution, be sure to save /// a copy in your eachSolutionAction. /// </param> public void DepthFirstSearch(Grid grid, Action eachSolutionAction) { CellConsideration cell = Consider(grid); if (grid.IsFull()) { // Found a solution! eachSolutionAction(); } else if (cell != null) { foreach (int hint in cell.PossibleValues) { grid.Set(hint, true, cell.Row, cell.Col); DepthFirstSearch(grid, eachSolutionAction); grid.Set(0, true, cell.Row, cell.Col); // speed hack: we know the previous square // was a zero, so no need to save a copy // of the grid every time. } } }
/// <summary> /// This method creates three new sudoku grids. /// </summary> private void makeNewGrids() { easy = gen.Generate(DifficultyLevel.Easy); easyPanel.Controls.Clear(); SudokuGridControl easyGcontrol = new SudokuGridControl(easy); easyPanel.Controls.Add(easyGcontrol); easyGcontrol.Dock = DockStyle.Fill; easyGcontrol.IsEditable = false; med = gen.Generate(DifficultyLevel.Medium); medPanel.Controls.Clear(); SudokuGridControl medGcontrol = new SudokuGridControl(med); medPanel.Controls.Add(medGcontrol); medGcontrol.Dock = DockStyle.Fill; medGcontrol.IsEditable = false; hard = gen.Generate(DifficultyLevel.Hard); hardPanel.Controls.Clear(); SudokuGridControl hardGcontrol = new SudokuGridControl(hard); hardPanel.Controls.Add(hardGcontrol); hardGcontrol.Dock = DockStyle.Fill; hardGcontrol.IsEditable = false; }
private void medButtonClick(object sender, EventArgs e) { result = med; hasResult = true; this.Close(); }
private void hardButton_Click(object sender, EventArgs e) { result = hard; hasResult = true; this.Close(); }
/// <summary> /// Helper function: count the blank squares in the given grid. /// </summary> /// <returns>the number of blank squares</returns> private int CountBlank(Grid grid) { int nBlanks = 0; grid.ForEachSquare((row, col, val) => { if (val == 0) { nBlanks+=1; } }); return nBlanks; }
/// <summary> /// Considers each cell of the grid. /// </summary> /// <returns> /// Returns the cell with the smallest number /// of possible values. This square is a good /// place to continue search. /// Returns null if the grid is invalid. /// </returns> private CellConsideration Consider(Grid grid) { int smallestNOfHints = int.MaxValue; CellConsideration smallestConsideration = null; for (int row = 0; row < 9; row++) { for (int col = 0; col < 9; col++) { if (grid.Get(row, col) == 0) { List<int> hints = GetHintsFor(grid, row, col); if (hints.Count == 0) { // We found a square that has no solution // This means the current state of the puzzle is bogus // so stop. return null; } if (hints.Count < smallestNOfHints) { smallestConsideration = new CellConsideration(row, col, hints); smallestNOfHints = hints.Count; } } } } return smallestConsideration; }
/// <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> /// Returns true if the puzzle isn't obviously broken. /// </summary> public bool IsValidPuzzle(Grid grid) { return FindErrors(grid).Count == 0; }
/// <summary> /// Get the list of valid moves for the given cell in the given grid. /// Used by the Hints bar and Consider() /// </summary> /// <returns> /// A list of valid move choices. /// </returns> public List<int> GetHintsFor(Grid grid, int row, int col) { // believe it or not, Lists are faster than int[]s for some reason List<int> stuff = grid.GetColumn(col).Concat(grid.GetRow(row)).Concat(grid.GetSquareAbout(row, col)).ToList(); List<int> results = new List<int>(); for (int i = 1; i <= 9; i++) { if (!stuff.Contains(i)) { results.Add(i); } } return results; }
/// <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> /// Finds errors in the grid. Returns a list of cells such that: /// - there is another cell with the same value in the row /// - there is another cell with the same value in the column /// - there is another cell with the same value in the 3x3 square /// where the cell resides /// </summary> public List<List<int>> FindErrors(Grid grid) { List<List<int>> errors = new List<List<int>>(); for (int row = 0; row < 9; row++) { for (int col = 0; col < 9; col++) { int val = grid.Get(row, col); // The same cell in the row? bool valueInRow = grid.GetRow(row).Count((eachCell) => eachCell == val) > 1; // The same cell in the column? bool valueInCol = grid.GetColumn(col).Count((eachCell) => eachCell == val) > 1; // The same cell in the 3x3 square? bool valueInSquare = grid.GetSquareAbout(row, col).Count((eachCell) => eachCell == val) > 1; if (val != 0 && (valueInRow || valueInCol || valueInSquare)) { errors.Add(new List<int>(new int[] { row, col })); } } } return errors; }
/// <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; }
private void easyButton_Click(object sender, EventArgs e) { result = easy; hasResult = true; this.Close(); }
/// <summary> /// Blank two squares in a symmetric fashion, or not. /// /// We always use rotational symmetry because it's prettiest. /// </summary> private void MaybeRandomBlank(Grid grid) { int row1 = rand.Next(9); int col1 = rand.Next(9); int row2 = (8 - row1); int col2 = (8 - col1); if (grid.Get(row1, col1) != 0 && grid.Get(row2, col2) != 0) { grid.Set(0, true, row1, col1); grid.Set(0, true, row2, col2); } }
/// <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(); }