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:
- The number, in each row and column, should be between 1 and 9 and should appear only once.
- 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:
public void GenerateGame(GameLevel level)
{
InitialiseSet();
int minPos,maxPos,noOfSets;
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:
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:
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:
- Border for each mini 3X3 matrix, to distinguish it clearly from other mini-sets.
- Answer spots are now read-only.
- Spinner text control when you edit a particular cell to enter a number between 1 and 9.
- 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:
private void DataGrid1_Paint(object sender,
System.Windows.Forms.PaintEventArgs e)
{
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.
protected override void Edit(System.Windows.Forms.CurrencyManager
source, int rowNum, System.Drawing.Rectangle bounds, bool readOnly,
string instantText, bool cellIsVisible)
{
base.Edit(source, rowNum, bounds, readOnly,
instantText, cellIsVisible);
if(this.TextBox.Text.TrimEnd()!="")
{
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);
_answerPostion=_game.CheckIfAnswerPosition(rowNum,
colPos,dataValue);
}
}
else
{
_answerPostion =false;
}
if (!readOnly && cellIsVisible)
{
this._currentRow = rowNum;
this.cm = source;
if(!_answerPostion)
{
this.vsBar.Parent = this.TextBox.Parent;
Rectangle rect =
this.DataGridTableStyle.DataGrid.GetCurrentCellBounds();
this.vsBar.Location =
new Point(rect.Right-this.SpinnerWidth,rect.Top);
this.vsBar.Size =
new Size(this.SpinnerWidth,this.TextBox.Height);
this.vsBar.Show();
this.vsBar.BringToFront();
this.vsBar.Show();
this.TextBox.ReadOnly=true;
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.