摘要:花了1周多时间学习了C语言,开始练手写解数独游戏的程序。
C语言学习 数独游戏
作者:乌龙哈里
时间:2015-11-22
平台:Window7 64bit,TCC 0.9.26(x86-64 Win64)
参考:
章节:
正文:
原来也用C#和Go语言写过,主要思路是暴力撞大运破解。思路什么的在程序了都注释了,不多说了。可能是没用什么先进的算法,感觉C解题速度和C#差不多(除了C#第一次运行之外),基本上出来一个数独表都不用1秒。
附完整程序:
1 /*************************************************** 2 *数独表 3 *作者:乌龙哈里 4 *编写时间:2015-11-21 5 *修改时间:2015-11-22 6 *思路: 7 1、每个表元素结构属性有: 8 值Value,回溯标志IsBack,可选值数量Remain,可选值数组Selection[9]; 9 2、根据规则,把数独表看成27个,每个含有9个元素的行列块组; 10 为了循环方便,分成:0-8:行组,9-17:列组,18-26:块组; 11 3、找出最少要填空格的行列块组,开始填写。填完这个再找下个最少的; 12 4、填写时先从行列块组中挑出剩下可填写的数字,从中随机找个值; 13 5、没有可选的时候,开始从回溯表中回到上一步, 14 回溯时如果可选值数量大于1时,则抛弃先前所填值,用另外的 15 值来尝试。 16 ****************************************************/ 17 18 #include <stdio.h> 19 #include <stdlib.h> 20 21 /* 22 *把表看成27组,每组9个元素, 23 * 0-8:行组,9-17:列组,18-26:块组 24 行内序号:从左到右 012345678 25 列内序号:从上到下 012345678 26 块内序号:012 27 345 28 678 29 *GetRcb():根据index计算出行列块 30 *参数:index: 序号 0-81,flag:0-2,0-行,1-列,2-块 31 * 32 *GetNum():根据行列块组rcb和块内序号index计算出在数独表中的序号 33 */ 34 int GetRcb(int index,int flag){ 35 int result=-1; 36 switch(flag){ 37 case 0: 38 result=index / 9; 39 break; 40 case 1: 41 result=index % 9+9; 42 break; 43 case 2: 44 result=index/9/3*3+index%9/3+18; 45 break; 46 } 47 return result; 48 } 49 50 int GetNum(int rcb,int index){ 51 int result=-1; 52 int flag=rcb/9; 53 switch(flag){ 54 case 0: 55 result=rcb*9+index; 56 break; 57 case 1: 58 result=rcb-9+index*9; 59 break; 60 case 2: 61 result=(rcb-18)/3*27+(rcb-18)%3*3+index/3*9+index%3; 62 break; 63 } 64 return result; 65 } 66 67 //定义:数独表、表内元素结构、回溯表,回溯只记录30步 68 typedef signed char byte; 69 typedef char bool; 70 71 #define true 1 72 #define false 0 73 74 byte SudokuTable[81]={0}; 75 76 #define STEP 30 77 int RecallTable[STEP]={-1}; 78 79 typedef struct element{ 80 byte Value; 81 bool IsBack; 82 byte Remain; 83 byte Selection[9]; 84 }Sudoku; 85 86 Sudoku *sdk; 87 88 /* 89 初始化数独元素: 90 */ 91 void InitSudoku(void){ 92 sdk=(Sudoku*)malloc(81*sizeof(Sudoku)); 93 for(int i=0;i<81;i++){ 94 sdk[i].Value=SudokuTable[i]; 95 sdk[i].IsBack=false; 96 sdk[i].Remain=9; 97 } 98 } 99 100 //查找最少空的行列块,用意:从这个开始填空 101 int GetFirstRcb(void){ 102 int result=0; 103 int lessNum=9; 104 int n; 105 for(int i=0;i<27;i++){ 106 n=9; 107 for (int j=0;j<9;j++){ 108 if(sdk[GetNum(i,j)].Value>0){ 109 n--; 110 } 111 } 112 if(n>0 && n<lessNum){ 113 result=i; 114 lessNum=n; 115 } 116 } 117 return result; 118 } 119 120 //整理可选值数组,把0值丢后面,可选值放前面,返回可选数 121 byte Arrange(int index){ 122 byte result=0; 123 for(int i=0;i<9;i++){ 124 if(sdk[index].Selection[i]>0){ 125 sdk[index].Selection[result]=sdk[index].Selection[i]; 126 if(i!=result){sdk[index].Selection[i]=0;} 127 result++; 128 } 129 } 130 return result; 131 } 132 /* 133 *设置可填写数字数组: 134 遍历元素所属的行列块中元素,在Selection数组中把用过的值设成0; 135 */ 136 void SetSelection(int index){ 137 for(int i=0;i<9;i++){sdk[index].Selection[i]=i+1;} 138 int rcb; 139 int n; 140 for(int i=0;i<3;i++){ 141 rcb=GetRcb(index,i); 142 for(int j=0;j<9;j++){ 143 n=GetNum(rcb,j); 144 if(sdk[n].Value>0){ 145 sdk[index].Selection[sdk[n].Value-1]=0; 146 } 147 } 148 } 149 sdk[index].Remain=Arrange(index); 150 } 151 152 //随机选出可填写值 153 byte GetValue(int index){ 154 byte result=0; 155 srand((unsigned int)time(0)); 156 int n=rand()%sdk[index].Remain; 157 result=sdk[index].Selection[n]; 158 sdk[index].Selection[n]=0; 159 sdk[index].Remain=Arrange(index); 160 return result; 161 } 162 163 /* 164 回溯,如果回溯表内没有记录,返回-1 165 如果可选值数量大于0的,则把回溯标记设成true 166 */ 167 int Recall(void){ 168 int index; 169 for(int i=0;i<STEP;i++){ 170 if(RecallTable[i]>-1){ 171 index=RecallTable[i]; 172 sdk[index].Value=0; 173 RecallTable[i]=-1; 174 if(sdk[index].Remain==0){ 175 sdk[index].IsBack=false; 176 } 177 else{ 178 sdk[index].IsBack=true; 179 return index; 180 } 181 } 182 } 183 return -1; 184 } 185 /* 186 填写回溯表 187 从后往前填写,满了就移动 188 */ 189 void WriteRecallTable(int index){ 190 if(RecallTable[0]>-1){ 191 for(int i=STEP-1;i>0;i--){ 192 RecallTable[i]=RecallTable[i-1]; 193 } 194 RecallTable[0]=index; 195 } 196 else{ 197 for(int i=0;i<STEP;i++){ 198 if(RecallTable[i]>-1){ 199 RecallTable[i-1]=index; 200 break; 201 } 202 } 203 } 204 } 205 /* 206 根据行列块分组来填写元素。 207 如果是回溯回来的,则抛弃掉现有的值,即不SetSelection(), 208 因为GetValue()选出值后会把所填写的值从可选值数组中除去, 209 而SetSelection()是根据所有行列块组的元素值选出剩下的可填值, 210 包括了上次行不通的值。 211 */ 212 bool WriteRcb(int rcb){ 213 int index; 214 for(int i=0;i<9;i++){ 215 index=GetNum(rcb,i); 216 if(sdk[index].Value==0){ 217 if(sdk[index].IsBack==false){ 218 SetSelection(index); 219 } 220 if (sdk[index].Remain==0){ 221 return false; 222 } 223 sdk[index].Value=GetValue(index); 224 sdk[index].IsBack=true; 225 WriteRecallTable(index); 226 } 227 } 228 return true; 229 } 230 //判断填完没有 231 bool Completed(void){ 232 for(int i=80;i>-1;i--){ 233 if(sdk[i].Value==0) { 234 return false; 235 } 236 } 237 return true; 238 } 239 //填写全表 240 void FillTable(void){ 241 int loop=1000; 242 int firstRcb=GetFirstRcb(); 243 244 while(loop>0 && Completed()==false){ 245 if(WriteRcb(firstRcb)==false){ 246 if(Recall()==-1){ 247 printf("Unlucky,cannot solve! "); 248 break; 249 } 250 } 251 firstRcb=GetFirstRcb(); 252 loop--; 253 } 254 } 255 256 /************************************* 257 *各种输出显示模块 258 **************************************/ 259 // //按行列块分组显示元素序号 260 // void DisplayRcb(void){ 261 // for (int i = 0; i <27;i++){ 262 // if(i%9==0){printf(" ");} 263 // printf("%2d: ", i%9); 264 // for (int j = 0; j <9; j++){ 265 // printf("%2d ",GetNum(i,j)); 266 // } 267 // printf(" "); 268 // } 269 // } 270 //显示所有数独表元素 271 void Display(void){ 272 for(int i=0;i<9;i++){ 273 if(i==3 || i==6){ 274 printf("------+------+------ "); 275 } 276 for(int j=0;j<9;j++){ 277 if(j==3 || j==6){ printf("|");} 278 printf("%2d",sdk[i*9+j].Value); 279 } 280 printf(" "); 281 } 282 } 283 // //显示元素可选数字 284 // void DisplayRemain(int index){ 285 // printf("element %d remain %d selecttion: ",index,sdk[index].Remain); 286 // for(int i=0;i<9;i++){ 287 // printf("%d ",sdk[index].Selection[i]); 288 // } 289 // printf(" "); 290 // } 291 292 /******************************** 293 *Main 294 *********************************/ 295 int main(void){ 296 InitSudoku(); 297 FillTable(); 298 Display(); 299 free(sdk); 300 return 0; 301 }