二、锯齿的生成
1、9*9的格子划分成9个随机形状的连通块。
这里采用了bfs + 并查集的算法。 在bfs生成连通块的同时,检测整个图所有的联通分量, 一些特殊的情况进行剪枝。
bfs 函数如下:
bool JigsawHelper:: bfs(int i, int j, int groupId)
{
bool vis[81];
memset(vis, 0, sizeof(vis));
int n = i * 9 + j;
vis[n] = true;
queue<int>q;
q.push(n);
add(i,j,q,vis);
int cnt = 1;
// cout<<"-------------------"<<endl;
while (cnt <= 9 && !q.empty())
{
int tp = q.front();
q.pop();
g[tp] = groupId;
if (!check(n))
{
g[tp] = 0;
continue;
}
cnt ++;
add(tp/9,tp%9,q,vis);
// cout<<"("<<tp/9<<","<<tp%9<<")";
}
// cout<<"-------------------"<<endl;
if (cnt < 10)
{
// cout<<"cnt: "<<cnt<<endl;
return false;
}
return true;
}
check()用于检测当前情况下能否有机会成功划分。
这里有两个重要的判定:
1) 生成过程中不同连通块的个数不得超过九个。
2)生成一个连通块完毕后,剩下的连通块中不能有个数不为9的倍数。
int cnt[81];
memset(cnt,0,sizeof(cnt));
for (int i = 0; i < 81; i++)
{
int num = FindSet(i);
cnt[num]++;
}
int nParent = FindSet(n);
int ans = 0;
int small = 0;
for (int i = 0; i < 81; i++)
{
if (cnt[i] == 0) continue;
if (cnt[i]%9 != 0) //连通个数不是9的倍数
{
ans ++;
}
if (cnt[i]%9 != 0 && i != nParent) //小于9的连通块
{
small += cnt[i]%9;
}
}
if (cnt[nParent] != 9) //生成过程中小连通块的个数和当前块的和大于9
{
if (small + cnt[nParent] > 9)
return false;
}
if (cnt[nParent] == 9 && ans > 0) //生成结束后仍然有个数不为9的倍数的连通块
return false;
return true;
到这里并没有结束,仍然有一些刁钻的数据会造成无法继续往下生成的情况,好在只有20%左右的几率。
项目不是做题,复杂度不大,rebuild即可。
void JigsawHelper:: build()
{
srand(time(NULL));
memset(g,0,sizeof(g));
vector<int>vt;
for (int i = 0; i < 81; i++)
vt.push_back(i);
for (int groupId = 1; groupId <= 9; groupId++)
{
int num = vt[rand()%vt.size()];
if (!bfs(num/9, num%9, groupId))
{
cout<<"fail!!!!!!!!!!! rebuild"<<endl;
build();
return;
}
vt.clear();
for (int i = 0; i < 81; i++)
{
if (g[i] == 0)
vt.push_back(i);
}
}
checkColor();
}
2、 染色
由于四色定理的存在,预先准备四种不同的颜色。
将每个连通块看做一个节点,Welch Powel法染色即可。
3、数独的生成及tricks。
获取锯齿数组和颜色数组后就可以重新用DLX解精确覆盖问题了,构造的时候方式略不同而已。
但是最终生成的时候却偶尔会出现卡死的情况!
对于一种锯齿并不是确定有解的。所以这里修改了下生成数独的方式并添加了一些剪枝。
1)选取种子的时候只取一个随机格子随机数。
2)dlx 中dfs时超过一定depth就return false。多次测试取15000。
3)算不出结果继续rebuild, 相信cpu的实力。(实测ipad mini2以上 生成所有难度均在1s之内,完全可以接受的范围)。
4、UI
我只是在简单的背景上添加了81个不同颜色的editbox,限制一下整数。
当然有时间换成用button选择肯定是更好的
void SudokuUI::editBoxTextChanged(EditBox *editBox, const std::string &text)
{
char buffer[2];
if (text.length() >= 1)
{
char c = text[text.length() - 1];
if (!isdigit(c))
{
editBox->setText("");
return;
}
sprintf(buffer, "%c",c);
editBox->setText(buffer);
}
}
由于锯齿形本身限制较多, 所以比较容易挖更多的洞,生成的效果也明显好一些,感受下难度吧XD~