C语言实现数独游戏
一、功能:
(1)輸出排名
從文件中讀出數據,保存在單鏈表中,并利用選擇排序對數據進行排序后輸出。
(2)開始游戲
進入該游戲系統后,系統先隨機創建一個唯一解的數獨,并解出該數獨答案。
(3)選擇難度
根據玩家選擇的難度,挖空系統已經解出的數獨,比如選擇簡單(給出40個數),則把系統已經解出的答案隨機挖空41格,挖空處用數字0代替。
(4)給出數獨并開始作答
玩家得到題目后進行解答(解答需將數獨完整寫一遍,以空格分割每列,以回車分割每行),最后系統根據答案判斷玩家解答是否正確,正確則輸出所用時間,如果該玩家是新紀錄,則還要記錄到文件,如果玩家解答錯誤,則失敗并給出正確答案。
功能結構圖:
二、涉及數據結構:
十字雙向循環鏈表
三、涉及算法:
DLX算法
如何將數獨轉化為精準覆蓋問題,并用DLX算法去解
數據結構體設計:
①玩家游戲記錄信息
②存儲數獨結點信息
四、實驗環境:
使用語言:C
使用軟件:Microsoft Visual C++ 6.0
五、功能截圖:
六、具體代碼:
#include<stdio.h> #include<stdlib.h> #include<memory.h> #include<math.h> #include<time.h> #include <windows.h> #include<string.h> #define MAX 999struct Node{ Node *up,*down,*left,*right,*colPtr,*rowPtr;//指向row,col對應的行對象和列對象 int row_num;//記錄行數,行專屬(從1開始) int col_elemCount;//記錄該列元素個數,列專屬 };struct player{//玩家信息結點 int m ; int s ; char name[20]; int level ; player* next; };int row_size=593;//行數 int col_size = 324;//列數 int result[81];//存放結果行的棧 int index = 0;//棧指針 int sudoku[81] = {0};//存放數獨 int time_start = 0; int time_end = 0;//起始時間void init(Node* head){head->left = head;head->right = head;head->up = head;head->down = head;for(int k = 0;k < row_size;k++){//創建行對象(頭插)Node* newNode = (Node*)malloc(sizeof(Node));newNode->up = head;newNode->down = head->down;newNode->left = newNode;newNode->right = newNode;newNode->down->up = newNode;head->down = newNode;newNode->row_num = row_size-k;newNode->col_elemCount = 0;//借用,作為標志} } void init_col(Node* head){/************初始化行列對象*******************/for(int j = 0;j < col_size;j++){//創建列對象(頭插)Node* newNode = (Node*)malloc(sizeof(Node));newNode->right = head->right;newNode->left = head;newNode->down = newNode;newNode->up = newNode;newNode->right->left = newNode;head->right = newNode;newNode->col_elemCount = 0;//列元素個數初始為0} }void link(Node* head,int** matrix){ /***********插入結點*************/ Node *current_row, *current_col, *current;//當前行對象,當前列對象,當前節點 current_row = head; for(int row = 0;row<row_size;row++){current_row = current_row->down;current_col = head;for(int col = 0;col<col_size;col++){current_col = current_col->right;if(matrix[row][col] == 0)continue;/*****插入結點,結點的行和列都用尾插法來與行列對象鏈接*****/Node* newNode = (Node*)malloc(sizeof(Node));newNode->colPtr = current_col;//設置節點對應的行和列newNode->rowPtr = current_row;/**********列尾插************/newNode->down = current_col;newNode->up = current_col->up;newNode->up->down = newNode;current_col->up = newNode;//鏈接當前節點到列雙向鏈尾端if(current_row->col_elemCount == 0){//行的這個為0,說明該行還沒有元素,行雙向鏈表不把行對象包含進來(便于后面覆蓋)current_row->right = newNode;newNode->left = newNode;newNode->right = newNode;current_row->col_elemCount++;//此為標志,為了不把行對象包含進來}current = current_row->right;newNode->left = current->left;/**********行尾插************/newNode->right = current;newNode->left->right = newNode;current->left = newNode;//鏈接當前節點到行雙向鏈尾端current_col->col_elemCount++;//該列元素加1}} }int** create_matrix()//將數獨轉換為01矩陣 {int** matrix = (int**)malloc(row_size*sizeof(int*));//申請二維數組空間for(int m=0;m<row_size;m++)matrix[m] = (int*)malloc(col_size*sizeof(int));for(int r=0;r<row_size;r++)for(int c=0;c<col_size;c++)matrix[r][c] = 0;//初始化int i = 0;for (int x = 0; x < 81; x++){int val = sudoku[x];if (val != 0){//有值則插入一行matrix[i][x] = 1;matrix[i][81 + x/9*9 + val -1] = 1;matrix[i][162 + x%9*9 + val -1] = 1;matrix[i][243 + (x/9/3*3+x%9/3)*9 +val -1] = 1;i++;continue;}for (int j = 0; j < 9; j++){//沒值則插入9行(1~9)matrix[i][x] = 1;matrix[i][81 + x/9*9 + j] = 1;matrix[i][162 + x%9*9 +j] = 1;matrix[i][243 + (x/9/3*3+x%9/3)*9 +j] = 1;i++;}}return matrix; }void cover(Node* cRoot){//覆蓋cRoot->left->right = cRoot->right;cRoot->right->left = cRoot->left;//刪除該列對象,及該列結點的每行結點Node *i, *j;i = cRoot->down;while (i != cRoot){j = i->right;while (j != i){j->down->up = j->up;j->up->down = j->down;j->colPtr->col_elemCount--;j = j->right;}i = i->down;} }void recover(Node* cRoot){//回溯 Node *i, *j;i = cRoot->up;while (i != cRoot){j = i->left;while (j != i){j->colPtr->col_elemCount++;j->down->up = j;j->up->down = j;j = j->left;}i = i->up;}cRoot->right->left = cRoot;cRoot->left->right = cRoot;}bool search(Node* head){if(head->right == head){return true;}Node *cRoot, *c;int minSize = MAX;//最少列元素個數for(c = head->right; c != head; c = c->right)//先選擇列元素最少的列對象(提高效率){if (c->col_elemCount < minSize){minSize = c->col_elemCount;cRoot = c;if (minSize == 1)//1是最小break;if (minSize == 0)//有一列為空,失敗return false;}}cover(cRoot);Node *current_row,*current;for (current_row = cRoot->down; current_row != cRoot; current_row = current_row->down){result[index]=current_row->rowPtr->row_num;//將該行加入result中(行數)index++;for (current = current_row->right; current != current_row; current = current->right){cover(current->colPtr);}if (search(head))//遞歸return true;for (current = current_row->left; current != current_row; current = current->left)recover(current->colPtr);index--;//發現該行不符合要求,出棧}recover(cRoot);return false; }int* to_sudoku(int** matrix){//01矩陣轉化為數獨int* done = (int*)malloc(81*sizeof(int));int temp[162]={0};for(int i = 0;i<81;i++){for(int j = 0;j<162;j++)temp[j] = matrix[result[i]-1][j];int pos = 0,val = 0;for(int k = 0;k<81;k++){if(temp[k] == 1)break;pos++;}for(int m = 81;m<162;m++){if(temp[m] == 1)break;val++;}done[pos]=val%9+1;}return done; }void print(int* answer){//打印數獨 printf("┏━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┓\n");for(int i = 0;i<81;i++){if(answer[i]==0)printf("┃ ");elseprintf("┃ %d",answer[i]);if(i==80){printf("┃ ");printf("\n");printf("┗━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┛\n");}else if((i+1)%9==0){printf("┃ ");printf("\n");printf("┣━━╋━━╋━━╋━━╋━━╋━━╋━━╋━━╋━━┫\n");}} }bool isRight(int* flag,int x,int y,int val){//判斷填入的數是否合法for(int i = 0;i<324;i++)if(flag[(x-1)*9+(y-1)] == 0 &&flag[81+(x-1)*9 + val -1] == 0 &&flag[162+(y-1)*9 + val -1] == 0 &&flag[243+((x-1)/3*3 + (y-1)/3+1)*9 +val -1] == 0 )return true; return false; }void create_sudoku(){for(int j = 0;j<81;j++)sudoku[j] = 0;//初始化int flag[324]={0};int x,y,val;//行號、列號、值int i=0;while(i<17){//一個數獨中有17個數及以上時有唯一解x = rand()%9+1;y = rand()%9+1;val = rand()%9+1;if(isRight(flag,x,y,val)){i++;sudoku[(x-1)*9+(y-1)]=val;flag[(x-1)*9+(y-1)] = 1;flag[81+(x-1)*9 + val -1] = 1;flag[162+(y-1)*9 + val -1] = 1;flag[243+((x-1)/3*3 + (y-1)/3+1)*9 +val -1] = 1;}} }void sudoku_level(int* answer,int count){//難度int x,y;//行號、列號int num=0;srand(time(NULL));for(int i = 0;i<81;i++)sudoku[i] = answer[i];while(num<(81-count)){//挖空x = rand()%9+1;y = rand()%9+1;if(sudoku[(x-1)*9+(y-1)] != 0){sudoku[(x-1)*9+(y-1)] = 0;num++;}} }bool receiver(int* player_res){//接收玩家答案for(int i=0;i<81;i++){scanf("%d",&player_res[i]);if(!(player_res[i]>=1&&player_res[i]<=9)){//0 表示玩家放棄fflush(stdin);return false;}}return true; }int get_time(){//獲得當前時間秒time_t t;t=time(NULL);return t; }void ready(){print(sudoku);printf("你有5秒鐘觀察時間\n");for(int i = 0;i<5;i++){printf("● ");Sleep(1000);}printf("\n");printf("觀察結束,計時開始,請開始作答。(輸入除1~9外,視為放棄作答)\n");printf("==========================================================================\n"); }bool judge(int* player_res,int* answer){//判斷玩家答案for(int i = 0;i<81;i++)if(player_res[i] != answer[i])return false; return true; }void record(player info){//記錄FILE* fp;int M = MAX,S = MAX,LEVEL = MAX;char NAME[20];char remove[100] = {" "};//用于記錄長度固定化,方便更新記錄//通過這種方法,可以直接在一個文件中更新數據,不必要全篇讀—改—寫,直接修改一行int c = 0;if((fp = fopen("record.txt","r+"))==NULL){//文件在cpp同目錄下printf("文件不存在,保存失敗!");//雖然會自動生成文件,but以防萬一return;}setbuf(fp,NULL);//設置緩沖區rewind(fp);c = ftell(fp);//記錄當前行的開頭指針位置while(fscanf(fp,"%s %d:%d %d",NAME,&M,&S,&LEVEL) != EOF){if(!strcmp(NAME,info.name) && LEVEL == info.level){//strcmp比較相同返回0if(info.m<M || (info.m==M&&info.s<S)){//如果是新紀錄,則更新fseek(fp,c,SEEK_SET);fputs(remove,fp);//覆蓋舊記錄fseek(fp,c,SEEK_SET);//回到該記錄的開頭位置fprintf(fp,"%s %d:%d %d",info.name,info.m,info.s,info.level);//寫入文件fflush(fp);//清除緩沖區return;}return;//不是新紀錄就不插入}fscanf(fp,"\n");//讀取換行c = ftell(fp);}fputs(remove,fp);//先覆蓋固定長度的區域fseek(fp,c,SEEK_SET);//回到覆蓋的區域首部fprintf(fp,"%s %d:%d %d",info.name,info.m,info.s,info.level);//在覆蓋的區域內插入記錄fseek(fp,0,SEEK_END);//指向尾部fprintf(fp,"\n");//插入換行符fclose(fp); }player* get_record(int level){//返回玩家記錄的單向鏈表頭結點FILE* fp;int M = MAX,S = MAX,LEVEL = MAX;char NAME[20];player* head = (player*)malloc(sizeof(player));head->next = NULL;if((fp = fopen("record.txt","r"))==NULL){printf("文件不存在!");system("pause");exit(1);}setbuf(fp,NULL);//設置緩沖區rewind(fp);while(fscanf(fp,"%s %d:%d %d",NAME,&M,&S,&LEVEL) != EOF){if(LEVEL == level){player* p = (player*)malloc(sizeof(player));//采用鏈表strcpy(p->name,NAME);p->m = M;p->s = S;p->next = head ->next;head->next = p;} } fclose(fp); return head; }void order(player* head){//單鏈表排序 player* p; player* q; int temp1; int temp2; char temp3[20]; for(p = head->next;p != NULL;p = p->next)for(q = p->next;q != NULL;q = q->next)if(p->m>q->m||(p->m==q->m&&p->s>q->s)){//對換兩個結點的內容temp1 = p->m;temp2 = p->s;strcpy(temp3,p->name);p->m = q->m;p->s = q->s;strcpy(p->name,q->name);q->m = temp1;q->s = temp2;strcpy(q->name,temp3);}}void show(player* easy,player* normal,player* hard){//輸出排行int no=1;player* p1 = easy->next;player* p2 = normal->next;player* p3 = hard->next; printf("==========================================================================\n");printf("\t\t 簡單\t\t\t 一般\t\t\t 困難\n");while(p1 != NULL || p2 != NULL || p3 != NULL){printf("NO.%d",no++);if(p1 != NULL){printf("\t\t%s\t%d:%d\t",p1->name,p1->m,p1->s);p1 = p1->next;}if(p2 != NULL){printf("\t%s\t%d:%d\t",p2->name,p2->m,p2->s);p2 = p2->next;}if(p3 != NULL){printf("\t%s\t%d:%d\t",p3->name,p3->m,p3->s);p3 = p3->next;}printf("\n");} }void re_init(Node* head){//重新初始化行和列對象 Node* p; for(p = head->down;p!=head;p=p->down)p->col_elemCount = 0;if(head->right == head){//如果有解,則列對象會在search中被刪除,需要重新鏈接列對象init_col(head);return;}for(p = head->right;p!=head;p=p->right){//無解時,則恢復列對象p->col_elemCount = 0;p->down = p;p->up = p;} }void main(){ int player_res[81]={0}; int choice; int** matrix;//存放數獨的01矩陣 int* answer;//存放答案 player* easy;//容易難度排行 player* normal;//簡單難度排行 player* hard;//困難難度排行 player info ;//玩家信息Node* head =(Node*)malloc(sizeof(Node)); init(head);init_col(head);//初始化行列對象,建立十字雙向循環鏈表 srand(time(NULL)); bool flag = false;//有無解 while(true){while(!flag){flag=true;create_sudoku();//創建17個數的數獨(17個數及以上唯一解)matrix = create_matrix();//得到該數獨的01矩陣link(head,matrix);//將01矩陣轉化為十字雙向循環鏈表(轉化為精準覆蓋問題)if(!search(head))//得到答案行(result)flag = false;//如果不幸無解,則重新構建數獨(經本人測試,無解的概率大概為1/1000)index = 0;//初始化result棧re_init(head);//重新初始化十字鏈表}flag = false; answer = to_sudoku(matrix);//得到答案printf(" 數獨\n"); printf("==========================================================================\n\n"); printf("1.開始游戲\n"); printf("2.查看排名\n"); printf("3.退出\n\n"); printf("==========================================================================\n"); printf("請選擇:"); scanf("%d",&choice); switch(choice){ case 1:int option;printf("玩家名:");scanf("%s",&info.name);if(strlen(info.name)>20){printf("名字太長!\n");break;}printf("請選擇游戲難度: 1.簡單\t2.一般\t3.困難\n");scanf("%d",&option);printf("\n");switch(option){case 1:sudoku_level(answer,40);//挖空答案ready();time_start = get_time();if(!receiver(player_res)){printf("\n您已放棄作答!\t正確答案為:\n\n");print(answer);break;}time_end = get_time();info.m = (time_end-time_start)/60;info.s = (time_end-time_start)%60;info.level = 1;if(judge(player_res,answer)){printf("回答正確!\t用時: %d:%d\n",info.m,info.s);record(info);}else {printf("\n回答錯誤!\t正確答案為:\n\n");fflush(stdin);print(answer);}break;case 2:sudoku_level(answer,35);time_start = get_time();ready();if(!receiver(player_res)){printf("\n您已放棄作答!\t正確答案為:\n\n");print(answer);break;}time_end = get_time();info.m = (time_end-time_start)/60;info.s = (time_end-time_start)%60;info.level = 2;if(judge(player_res,answer)){printf("回答正確!\t用時: %d:%d\n",info.m,info.s);record(info);}else {printf("回答錯誤!\t正確答案為:\n\n");fflush(stdin);print(answer);}break;case 3:sudoku_level(answer,30);time_start = get_time();ready();if(!receiver(player_res)){printf("\n您已放棄作答!\t正確答案為:\n\n");print(answer);break;}time_end = get_time();info.m = (time_end-time_start)/60;info.s = (time_end-time_start)%60;info.level = 3;if(judge(player_res,answer)){printf("回答正確!\t用時: %d:%d\n",info.m,info.s);record(info);}else {printf("回答錯誤!\t正確答案為:\n\n");fflush(stdin);print(answer);}break;default:printf("no option!\n");fflush(stdin);break;}break; case 2:easy = get_record(1);normal = get_record(2);hard = get_record(3);order(easy);order(normal);order(hard);show(easy,normal,hard);break; case 3:printf("\n拜拜~\n\n");exit(0); default:printf("no option!\n");fflush(stdin);break; } system("pause"); system("cls"); } } (轉載請說明出處)總結
- 上一篇: 热血江湖战无止境与服务器连接不稳定,《热
- 下一篇: java 音频对比_java – 比较两