C語言學習 數獨游戲


摘要:花了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 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM