FillCell
这个方法是填充数独表的递归方法,看长长滴代码:
代码
/// <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
如下:
代码
/// <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
代码
/// <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();
}
}
只要按回车键就可以生成一个不一样的新数独!
对于有兴趣的读者,如果是要继续开发成数独游戏,可以随机的遮罩一定数量的格子,难度越高遮罩的格子越多,这样能保证的是数独游戏一定有解,但是解不一定唯一。如果要保证解唯一,那么就需要再用一个类似这个的解数独的方法,让计算机先做我们遮罩后的数独,看是不是有且仅有一个解,如果不是,那么重新选择遮罩区域,直到计算机发现解唯一为止,然后输出到前端,给玩家来玩!
下一篇是整个程序的源代码。