FillCell
这个方法是填充数独表的递归方法,看长长滴代码:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
///
<summary>
/// 填充单元格
/// </summary>
/// <param name="table"> 表 </param>
/// <param name="index"> 索引 </param>
/// <returns> 返回结果 </returns>
private bool FillCell(Cell[,] table, int index)
{
RelativeCellMethod removeCandidateMethod = new RelativeCellMethod(RemoveCellCandidate);
RelativeCellMethod recoverCandidateMethod = new RelativeCellMethod(RecoverCellCandidate);
if (index >= 81 )
{ // 如果索引超出范围,则表示数独已成功生成,直接返回
return true ;
}
if (table[index / 9 , index % 9 ].Value != 0 )
{ // 如果索引的单元格已赋值,则直接跳到下一个索引赋值
return FillCell(table, index + 1 );
}
bool flag = true ;
List < int > nextCandidates = new List < int > ();
// 预先保存好改单元格的候选数序列,如果所有候选数都不成功,则把候选数全部还原之后再返回
nextCandidates.AddRange(table[index / 9 , index % 9 ].Candidate);
while (table[index / 9 , index % 9 ].Candidate.Count > 0 && flag)
{ // 如果单元格候选数个数大于0,且标记为真,则循环试探候选数
SetValue(table[index / 9 , index % 9 ]); // 为单元格赋值
flag &= DealRelativeCell(removeCandidateMethod, table, index); // 移除相关单元格的对应这个值的候选数
if ( ! flag)
{ // 如果移除候选数失败,则恢复候选数,并继续下个循环
DealRelativeCell(recoverCandidateMethod, table, index);
}
else
{ // 如果移除候选数成功,则继续试探填充下一个单元格
flag &= FillCell(table, index + 1 );
if ( ! flag)
{ // 如果填充下一个单元格失败,则恢复候选数,并继续下个循环
DealRelativeCell(recoverCandidateMethod, table, index);
}
else
{ // 如果填充下一个单元格成功,则直接返回(运行到这里肯定表示整个数独已成功生成!)
return true ;
}
}
flag = ! flag; // 把标志取反,继续下个循环
}
if (table[index / 9 , index % 9 ].Candidate.Count == 0 )
{ // 如果所有候选数都是过了且全部失败,恢复此单元格的候选数,并返回false
table[index / 9 , index % 9 ].Candidate.AddRange(nextCandidates);
return false ;
}
return flag;
}
/// 填充单元格
/// </summary>
/// <param name="table"> 表 </param>
/// <param name="index"> 索引 </param>
/// <returns> 返回结果 </returns>
private bool FillCell(Cell[,] table, int index)
{
RelativeCellMethod removeCandidateMethod = new RelativeCellMethod(RemoveCellCandidate);
RelativeCellMethod recoverCandidateMethod = new RelativeCellMethod(RecoverCellCandidate);
if (index >= 81 )
{ // 如果索引超出范围,则表示数独已成功生成,直接返回
return true ;
}
if (table[index / 9 , index % 9 ].Value != 0 )
{ // 如果索引的单元格已赋值,则直接跳到下一个索引赋值
return FillCell(table, index + 1 );
}
bool flag = true ;
List < int > nextCandidates = new List < int > ();
// 预先保存好改单元格的候选数序列,如果所有候选数都不成功,则把候选数全部还原之后再返回
nextCandidates.AddRange(table[index / 9 , index % 9 ].Candidate);
while (table[index / 9 , index % 9 ].Candidate.Count > 0 && flag)
{ // 如果单元格候选数个数大于0,且标记为真,则循环试探候选数
SetValue(table[index / 9 , index % 9 ]); // 为单元格赋值
flag &= DealRelativeCell(removeCandidateMethod, table, index); // 移除相关单元格的对应这个值的候选数
if ( ! flag)
{ // 如果移除候选数失败,则恢复候选数,并继续下个循环
DealRelativeCell(recoverCandidateMethod, table, index);
}
else
{ // 如果移除候选数成功,则继续试探填充下一个单元格
flag &= FillCell(table, index + 1 );
if ( ! flag)
{ // 如果填充下一个单元格失败,则恢复候选数,并继续下个循环
DealRelativeCell(recoverCandidateMethod, table, index);
}
else
{ // 如果填充下一个单元格成功,则直接返回(运行到这里肯定表示整个数独已成功生成!)
return true ;
}
}
flag = ! flag; // 把标志取反,继续下个循环
}
if (table[index / 9 , index % 9 ].Candidate.Count == 0 )
{ // 如果所有候选数都是过了且全部失败,恢复此单元格的候选数,并返回false
table[index / 9 , index % 9 ].Candidate.AddRange(nextCandidates);
return false ;
}
return flag;
}
前两行定义了两个代理方法,在while方法中,一个一个的试单元格的候选数填入到单元格中,如果填入失败,则恢复候选数,继续尝试下一个候选数,如果所有候选数都不能正确生成数独,那么跳出while循环,函数返回false,返回到上一个单元格,将上一个单元格的值重置为0,试另外的候选数,以此类推。直到索引index大于等于81,则表示所有单元格都已成功赋值,那么全部返回true。
接下来只需要写一个显示数独的方法:
Show
如下:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
///
<summary>
/// 显示数独
/// </summary>
/// <param name="table"> 表 </param>
private void Show(Cell[,] table)
{
for ( int i = 0 ; i < 9 ; i ++ )
{
for ( int j = 0 ; j < 9 ; j ++ )
{
Console.Write( " {0,2} " , table[i, j].Value);
}
Console.WriteLine();
}
Console.WriteLine( " ---------------------------------------------- " );
}
/// 显示数独
/// </summary>
/// <param name="table"> 表 </param>
private void Show(Cell[,] table)
{
for ( int i = 0 ; i < 9 ; i ++ )
{
for ( int j = 0 ; j < 9 ; j ++ )
{
Console.Write( " {0,2} " , table[i, j].Value);
}
Console.WriteLine();
}
Console.WriteLine( " ---------------------------------------------- " );
}
还有一个初始化数独表,然后开始填充数独的方法:
StartFillTable
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
///
<summary>
/// 开始生成数独
/// </summary>
public void StartFillTable()
{
Cell[,] table = new Cell[ 9 , 9 ];
while ( true )
{
for ( int i = 0 ; i < 9 ; i ++ )
for ( int j = 0 ; j < 9 ; j ++ ) // 初始化数独表
table[i, j] = new Cell();
bool flag = FillCell(table, 0 ); // 填充数独表
if (flag) // 如果生成数独成功,则显示这个数独
Show(table);
Console.ReadKey();
}
}
/// 开始生成数独
/// </summary>
public void StartFillTable()
{
Cell[,] table = new Cell[ 9 , 9 ];
while ( true )
{
for ( int i = 0 ; i < 9 ; i ++ )
for ( int j = 0 ; j < 9 ; j ++ ) // 初始化数独表
table[i, j] = new Cell();
bool flag = FillCell(table, 0 ); // 填充数独表
if (flag) // 如果生成数独成功,则显示这个数独
Show(table);
Console.ReadKey();
}
}
我是用控制台应用程序编写的,如果是用Silverlight或者其他的呈现会更好看!,下面是Main方法和结果显示:
class
Program
{
static void Main( string [] args)
{
CellMethod method = new CellMethod();
method.StartFillTable();
}
}
{
static void Main( string [] args)
{
CellMethod method = new CellMethod();
method.StartFillTable();
}
}
只要按回车键就可以生成一个不一样的新数独!
对于有兴趣的读者,如果是要继续开发成数独游戏,可以随机的遮罩一定数量的格子,难度越高遮罩的格子越多,这样能保证的是数独游戏一定有解,但是解不一定唯一。如果要保证解唯一,那么就需要再用一个类似这个的解数独的方法,让计算机先做我们遮罩后的数独,看是不是有且仅有一个解,如果不是,那么重新选择遮罩区域,直到计算机发现解唯一为止,然后输出到前端,给玩家来玩!
下一篇是整个程序的源代码。