Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Sudoku Game in C#

0.00/5 (No votes)
25 Dec 2005 1  
The sudoku game in C#.

Introduction

I came across the Sudoku game a few months ago. I commute by train daily to my office; this puzzle, in everyday newspaper, was one of my favorite ways to pass time in the train. I thought why not develop this game in C#? This article discusses the implementation of this game in C#. The game provides three complexity levels: simple, medium and complex. The user can view the answer for a few seconds to get a clue. I hope you will enjoy playing this game. You require .NET Framework installed on your PC or laptop to run this program.

Rules of Sudoku

The game has simple rules. The game presented in this article arranges numbers in 9X9 matrix, which is the most common form of this game. The rules are as follows:

  1. The number, in each row and column, should be between 1 and 9 and should appear only once.
  2. The 9X9 matrix is made of nine 3X3 matrices. So, the number in each of these subsets should also be between 1 and 9 and should appear only once.

Now that we know the rules, let us see the implementation.

Implementation

The main class implementing this game is Sudoku and is implemented in Sudoku.cs file. The view is implemented using a DataGrid and the main form for implementing the view is in SudokuMainForm.cs file. The basic design idea is to first generate the solution and then unmask certain spots based on the complexity level. Initially, I spent some time to create a solution by populating sets which were independent using random numbers between 1 and 9. Then, I tried to fill other sets. This was a bit complex and was taking time. Hence, I decided to take one unique solution as the base and then derive other unique solutions by swapping rows, columns, sets and reversing numbers. This way, I could generate 1000s of combinations. GenerateGame() method of the Sudoku class uses this technique. The other part implements the view. I have used DataGrid which is a very useful control to represent data in tabular form. I have used DataSet which is easier to bind to the grid. As I am using two dimensional array to keep the problem and answer sets, I have exposed two properties ProblemSet and AnswerSet which return the data in two dimensional array as a DataSet.

Another important part of the game is to provide data validations. The ColumnChanging event of the DataTable is quite useful and I have used this event to handle data validations such as "valid number", "answer position not changed" and "number is not a duplicate". The event arguments of this event enable us to set the appropriate error message for the affected column. I have provided a button control to show the answer, which displays the answer for a few seconds and then brings back the problem. You can use this if the problem is complex and for getting some clues. The next section describes the code. I will only explain the important methods. You can see the full implementation by downloading the source code using the links given above.

Using the Code

Sudoku class is the main class implementing the game. GenerateGame(GameLevel level) method generates the new game. The code for this is given below:

// Method: GenerateGame
// Purpose: Generates game based on complexity level.
public void GenerateGame(GameLevel level)
{
    // InitialiseSet
    // This first creates answer set by using Game combinations
    InitialiseSet();
    int minPos,maxPos,noOfSets;
     
    // Now unmask positions and create problem set.
    switch(level)
    {    
        case GameLevel.SIMPLE:
            minPos=4;
            maxPos=6;
            noOfSets=8;
            UnMask(minPos,maxPos,noOfSets);
            break;
        case GameLevel.MEDIUM:
            minPos=3;
            maxPos=5;
            noOfSets= 7;
            UnMask(minPos,maxPos,noOfSets);
            break;
        case GameLevel.COMPLEX:
             minPos=3;
             maxPos=5;
            noOfSets = 6;
            UnMask(minPos,maxPos,noOfSets);
             break;
        default:
             UnMask(3,6,7);
              break;
    }

In the above code, GameLevel is of type enum and it defines the complexity levels. The InitialiseSet() method generates the unique solution. Then, based on the complexity level, the Unmask() method will keep some answer spots and create the ProblemSet by clearing other positions. Here, the important property is the GameSet that returns the DataSet which is used to bind to the DataGrid. The DataSet returns three sets of data: ProblemSet, a copy of ProblemSet and AnswerSet. This retrieval of all sets of data in a single DataSet simplifies the load and save functionality:

// Property:GameSet
// Return Problem Set as DataSet

public DataSet ProblemSet
{
    get{ return FormDataSet();}
}

The above property invokes the private method, FormDataSet(). This method then generates the Dataset from the array and returns to the caller. Data validations are handled using the columnchanging event of the DataTable. The following code snippet shows the event handler code:

// Handler method for data validations.
private void CurrentSet_ColumnChanging(object sender, 
                System.Data.DataColumnChangeEventArgs e) 
{
    try
    {
        lblStatus.Text="";
        int rowPos = dataGrid1.CurrentCell.RowNumber;
        string currentNumber = e.ProposedValue as string;
        
        int number =Int32.Parse(currentNumber);
        if((number < 1)||(number >9))
        {
            string errorMessage=
                "Number should be between 1 and 9";
            e.Row.SetColumnError(e.Column,errorMessage);
        }
        else
        {
            int col =e.Column.Ordinal;
            bool answerChanged = 
                _newGame.CheckForAnswerChange(rowPos,col,number);
            
            if(answerChanged)
            {
                lblStatus.Text="You can't change the answer";
                e.ProposedValue = e.Row[e.Column];

            }else if(_newGame.CheckForDuplicate(rowPos,col,number)){
               e.Row.SetColumnError(e.Column,"Number is Duplicate");
            
            }else
             {
                e.Row.ClearErrors();
                bool answerComplete= IsSolutionComplete();
                if(answerComplete)
                {
                  lblStatus.Text= "Great!!! You have done it";
                }
            }
        }
    }
    catch(Exception ex)
    {
        e.Row.SetColumnError(e.Column,
             "Enter valid Number between 1 & 9");
    }
}

The above code first reads the current row position of the cell using the datagrid1.CurrentCell.RowNumber property. The user entered value for the cell is available in the event argument property e.ProposedValue. This property is converted to a number and then all data validations are performed by calling CheckForAnswerChangd() and CheckForDuplicate() methods of the Sudoku class. The handler also checks whether the solution is complete by invoking IsSolutionComplete() method. The label control is used to show the status of validations and an appropriate message is shown if the solution is complete. The error message can be set by setting the e.Row.SetcolumnError property.

I have used the timer control to show the answer for a few seconds and then to redisplay the problem.

New Enhancements

Based on the suggestions from readers, I have amended the program with the following enhancements:

  1. Border for each mini 3X3 matrix, to distinguish it clearly from other mini-sets.
  2. Answer spots are now read-only.
  3. Spinner text control when you edit a particular cell to enter a number between 1 and 9.
  4. Load and Save facility for the game. This will enable the user to continue the game at a later time.

Border for Mini 3x3 Matrix

Since a DataGrid is used for the view, either the grid style of each cell can be customized or the border of the Datagrid can be changed. To provide border for each mini-set, we need to override the paint method of DataGrid. The following code snippet shows the code in overloaded method:

/// DataGrid Paint overridden to paint border
private void DataGrid1_Paint(object sender, 
            System.Windows.Forms.PaintEventArgs e)
{
    // Override this handler to do custom painting.
    Point currentPoint = new Point(0,0);
    Size size = new Size(PREFERRED_COLUMN_WIDTH*3,
                             PREFERRED_ROW_HEIGHT*3);
    Pen myPen = new Pen(Color.Red,3);
        
    for(int i=0;i<3;i++)
    {
        for(int j=0;j<3;j++)
        {
            currentPoint.X = i*PREFERRED_ROW_HEIGHT*3;
            currentPoint.Y = j*PREFERRED_ROW_HEIGHT*3;
            Rectangle rect = new Rectangle(currentPoint,size);
            e.Graphics.DrawRectangle(myPen,rect);
        }
    }
}

DataGridSpinnerColumn

Features 2 and 3 mentioned above are provided by implementing a custom DataGrid column. A custom DataGrid column needs to be derived from DataGridColumnStyle, which is an abstract class. Microsoft .NET Framework provides two types of custom columns which are derived from this class. One is DataGridTextBoxColumn and the other is DataGridBoolColumn. I have implemented a DataGridSpinnerColumn class for DataGridSpinnerColumn and it is derived from DataGridTextBoxColumn. This is done to reuse the basic properties and methods implemented by this class and to enhance some of its methods required for this custom column. I have used vertical scrollbar control which will act as spinner control.

The main requirement is that when we edit the cell, it should display the custom spinner column so that the user can use scroll buttons to change the cell value and when the focus goes to the other cell, then the value should be displayed in the cell. Also, the answer spots should be read only. Because of this feature, the custom column is not general purpose and cannot be used as is in other applications. This is because showing the spinner control is done by overriding the edit method which in turn checks whether the cell under edit is the answer spot. If the cell does not contain the answer spot, then only the spinner control is displayed. The code snippet for the edit method is given below.

For more information on DataGridColumnStyles, refer to the MSDN documentation.

// On edit, add scroll event handler, and display combobox
protected override void Edit(System.Windows.Forms.CurrencyManager 
    source, int rowNum, System.Drawing.Rectangle bounds, bool readOnly, 
    string instantText, bool cellIsVisible)
{
    //Call base method which is important else 
    //edit will not function properly
    base.Edit(source, rowNum, bounds, readOnly, 
                     instantText, cellIsVisible);

    // Check if cell is not empty
    if(this.TextBox.Text.TrimEnd()!="")
    {
        // Get Column position

        int dataValue = Int32.Parse(this.TextBox.Text);
        int pos = this.MappingName.LastIndexOf("col");
        if(pos > -1)
        {
            string colIndex = this.MappingName.Substring(pos+3);
            int colPos = Int32.Parse(colIndex);
            // Check whether it is answer spot.
            _answerPostion=_game.CheckIfAnswerPosition(rowNum,
                                              colPos,dataValue);
        }
    }
    else
    {
         _answerPostion =false;
    }
    if (!readOnly && cellIsVisible)
    {        
        // Save current row in the DataGrid and currency manager 
        // associated with the data source for the DataGrid
        this._currentRow = rowNum;
        this.cm = source;

        if(!_answerPostion)
        {
            // Make parent of scrollbar same as parent.
            this.vsBar.Parent = this.TextBox.Parent;
            Rectangle rect = 
                this.DataGridTableStyle.DataGrid.GetCurrentCellBounds();
            //Place this control to right.
            this.vsBar.Location = 
                new Point(rect.Right-this.SpinnerWidth,rect.Top);
            this.vsBar.Size = 
                new Size(this.SpinnerWidth,this.TextBox.Height);

            // Make the scrollbar visible and place on top textbox control
            this.vsBar.Show();
            // As textbox control also there let us bring this to front.
            this.vsBar.BringToFront();
            
            this.vsBar.Show();
            //    this.TextBox.Text= this.vsBar.Value.ToString();
            this.TextBox.ReadOnly=true;
            //Set text color properties different as we are editing cell.
            this.TextBox.BackColor = Color.Blue;
            this.TextBox.ForeColor=Color.White;
        }
        else
        {
            this.TextBox.ReadOnly=true;
            this.TextBox.BackColor=Color.White;
            this.TextBox.ForeColor =Color.Black;
        }
    }
}

Please refer to the source code for full implementation of this class.

Load and Save

I have re-factored the sudoku class to provide this facility. The properties ProblemSet and AnswerSet are now replaced by the GameSet property.

This will enable us to get all sets of data in a single DataSet. I have used ReadXml() and WritXml() methods of the DataSet class for implementing the load and save functionalities. Also, I have exposed the InitialiseGameSet() method to initialize all the data members of sudoku class once the DataSet is loaded from the XML file. This code is easy and hence not described here.

Conclusion

You have seen that how easy it is to implement this game in C# using the power of Microsoft .NET. You can download the full source code using the link provided at the top. l hope you will enjoy this article and the new enhancements to this game. If you have any suggestions or difficulties, then please leave a note in the comments section below. Good luck !!!

History

  • 25th December, 2005: Initial version

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.

A list of licenses authors might use can be found here.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here