第二次作业——个人项目实战
Sudoku Crosswords
Github Project Address
Personal Software Process Stages
| Personal Software Process Stages | Estimated time consuming(m) |
|---|---|
| Planning | 30 |
| · Estimate | 30 |
| Development | 380 |
| · Analysis | 60 |
| · Design Spec | 30 |
| · Design Review | 30 |
| · Coding Standard | 20 |
| · Design | 60 |
| · Coding | 60 |
| · Code Review | 80 |
| · Test | 40 |
| Reporting | 110 |
| · Test Report | 20 |
| · Size Measurement | 30 |
| · Postmortem & Process Improvement Plan | 60 |
| Total | 520 |
Problem Thinking
Sudoku is an interesting puzzle which can stretch your brain. I think I have learnt the ropes after playing many times.
Hence, I take it for granted that generating a Sudoku is easy for me. After reading the request of this project, however, I get into a big trouble.
How can I generate so many(maybe thousands, even millions) Sudoku in a limited time and memory!
For this, we should think out of the box.
- Original Idea
Referring to the rule of Sudoku, we just work out holes one by one by backtracking algorithm. This method seems to work well when the number of Sudoku (denoted as SudokuNum for convenience) is rather small. However, as SudokuNum increases, either the time consuming or the memory consuming becomes intolerable.
So, I have turned to baidu & google for help. After reading many pages, I gain some important features which may be useful for making a new method.
- Some Features about Sudoku
- Tip 1: There exists 6,670,903,752,021,072,936,960 kinds of Sudoku.
- Tip 2: If we do some special transformations to a random Sudoku, the result can also be a Sudoku.
-
initial Sudoku:
![]()
-
exchange 2 number:Fig.1
![]()
-
exchange 2 column in the same block:Fig.2
![]()
-
exchange 2 row in the same block:Fig.3
![]()
-
exchange 2 line block in column:Fig.4
![]()
-
exchange 2 line block in row:Fig.5
![]()
-
exchange the diagonal:Fig.6
![]()
-
rotate 90 degree every time:Fig.7
![]()
-
Tip 1 tells me that: Maybe we could use the nine random numbers as the first row of Sudoku, and gain another nine random numbers to place in second row if there is no conflict, and so on...(we assume that we would come across the correct Sudoku for there are 6,670,903,752,021,072,936,960 kinds of Sudoku)
//*******************************//
/* */
// SudokuRandomGenerate //
/* */
//******************************//
gain nine random numbers as row_1;
while row_i < 10
gain nine random numbers;
while the nine random numbers do not satisfy the rule of Sudoku
gain nine random numbers;
end while
place the nine random numbers as row_++i;
end while
It's obvious that the time we try to gain a correct Sudoku is uncertain, and this method maybe very slow. Even worse, if the nine random numbers is never satisfy the rule, this program would NOT STOP! Hence, I do some tricks as implementing this algorithm.
Tip 2 tells me that: In this way, we could gain a Sudoku easily and quickly by make a transformation above on an already existed Sudoku. As shown in the Fig.1 - Fig.7, However, the Sudoku we gain is too similarly. In the meantime, if we do the rotation transformation on a Sudoku four times, or do the diagonal transformation on a Sudoku twice, we will gain the initial Sudoku! This dose not satisfy the project request, which we should generate n different Sudoku.
//*******************************//
/* */
// SudokuMatrixTransform //
/* */
//******************************//
gain an initial Sudoku;
while
make some transformations on the initial Sudoku;
gain another Sudoku;
end while
- Final Method I Used
gain an initial Sudoku by sudokuRandomGenerate;
while i < n
gain i th Suduku by SudokuMatrixTransform (set the Sudoku just gained as initial Sudoku);
i++;
if i % 50 == 0
regain the initial Sudoku by sudokuRandomRenerate;
i++;
end if
end while;
Implementation Detail
In order to realize the Sudoku generate task, I build a SudokuGeneratorDLL.dll file.
In order to realize the basic function(sudoku.exe -c n), I write the sudoku.cpp.
-
SudokuGeneratorDLL.dll
As memtioned, I have to implementation two algorithoms:- SudokuRandomGenerate: Generate a Sudoku by Random numbers
- buildRandomArray: gain the nine random numbers
- conflictCheck: check whether there is a confilct emerge.
- generateRandomSudoku: star this method
- and so on.
- SudokuMatrixTransform: Transform an initial Sudoku to gain a new Sudoku
- contains the seven transformations function.
- generateProcess: star this method.
- and so on.
Inorder to gain a better design in code, I also design other two class:
- SudokuOptions: Contain many options used for control the process
- generateWay: SIMPLE(SudokuMatrixtransform method will be used) or COMPLEX(SudokuRandomGenerate will be used)
- randomTransformTimes: the Transform Times play on the initial Sudoku to gain a new Sudoku(set for SudokuMatrixTransform)
- MAX_RANDOM_CALL_TIMES: if we have try MAX_RANDOM_CALL_TIMES we still can't gain the correct Sudoku, then give it up(set for SudokuRandomGenerate)
- isPrint: whether write the Sudoku into "./sudoku.txt"
- sudokuNum: the number of Sudoku we need.
- and so on.
- GenerateSudoku: Use SudokuRandomGenerate class and SudokuMatrixTransform to generate Sudoku
- sdkOp: SudokuOptions
- generateProcess: star this method.
- sudokuPrint: write sudoku into "./sudoku.txt"
- and so on.
Because we have built this .dll file, it's convenient for me to reuse those code in the externe project.
![]()
- SudokuRandomGenerate: Generate a Sudoku by Random numbers
-
sudoku.exe -c n
The source file of this .exe is sudoku.cpp- sudoku.cpp
- a simple way to judge the command correct or not
- n should be a positive integer
- if any command goes wrong, help document would be shown, and exit this program.
- a simple way to get the parameter
- we can gain the sudokuNum form command
- Appropriate settings for SudokuOptions
- we should initial the SudokuOptions in order to control this process well.
- a simple way to judge the command correct or not
- sudoku.cpp

Code Description
- I have overload operator = for SudokuOptions for convience.
DLLAPI SudokuOptions& SudokuOptions::operator=(const SudokuOptions& Opt) {
this->generateWay = Opt.generateWay;
this->isPrint = Opt.isPrint;
this->MAX_RANDOM_CALL_TIMES = Opt.MAX_RANDOM_CALL_TIMES;
this->sudokuNum = Opt.sudokuNum;
this->randomTransformTimes = Opt.randomTransformTimes;
return *this;
}
- Here, we use SudokuOptions.generateWay to control the process.
SudokuRandomGenerator sudokuRG;
sudokuRG.setMAX_RANDOM_CALL_TIME(sdkOp.MAX_RANDOM_CALL_TIMES);
//generate the first random sudoku
if (sdkOp.sudokuNum > 0) {
sudokuRG.generateRandomSudoku();
sudokuRG.getSudoku(sdk);
sdkOp.reduceSudokuNum();
if (sdkOp.isPrint) sudokuPrint();
}
/*************************************************/
/* */
/*use SudokuMatrixTransform to generate sudoku */
/*this mathod would be twice faster than next one*/
/*especially when the sudokuNum is large! */
/* */
/*************************************************/
if (sdkOp.generateWay == SIMPLE) {
SudokuMatrixTransform sudokuMT;
sudokuMT.setMatrix(sdk);
while (sdkOp.sudokuNum) {
//do few times random transform to makesure different sudoku
REP(i, sdkOp.randomTransformTimes) {
sudokuMT.generateProcess();
}
sudokuMT.getMatrix(sdk);
sdkOp.reduceSudokuNum();
if (sdkOp.isPrint) sudokuPrint();
//after many times generate, we reset the original sudoku
//here, we set this to 30
if (sdkOp.sudokuNum > 0 && sdkOp.sudokuNum % 30 == 0) {
sudokuRG.generateRandomSudoku();
sudokuRG.getSudoku(sdk);
sdkOp.reduceSudokuNum();
if (sdkOp.isPrint) sudokuPrint();
sudokuMT.setMatrix(sdk);
}
}
}
else {//use SudokuRandomGenerate method
while (sdkOp.sudokuNum) {
sudokuRG.generateRandomSudoku();
sudokuRG.getSudoku(sdk);
sdkOp.reduceSudokuNum();
if (sdkOp.isPrint) sudokuPrint();
}
}
- The seven different transform
DLLAPI void SudokuMatrixTransform::exch2num() {
int exnum1 = rand() % 9 + 1;
int exnum2 = rand() % 9 + 1;
REP(i, 9)REP(j, 9) {
if (mx[i][j] == exnum1)
mx[i][j] = exnum2;
else if (mx[i][j] == exnum2)
mx[i][j] = exnum1;
}
}
DLLAPI void SudokuMatrixTransform::exch2column() {
int excolumn = rand() % 3;
int excolumn1 = rand() % 3;
int excolumn2 = (excolumn1 + 1) % 3;
excolumn1 = excolumn * 3 + excolumn1;
excolumn2 = excolumn * 3 + excolumn2;
REP(i, 9) {
std::swap(mx[i][excolumn1], mx[i][excolumn2]);
}
}
DLLAPI void SudokuMatrixTransform::exch2row() {
int exrow = rand() % 3;
int exrow1 = rand() % 3;
int exrow2 = (exrow1 + 1) % 3;
exrow1 = exrow * 3 + exrow1;
exrow2 = exrow * 3 + exrow2;
REP(i, 9) {
std::swap(mx[exrow1][i], mx[exrow2][i]);
}
}
DLLAPI void SudokuMatrixTransform::exch2blockc() {
int exblock1 = rand() % 3;
int exblock2 = (exblock1 + 1) % 3;
for (int column1 = exblock1 * 3, column2 = exblock2 * 3; column1 < (exblock1 + 1) * 3; column1++, column2++) {
REP(i, 9) {
std::swap(mx[i][column1], mx[i][column2]);
}
}
}
DLLAPI void SudokuMatrixTransform::exch2blockr() {
int exblock1 = rand() % 3;
int exblock2 = (exblock1 + 1) % 3;
for (int row1 = exblock1 * 3, row2 = exblock2 * 3, i = 0; i < 3; row1++, row2++, i++) {
REP(i, 9) {
std::swap(mx[row1][i], mx[row2][i]);
}
}
}
DLLAPI void SudokuMatrixTransform::exch2diag() {
//memcpy(tmp, mx, sizeof(int) * 81);
REP(i, 9)REP(j, 9) tmp[i][j] = mx[i][j];
REP(i, 9)REP(j, 9) {
mx[i][j] = tmp[j][i];
}
}
DLLAPI void SudokuMatrixTransform::rotate() {
REP(i, 9)REP(j, 9) tmp[i][j] = mx[i][j];
REP(i, 9)REP(j, 9) {
mx[9 - 1 - j][9 - 1 - i] = tmp[i][9 - 1 - j];
}
}
- Conflict Check functions
DLLAPI bool SudokuRandomGenerator::isCandidate(int row, int col) {
//we need check for each random number
REP(i, 9) {
sdk[row][col] = sdkr[i];
if (conflictCheck(row, col)) {
return true;
}
}
return false;
}
//According to the rule of Sudoku, the conflict may happen in:
//1. row
//2. column
//3. block
DLLAPI bool SudokuRandomGenerator::conflictCheck(int row, int col) {
return checkCol(row, col) && checkRow(row, col) && checkBlk(row, col);
}
DLLAPI bool SudokuRandomGenerator::checkRow(int row, int col) {
int currentValue = sdk[row][col];
REP(colNum, col)
if (currentValue == sdk[row][colNum])
return false;
return true;
}
DLLAPI bool SudokuRandomGenerator::checkCol(int row, int col) {
int currentValue = sdk[row][col];
REP(rowNum, row)
if (currentValue == sdk[rowNum][col])
return false;
return true;
}
DLLAPI bool SudokuRandomGenerator::checkBlk(int row, int col) {
int baseRow = row / 3 * 3;
int baseCol = col / 3 * 3;
REP(rowNum, 8) {
if (sdk[baseRow + rowNum / 3][baseCol + rowNum % 3] == 0)continue;
for (int colNum = rowNum + 1; colNum < 9; colNum++) {
if (sdk[baseRow + rowNum / 3][baseCol + rowNum % 3] == sdk[baseRow
+ colNum / 3][baseCol + colNum % 3])
return false;
}
}
return true;
}
- Build nine random numbers
DLLAPI void SudokuRandomGenerator::buildRandomArray() {
currentCallTime++;
REP(i, 9) sdkr[i] = i + 1;
//we swap random two num 30 times to generate the random array
REP(i, 30) {
int index1 = rand() % 9;
int index2 = rand() % 9;
std::swap(sdkr[index1], sdkr[index2]);
}
}
- As described above, we try MAX_RANDOM_CALL_TIMES times for every try. If failed, I reset the Sudoku to 0, and do it again.
DLLAPI void SudokuRandomGenerator::generateRandomSudoku() {
REP(row, 9) {
if (row == 0) {//for the first row, it must be no conflict. so we do not check for it.
currentCallTime = 0;
buildRandomArray();
setSudokuRow(row);
}
else {
buildRandomArray();
REP(col, 9) if (currentCallTime < MAX_RANDOM_CALL_TIMES) {
if (!isCandidate(row, col)) {
resetRow(row);
row -= 1;//if conflict has been detected, we backtrack one row.
col = 8;
buildRandomArray();
}
}
else {//Failed, reset Sudoku to 0, and do it again.
row = -1;
col = 8;
resetSudoku();
currentCallTime = 0;
}
}
}
}
- Command judge
if (argc != 3 || string(argv[1]) != "-c" || !isanum(string(argv[2]))) {
cout << "Usage Error!" << endl;
cout << "Usage: sudoku.exe -c n" << endl;
cout << " >> n must be a nonecgtive interge." << endl;
exit(0);
}
else {
sudokuNum = str2num(string(argv[2]));
}
- SudokuOptions default settings
SudokuOptions sdkOpt;
sdkOpt.setSudokuNum(sudokuNum);
sdkOpt.setIsPrint(true);
/* HERE IS IMPORTANT */
/****************************/
/*randomTransfromTimes: the bigger, the better result will get! At the meantime, the much time will cost.*/
/*GenerateWay: */
/* SIMPLE: genarete by MatrixTransform, this method is almost twice faster than the next one.*/
/* COMPLEX: genarete by Random, this method would get a more ideal result than pre one. */
sdkOpt.setrandomTransfromTimes(3);
sdkOpt.setGenerateWay(SIMPLE);
Test & Run
To simplify, I didn't waste too much time designing a good command judging code. So, my sudoku.exe only support "sudoku.exe -c n" command.
- Command Show

- Result Show

Result Sample
7 8 5 1 4 9 2 3 6
3 6 2 7 8 5 9 4 1
4 1 9 2 3 6 8 7 5
8 5 4 6 9 3 1 2 7
2 7 6 5 1 4 3 9 8
9 3 1 8 2 7 5 6 4
5 9 3 4 7 8 6 1 2
6 2 7 9 5 1 4 8 3
1 4 8 3 6 2 7 5 9
7 8 4 1 5 9 3 2 6
3 6 2 7 8 4 5 9 1
5 1 9 2 3 6 7 8 4
4 9 3 5 7 8 1 6 2
6 2 7 9 4 1 8 5 3
1 5 8 3 6 2 4 7 9
8 4 5 6 9 3 2 1 7
2 7 6 4 1 5 9 3 8
9 3 1 8 2 7 6 4 5
4 9 3 1 6 2 5 7 8
6 2 7 8 5 3 9 4 1
1 5 8 4 7 9 3 6 2
8 4 5 2 1 7 6 9 3
2 7 6 9 3 8 4 1 5
9 3 1 6 4 5 8 2 7
7 8 4 3 2 6 1 5 9
3 6 2 5 9 1 7 8 4
5 1 9 7 8 4 2 3 6
- In order to compare the two different methods, we make a test with n = 10, 100, 1000, 10000, 100000,1000000
![]()
| n | SIMPLE(s) | COMPLEX(s) |
|---|---|---|
| 10 | 0.381414 | 0.434023 |
| 100 | 0.452047 | 0.484669 |
| 1000 | 0.770437 | 1.413 |
| 10000 | 3.949 | 10.605 |
| 100000 | 42.706 | 126.965 |
| 1000000 | 436.666 | 1294.228 |
I have ignored n = 1000000 on the chart below Intentionally for better comparing view

- BUG
- Small probability event would happen that we generate the same Sudoku.
- But I think we can ignore this BUG for that even we generate 10^6(a milion) Sudoku, the probability is still smaller than 10^(-25).
- And I have added many tricks to make the result more random.(i.g. use the rand() many times).
Improvement
- There are three stage about this basic project:
- stage 1: How can I generate a Sudoku.
- stage 2: Is it a correct Sudoku?
- stage 3: How can I generate many (a million or even more) different correct Sudoku.
- Stage 1: How can I generate a Sudoku.
As described in the beginning, we can adapt backtracking algorithm to generate a Sudoku. Also, we can easily gain a similar Sudoku by transforming an existing Sudoku.Either way can we gain a correct Sudoku. So this stage is easy to realize. - Stage 2: Is it a correct Sudoku?
From the way I gain the Sudoku, I can make sure any Sudoku I gain must be a correct Sudoku. What's more, we found that the transformations play on the Sudoku would also gain a correct Sudoku, from the Fig.1-Fig.7. - Stage 3: How can I generate many (a million or even more) different correct Sudoku.
Stage 3 is the most difficult, and algorithm optimization is always difficult. As noted, the original backtracking algorithm is not suitable for a big SudokuNum, and it maybe take a few hours to generate a million Sudoku. To deal with this intolerable time and memory consuming, I adapt the Matrix Transformation to gain many similar but not the same Sudoku. In order to make sure we have generated different Sudoku, I change the initial Sudoku after fifty times.
In the implementation, we have set SIMPLE and COMPLEX controler. To make the program run fast, we can also set the SudokuOptions. In the last content, we have show the great difference between SIMPLE and COMPLEX method. - Runtime analyze
- Time Consuming
![]()
![]()
In the SIMPLE way, we found that the main running time is consumed by GenerateSudoku. However, it doesn't take much time in SudokuRandomGenerate when COMPLEX is used. - Memory Used
![]()
![]()
940 KB, it is OK!
- Time Consuming
- Some Important Improvement for friendly command line
So far, the sudoku.exe only supports "-c n" command, it's not a friendly program, isn't it? So we need to do something more to make it more lovely, and this part of code will show in the Extended Content. We are only talking about the basic project request, here.
Personal Software Process Stages
| Personal Software Process Stages | Actual time consuming(m) |
|-----------------------------------------|-----------------------------------------|------------------|
| Planning | 20 |
| · Estimate | 20 |
| Development | 445 |
| · Analysis | 120 |
| · Design Spec | 30 |
| · Design Review | 30 |
| · Coding Standard | 15 |
| · Design | 40 |
| · Coding | 180 |
| · Code Review | 100 |
| · Test | 30 |
| Reporting | 140 |
| · Test Report | 60 |
| · Size Measurement | 20 |
| · Postmortem & Process Improvement Plan | 60 |
| Total | 605 |
Extended Content
GUI Design
While implementing the basic object request, I choosed the C++ as my coding language. However, it's not easy for me to use C++ design a GUI easily.
Finally, I used C# instead, and my SQL Server pratice work is also designed using C#. But, I don't want to rewrite the C++ code. This really burn my fingers.
Fortunately, I have built the .dll file, maybe I can make full use of this. So, I begin to thinking how the .dll is used by C#.
After looking for many material, I begin my adapting.
- Make Some Modification In SudokuGeneratorDLL.dll
It's always not easy for C# to use C++ class (GenareteSudoku) directly, and for this GUI request, we just generate one correct Sudoku is enough.
So, I added a function in the original code to generate a sudoku using SudokuRandomGenerate method.
DLLAPI void SudokuEmerge(int* sdk) {
SudokuOptions sudokuOpt;
sudokuOpt.setGenerateWay(COMPLEX);
sudokuOpt.setIsPrint(false);
sudokuOpt.setSudokuNum(1);
sudokuOpt.setrandomTransfromTimes(5);
GenerateSudoku GSudoku;
GSudoku.setOptions(sudokuOpt);
GSudoku.generateProcess();
sudoku sdkt;
GSudoku.getSudoku(sdkt);
REP(i, 9)REP(j, 9) sdk[i * 9 + j] = sdkt[i][j];
}
- Calling interface
For convenience, I used the unsafe code in the C#.
using System.Runtime.InteropServices;
...
[DllImport("./SudokuGeneratorDLL.dll",
EntryPoint = "?SudokuEmerge@@YAXPAH@Z")]
//static extern void SudokuEmerge([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1, SizeConst = 81)] int[] sudoku);
static extern void SudokuEmerge(int[] sudoku);
The function name in the built .dll file has been changed, we should keep in mind.(SudokuEmerge becomes ?SudokuEmerge@@YAXPAH@Z).
Core Code
Here, you can find the most important code in GUI. We first generate a correct Sudoku, then dig holes to form the sudoku for this GUI.
- Dig Holes
As request, I have dig a hole number ranging 30 to 60.
Random random = new Random();
int randholes = random.Next(30, 60);
label6.Text = "Hole:" + randholes.ToString();
DigHoles(randholes);
...
public void DigHoles(int num)
{
int row = 0;
int col = 0;
int sdkIndex = 0;
Random rand = new Random();
int baseRow = 0;
int baseCol = 0;
for (; baseRow < 3; baseRow++)
{
for (; baseCol < 3; baseCol++)
{
for (int i = 0; i < 2; i++)
{
int randIndex = rand.Next(0, 8);
row = baseRow * 27 + (randIndex / 3) * 9;
col = baseCol * 3 + randIndex % 3;
sdkIndex = row + col;
if (sudoku[sdkIndex] == -1)//is already set!
{
i -= 1;
}
else
{
sudoku[sdkIndex] = -1;
num--;
}
}
}
}
//we have comsuming the 28 holes, now we continue.
while (num > 0)
{
sdkIndex = rand.Next(0, 80);
if (sudoku[sdkIndex] == -1)
{
num++;
}
else
{
sudoku[sdkIndex] = -1;
}
num--;
}
//MessageBox.Show("Good Luck!", "New game", MessageBoxButtons.OK);
}
- Check The Answer
As we known, there may be more than one solution existed. Hence, we can't just simply compare the answer with original sudoku.
But we can first compare the answer with the original sudoku, if not match, then we do other check again.
public bool AnswerCheck()
{
//check with the Sudoku we have emerged firts
//at the meantime, we have check the Sudoku is finished or not!
int i;
for (i = 0; i < 81; i++)
{
if (sudoku[i] == -1)//-1 stand for null
{
MessageBox.Show("row:" + (i / 9 + 1).ToString() + ", col:" + (i % 9 + 1).ToString() + " is empty!", "Empty hole exist.", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return false;
}
if (sudoku[i] != answer[i])
break;
}
if (i == 81) { return true; }
//now, we have to check the hole, becouse there maybe many answers
int j;
for (i = 0; i < 81; i++)
{
//check in row
int row = i / 9 * 9;
for (j = 0; j < 9; j++, row++)
{
if (row == i) continue;
else
{
if (sudoku[row] == sudoku[i])
return false;
else
continue;
}
}
//check in column
int column = i % 9;
for (j = 0; j < 9; j++, column += 9)
{
if (column == i) continue;
else
{
if (sudoku[column] == sudoku[i])
return false;
else
continue;
}
}
//check in block
row = i / 9;
column = i % 9;
int baseRow = row / 3 * 3;
int baseCol = column / 3 * 3;
//here, we divided it into three base line, each contains three numbers
int basePoint1 = baseRow * 9 + baseCol;
int basePoint2 = basePoint1 + 9;
int basePoint3 = basePoint2 + 9;
for (j = 0; j < 3; j++, basePoint1++, basePoint2++, basePoint3++)
{
if (basePoint1 != i)
{
if (sudoku[basePoint1] == sudoku[i]) return false;
}
if (basePoint2 != i)
{
if (sudoku[basePoint2] == sudoku[i]) return false;
}
if (basePoint3 != i)
{
if (sudoku[basePoint3] == sudoku[i]) return false;
}
}
}
//after all check, we confirm this soduku is correct.
return true;
}
GUI Show
In a word, It's ugly on looking for deadline coming. <~>
-
Initial
![]()
-
New
![]()
-
Show Answer

- Simple Guide
-
Click button (New) to start a new game.
![]()
-
Chose the hole in the sudoku, chose the corresponding number in the right bottom block.
![]()
-
Click button (Submit) to check the answer.
![]()
-
Click button (cancel) will get the correct answer.
-
All the code and program have been available in Githut.
Game Design
It seems easy to adapt a GUI into GAME, but the unique request for this GAME puzzled me.
How can I make sure the sudoku have only one answer!
- Thinking About the ONLY ONE Answer
Maybe I can just use the previous code for generate the GUI, but check the answer after the hole having set. If there is only one possible solution, this is OK.
But, this take too much time! With my patience dying out, I can't get a sudoku GAME! Of course NO ONE would like to play this clumsy GAME program.
I have to think out of the box again.
What the answer would be like if there is only one solution exist? Yeah, it must be the same as the original sudoku which we haven't dig holes.Hence, I can dig the holes one by one, after digging a hole, check the possible solution, if more than one, recovery this hole, and choose another one.
This is work well! At least, I can gain a GAME sudoku now, though, still takes much time. The most time consuming progress is the Only Answer Check, which contains DFS method. When the number of holes increase, this process becomes intolerable.
To deal with this, it's necessary to make the DFS become more efficiency. I notice that we don't have to work out all the possible answer, then to tell us the sudoku exist more than one solution. In the other way, we just check two possible answer at most for that if there is more than one solution, we can gain a answer sudoku different from the original sudoku. Hence, If we found a difference sudoku which is alse correct, we can stop the DFS.
With this important improvement, the program can generate a GAME as quickly as you can feel.
However, too many holes would make the generating progress difficult. As we known, if we want to gain only one solution sudoku, there should exit at least 17 numbers.
When I try to generate a sudoku GAME with 17 numbers, the time consuming becomes intolerable again!
How to deal with this? Sorry, I don't know!
But when the number of holes is between 30 and 50, this program work well. More details will be show in the following. - Core Code
//we set max hole 40 in order to speed up this program.
int randholes = random.Next(30, 50);
Little change in generating code
public void DigHoles(int num)
{
int hls = num;
int row = 0;
int col = 0;
int sdkIndex = 0;
Random rand = new Random();
int baseRow = 0;
int baseCol = 0;
for (; baseRow < 3; baseRow++)
{
for (; baseCol < 3; baseCol++)
{
for (int i = 0; i < 2; i++)
{
int randIndex = rand.Next(0, 8);
row = baseRow * 27 + (randIndex / 3) * 9;
col = baseCol * 3 + randIndex % 3;
sdkIndex = row + col;
if (sudoku[sdkIndex] == -1)//is already set!
{
i -= 1;
}
else
{
sudoku[sdkIndex] = -1;
num--;
//make sure there is only one solution
if (OnlyAnswerCheck(hls-num) == true)
{
continue;
}
else
{
sudoku[sdkIndex] = answer[sdkIndex];
num++;
i -= 1;
}
}
}
}
}
//we have comsuming the 28 holes, now we continue.
while (num > 0)
{
sdkIndex = rand.Next(0, 80);
if (sudoku[sdkIndex] == -1)
{
continue;
}
else
{
sudoku[sdkIndex] = -1;
//make sure there is only one solution
if (OnlyAnswerCheck(hls - num + 1) == true)
{
num--;
}
else
{
sudoku[sdkIndex] = answer[sdkIndex];
}
}
}
//MessageBox.Show("Good Luck!", "New game", MessageBoxButtons.OK);
}
The only Answer Check Progress
/*******************************************************/
/* */
/*Caculate the possible answer quickly */
/* */
/*******************************************************/
public bool OnlyAnswerCheck(int hls)
{
//get the holes
int[] holes = new int[hls];
for (int i = 0, j = 0; i < 81; i++)
{
if (sudoku[i] == -1)
{
holes[j++] = i;
}
}
//numTag[0]: the usefull number of number
int[,] numTag = new int[hls, 10];
// get useful number
getUseful(hls, numTag, holes);
//Core Code
if(Solve(0, holes, hls, numTag) != -1)
{
return true;
}
else
{
return false;
}
}
/*******************************************************/
/* */
/*Solve */
/* */
/*******************************************************/
//return
//0: no answer;
//1: find a answer;
//-1: more than one solution
public int Solve(int holesIndex, int[] holes, int hls, int[,] numTag)
{
//return test
if(holesIndex == hls)
{
int i;
for( i = 0; i < 81; i++)
{
if (answer[i] != sudoku[i])
break;
}
if(i == 81)
{
return 1;
}
else
{
return -1;
}
}
//no number
if(numTag[holesIndex, 0] == 0)
{
return 0;
}
for(int i = 1; i <= 9; i++)
{
if(numTag[holesIndex, i] == 1)
{
//note down the original answer
int sudoku_ = sudoku[holes[holesIndex]];
sudoku[holes[holesIndex]] = i;
getUseful(hls, numTag, holes);
int reVal = Solve(holesIndex + 1, holes, hls, numTag);
sudoku[holes[holesIndex]] = sudoku_;
if (reVal == -1)
{
return -1;
}
else//reVal == 0, 1
{
getUseful(hls, numTag, holes);
continue;
}
}
}
//
return 0;
}
/*******************************************************/
/* */
/*get the useful number */
/* */
/*******************************************************/
public void getUseful(int hls, int[,] numTag, int[] holes)
{
for (int i = 0; i < hls; i++) for (int j = 0; j < 10; j++)
{
numTag[i, j] = 0;
}
for (int i = 0; i < hls; i++)
{
int holesIndex = holes[i];
for (int num = 1; num <= 9; num++)
{
if (checkIndexNum(holesIndex, num))
{
numTag[i, num] = 1;
numTag[i, 0]++;
}
}
}
}
/*******************************************************/
/* */
/*design for Check confict in a hole */
/* */
/*******************************************************/
public bool checkIndexNum(int holesIndex, int num)
{
return checkInRow(holesIndex, num) && checkInCol(holesIndex, num) && checkInBlk(holesIndex, num);
}
public bool checkInRow(int index, int num)
{
int rowstart = index / 9 * 9;
for (int i = 0; i < 9; i++, rowstart++)
{
if (rowstart == index)
{
continue;
}
if (sudoku[rowstart] == num)
{
return false;
}
}
return true;
}
public bool checkInCol(int index, int num)
{
int colstart = index % 9;
for (int i = 0; i < 9; i++, colstart += 9)
{
if (colstart == index)
{
continue;
}
if (sudoku[colstart] == num)
{
return false;
}
}
return true;
}
public bool checkInBlk(int index, int num)
{
int baseRow = index / 9 / 3;
int baseCol = index % 9 / 3;
for (int i = 0; i < 9; i++)
{
int row = baseRow * 27 + (i / 3) * 9;
int col = baseCol * 3 + i % 3;
int sdkIndex = row + col;
if (sdkIndex == index)//is already set!
{
continue;
}
if (sudoku[sdkIndex] == num)
{
return false;
}
}
return true;
}
- GAME Show



- Time Consuming
** To simplify, we only do each test once. So, the following data shows the proximate efficiency.
30 Holes
![]()
40 Holes

45 Holes

48 Holes

50 Holes

We do not test more holes, because of random method, the time consuming is not accurate.
Conclusion
For basic require, if we use "printf" instead of "cout", the time consuming would reduce much, but it's not the main purpose of this project. So I didn't change the code in the final version.
How to generate the only one answer Sudoku is the most important part in the GAME project.In my implementation, it works well when the number holes is between 30 and 50. As for the most difficult sudoku which only contains 17 hints, but 64 holes, it doesn't work any more.
P.S.
As noted in the previous passage, the GAME only does well with the holes between 30 and 50. I still hope that this program can generate a 64 holes sudoku, that is 17 hints exist!
But, whatever how long I have waited, the program just couldn't generate a sudoku with 64 holes.
This make me wonder that whether my program is strong enough to generate a sudoku with 64 holes or not! What's worse, maybe there are same hidden BUGs in my program.Hence, after submitting this blog, I have to reread my code once more.
As expected, I found some unsuitable codes which are responsible for the digging holes task.
I have used the random method all the time. In the begin of this blog, I have illustrated why can we use a random method to generate a sudoku. The most important reason is that There exist so many sudoku(6*10^21), so the possibility of come across a correct sudoku is quietly large!. It's obvious that I have ignored this standpoint.
There are still many sudoku with one solution, however. Maybe I can add more constraints to generate holes, the random method would work better again.
Form this idea, I added more tag in the process. It is gratifying that this modification have evidently reduced the time consuming!
public void DigHoles(int num)
{
int hls = num;
int baseRow = 0;
int baseCol = 0;
Random rand = new Random();
while (num > 0)
{
int row = 0;
int col = 0;
int sdkIndex = 0;
for (; baseRow < 3; baseRow++)
{
for (; baseCol < 3; baseCol++)
{
bool[] useful = new bool[9];
for (int j = 0; j < 9; j++)
{
useful[j] = true;
}
int usenum = 9;
for (int i = 0; i < 2; i++)
{
if (usenum == 0)
{
baseCol--;
break;
}
//chose a hole quickly
int randIndex = rand.Next(1, usenum);
for (int j = 0, id = 0; j < 8; j++)
{
if (useful[j])
{
id++;
if (id == randIndex)
{
randIndex = j;
break;
}
}
}
row = baseRow * 27 + (randIndex / 3) * 9;
col = baseCol * 3 + randIndex % 3;
sdkIndex = row + col;
if (sudoku[sdkIndex] == -1)//is already set!
{
i -= 1;
}
else
{
sudoku[sdkIndex] = -1;
num--;
//make sure there is only one solution
if (OnlyAnswerCheck(hls - num) == true)
{
continue;
}
else
{
sudoku[sdkIndex] = answer[sdkIndex];
useful[randIndex] = false;
usenum--;
num++;
i -= 1;
}
}
}
if (baseCol < 0)
{
baseRow--;
break;
}
}
}
//we have comsuming the 28 holes, now we continue.
bool[,] usefulHoles = new bool[81, 81];
int[] usf = new int[81];
int[] usfini = new int[81];
for (int i = 0; i < 81; i++)
{
for (int j = 0; j < 81; j++)
{
if (sudoku[j] == -1)
{
usefulHoles[i, j] = false;
}
else
{
usefulHoles[i, j] = true;
usf[i]++;
}
}
usfini[i] = usf[i];
}
int ininum = num;
while (num > 0)
{
int randIndex = rand.Next(1, usf[num]);
//get the random index
for (int i = 0, j = 0; i < 81; i++)
{
if (usefulHoles[num, i] == true)
{
j++;
if (j == randIndex)
{
sdkIndex = i;
break;
}
}
}
if (sudoku[sdkIndex] == -1)
{
continue;
}
else
{
sudoku[sdkIndex] = -1;
//make sure there is only one solution
if (OnlyAnswerCheck(hls - num + 1) == true)
{
num--;
}
else
{
sudoku[sdkIndex] = answer[sdkIndex];
usefulHoles[num, sdkIndex] = false;
usf[num]--;
}
}
if (usf[num] == 0) {
num++;
usf[num] = usfini[num];
}
if (num > ininum) break;
}
if (num > ininum)
{
baseRow--;
}
}
//MessageBox.Show("Good Luck!", "New game", MessageBoxButtons.OK);
}
Now, we can gain a 55 holes sudoku in few minutes.

It's a pity that my program still don't have result in the 64 holes.
This part .exe file have been uploaded on GitHut/Final folder.





















浙公网安备 33010602011771号