摘要:花了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!\n"); 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("\n");} 263 // printf("%2d: ", i%9); 264 // for (int j = 0; j <9; j++){ 265 // printf("%2d ",GetNum(i,j)); 266 // } 267 // printf("\n"); 268 // } 269 // } 270 //顯示所有數獨表元素 271 void Display(void){ 272 for(int i=0;i<9;i++){ 273 if(i==3 || i==6){ 274 printf("------+------+------\n"); 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("\n"); 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("\n"); 290 // } 291 292 /******************************** 293 *Main 294 *********************************/ 295 int main(void){ 296 InitSudoku(); 297 FillTable(); 298 Display(); 299 free(sdk); 300 return 0; 301 }