Пример #1
0
        /// <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);
        }
Пример #2
0
        /// <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);
        }
Пример #3
0
        /// <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();
                }
            }
        }
Пример #4
0
        /// <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();
        }
Пример #5
0
 /// <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;
 }
Пример #6
0
 /// <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;
 }
Пример #7
0
        /// <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);
        }
Пример #8
0
        /// <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;
        }
Пример #9
0
        /// <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();
                }
            }
        }
Пример #10
0
        /// <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();
        }