前几天在玩数独游戏的时候,产生了一个大胆的想法:
数独app上的题目都是现成的,干脆自己写一个可以随机生成数独的程序算了
一、需求提出:
1.随机生成数独题目,要求该题目有解;
2.当填数违反数独操作(填到题目原本的数字去了,填数违规等)时,禁止操作,弹出提示;
3.当81格都填满时,判断对错;
二、需求分析
总:
需求2,3相对简单,尤其是需求3,都禁止违规操作了,填满81格不就赢了吗?也许是当初没有给自己再次组织语言的机会,提出了这么傻缺的需求,核心问题就是需求1。
需求1
首先得到一个9*9,符合数独规则的矩阵,然后随机挖空不就行了吗?所以需求1的问题转化为如何得到一个随机9*9数独矩阵。
第一个想出来的方法是用搜索算法,在每一个格子随机生成1-9的数字,然后判断符不符合规则,再下一个。
这个算法的缺点是效率感人,81个格子,每个格子的数字都要判断,每个格子的数字还都是随机生成的,效率能不感人吗?
优点是还没付诸行动就被否定了,给程序编写省下了不少时间
第二个想法就相对可靠了,先在随机在若干个格子上生成随机数(这些随机数也要符合数独规则),这样就变成了一道题目,然后让电脑用搜索算法求解,只要能解出一种,就停止解题,并且选取这种解答作为随机9*9数独矩阵以及本题的答案。解不出也没有关系重新生成随机数再重新解过就得了。
这个方法可行度很高,优点很多,其中最重要的优点是我在刷蓝桥杯时曾经写过一个解数独的程序
经过调试,我把随机数个数设置在了15,既不会因为太少而使得随机数在矩阵上分布不均匀,又保证不会因为数字太多,答案太少而影响生成效率。
得到这个矩阵后,经过简单的随机挖空,一道数独题目就随机生成了。
ps:我把这些随机生成的题目塞回解数独器求解,发现解法并不唯一,若要解法唯一则效率低下。在调试后把挖空个数控制在了46~51个。使得挖空不会太少,影响游戏性;挖空不会太多,让题目有成千上万种解
经过n(n>=30)次测试,在挖空46个的情况下,求解法个数m
1<m<10 和 9<m<100 的次数几乎各占一半,两者加起来约莫90%,m=1与m>99的情况则瓜分了剩下的10%
虽然达不到只有一个解的严格标准,但是,嗯,解比较少,也还凑合吧
需求2
我采取三位数输入的方法
百位是横标,十位是竖标,个位是填数,除个位外不得含9,个位不得为0,判断方法直接用解数独器现成的就行了
输入999表示弃权
程序运行结果:
开始
操作
弃权:
四.代码:
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
#include<ctime>
#define big 7 //大方格边界颜色
#define mid 2 //中方格边界颜色
#define small 8 //小方格边界颜色
#define col 2 //题目原数据颜色
int flag=1,total=0;
void gotoxy(int x, int y){
++x,++y;
COORD pos;//表示一个字符在控制台屏幕上的坐标
pos.X = x;
pos.Y = y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);//是API中定位光标位置的函数。
}//光标控制模块
void color(int b){
HANDLE hConsole = GetStdHandle((STD_OUTPUT_HANDLE)) ;
SetConsoleTextAttribute(hConsole, b) ;//颜色控制
}//颜色控制模块
void Boundary(){
gotoxy(3,1);
color(big);
printf("+---+---+---+---+---+---+---+---+---+");
color(small);
for(int i=1;i<10;++i){
color(small);
gotoxy(3,i*2+1);
if(i==3 || i==6){
color(mid);
}
printf("+---+---+---+---+---+---+---+---+---+");
for(int j=0;j<10;++j){
gotoxy(3+j*4,i*2);
if(j==3 || j==6){
color(mid);
}
if(j==0 || j==9){
color(big);
}
printf("| ");
color(7);
gotoxy(3+j*4,i*2+1);
if(i%3!=0 || j%3!=0){
color(small);
}
if(j==0 || j==9){
color(big);
}
if((i%3==0 || j%3==0) && j!=0 && j!=9){
color(mid);
}
printf("+");
color(small);
}
}
color(big);
gotoxy(3,19);
printf("+---+---+---+---+---+---+---+---+---+");
color(7);
gotoxy(60,1);
printf("数独游戏");
gotoxy(45,3);
printf("哈蒙森软件工业公司1875年出品");
gotoxy(45,5);
printf("解法:0种");
gotoxy(45,7);
printf("操作中输入'678',表示对6行8列标记为8;输入'999'表示放弃游戏");
for(int i=0;i<9;++i){
gotoxy(4*i+5,0);
printf("%d",i);
gotoxy(4*i+5,20);
printf("%d",i);
gotoxy(1,2*i+2);
printf("%d",i);
gotoxy(41,2*i+2);
printf("%d",i);
}
}
bool Flag(int m,int n,int b,int board[9][9]){
int a[9];
for(int i=0;i<9;++i){
if(board[i][m]==b){
return false;
}
if(board[n][i]==b){
return false;
}
}
for(int i=0;i<9;++i){
a[i]=0;
}
a[0]=b;
gotoxy(50,15);
int q=(n/3)*3,p=(m/3)*3,r=1;
for(int i=p;i<p+3;++i){
for(int j=q;j<q+3;++j){
if(board[j][i]>0 && board[j][i]<10){
a[r]=board[j][i];++r;
}
}
}
for(int i=0;i<9;++i){
for(int j=i+1;j<9;++j){
if(a[i]==a[j] && i!=j && a[i]!=0){
return false;
}
}
}
return true;
}
void Sudoku(int board[9][9]){
time_t seed;
srand(time(&seed));
int number=15,p=0,q=0,i=0;
while(i<number){
int m=rand()%9,n=rand()%9,b=rand()%9+1;
if(Flag(n,m,b,board)==true){
board[m][n]=b;
p=m,q=n;
++i;
}
}
board[p][q]=0;
}
int bfs(int x,int y,int num,int board[9][9]){
int m,n,time=0;
int dir[9][2]={{1,1},{1,0},{1,-1},{0,1},{0,-1},{-1,1},{-1,0},{-1,-1},{0,0}};
for(int i=0;i<9;++i){
m=x+dir[i][0],n=y+dir[i][1];
if(num==board[n][m]){
++time;
}
if(time>1){
return 0;
}
}
return 1;
}
int Judge(int x,int y,int n,int board[9][9]){
int t=0;
for(int i=0;i<9;++i){
if(board[y][i]==n){
++t;
if(t>1){
return 1;
}
}
}
t=0;
for(int i=0;i<9;++i){
if(board[i][x]==n){
++t;
if(t>1){
return 1;
}
}
}
int nu,m;
if(bfs(1+(x/3)*3,1+(y/3)*3,n,board)==0){
return 1;
}
}
void Show(int input,int board[9][9],int sudoku[9][9][2]){//30<=input<=55
time_t seed;
srand(time(&seed));
int i=0;
color(col);
for(int i=0;i<9;++i){
for(int j=0;j<9;++j){
gotoxy(5+j*4,i*2+2);
printf(" ");
board[i][j]=0;
}
}
while(i<input){
int m=rand()%9,n=rand()%9;
if(sudoku[m][n][1]==0){
gotoxy(5+n*4,m*2+2);
printf("%d",sudoku[m][n][0]);
sudoku[m][n][1]=1;
board[m][n]=sudoku[m][n][0];
++i;
}
}
}
void Sel(int x,int y,int kind,int board[9][9],int sudoku[9][9][2],int &total){
if(flag==0 && kind==0){
return;
}
if(y<9){
if(x<9){
if(board[y][x]<1 || board[y][x]>9){
for(int i=1;i<=9;++i){
board[y][x]=i;
int k=Judge(x,y,i,board);
if(k==0){
Sel(x+1,y,kind,board,sudoku,total);
}
board[y][x]=0;
}
}else{
Sel(x+1,y,kind,board,sudoku,total);
}
}else{
Sel(0,y+1,kind,board,sudoku,total);
}
}else{
++total;
flag=0;
if(kind==0){
for(int i=0;i<9;++i){
for(int j=0;j<9;++j){
sudoku[i][j][0]=board[i][j];
}
}
}
}
}
int main(){
int board[9][9],sudoku[9][9][2];
int leve,op,input=35;
for(int i=0;i<9;++i){
for(int j=0;j<9;++j){
board[i][j];
sudoku[i][j][1]=0;
}
}
Boundary();
while(input>34 && input<56){
total=0,flag=1;
while(total==0){
Sudoku(board);
Sel(0,0,0,board,sudoku,total);
gotoxy(50,22);
}//生成符合数独规则的矩阵
Show(input,board,sudoku);//随机挖空
Sel(0,0,1,board,sudoku,total);
gotoxy(45,5);
color(7);
printf("解法:%d种 ",total-1);
gotoxy(54,9);
int key=1;
while(key==1){
int h=0;
gotoxy(45,9);
printf("操作 输入3位数:");
scanf("%d",&op);
gotoxy(5+(op/100%10)*4,(op/10%10)*2+2);
if(sudoku[op/10%10][op/100%10][1]==0 && op%10!=0 && op<999 && op/10%10!=9 && op/100%10!=9){
board[op/10%10][op/100%10]=0;
if(Flag(op/100%10,op/10%10,op%10,board)==true){
board[op/10%10][op/100%10]=op%10;
gotoxy(5+(op/100%10)*4,(op/10%10)*2+2);
printf("%d",op%10);
for(int i=0;i<9;++i){
for(int j=0;j<9;++j){
if(board[i][j]!=0){
++h;
}
}
}
if(h==81){
key=0;
gotoxy(45,9);
printf("你赢了!");
Sleep(3000);
}
}else{
color(12);
gotoxy(45,11);
printf("输入错误!");
Sleep(500);
}
}else{
if(op<999){
color(7);
gotoxy(45,11);
printf("操作非法!");
Sleep(500);
}
}
if(op==999){
key=0;
gotoxy(45,9);
printf("你输了!现在显示其中一种解法!");
for(int i=0;i<9;++i){
for(int j=0;j<9;++j){
if(sudoku[i][j][1]==0){
Sleep(100);
gotoxy(5+j*4,i*2+2);
printf("%d",sudoku[i][j][0]);
}
board[i][j]=0;
sudoku[i][j][0]=0;
sudoku[i][j][1]=0;
}
}
}
color(7);
gotoxy(45,9);
printf("操作: ");
gotoxy(45,11);
printf(" ");
}
gotoxy(45,11);
printf("输入35~55之间的数字开始新游戏,或按其他键退出:");
scanf("%d",&input);
gotoxy(45,11);
printf(" ");
}
gotoxy(0,20);
return 0;
}