沒事寫點啥(一)——C++掃雷
阿新 • • 發佈:2019-01-28
先說明一下,本來這個文章我在新浪部落格上發過一次,但是不知道最近怎麼回事新浪總登不上去= =再加CSDN是專門為IT類服務的(我剛剛註冊的號,給我的印象是這樣)就在這再寫一次吧,這次我會盡量寫的詳細一點。
鄙人呢是個小白,會的不多也就是C++了,而且目前還是命令列版本的,圖形部分還沒學會,有的地方還很麻煩,總之不管怎麼說吧算是稀裡糊塗的寫出來了,發在這和大家交流一下,恭請各路大神賜教~~
掃雷,作為一款小遊戲可以說非常成功,一段時間內風靡全世界,也是您在空餘時間不多時用來消遣的比較好的一個選擇(怎麼像打廣告一樣= =)相信大家對它的規則應該不陌生吧,不過呢我在這還是再囉嗦一會。
1.點選一個方格,如果翻開是地雷的話則失敗
2.如果翻開不是地雷的話,若為非零數字則表示周圍若干個格子中地雷的個數(中間部分則為周圍8個格子,邊緣非頂點處則為5個格子,頂點處為3個格子)
3.若為0則會向外拓展
4.若玩家可以確定一個地方為地雷則可以對此進行標記
5.當非地雷格子被全部翻開時則獲勝
掃雷的演算法裡,我想最複雜的地方我不細說大家也應該知道,就是空格的處理。尤其是遇到一大片空地時,簡直就是多米諾效應。這種情況下,我想最好的方法恐怕也就是遞迴了。
我這裡寫的是40/16*16的掃雷,也就是16*16的地圖40個地雷,屬於中等難度。想調的話,改動幾個地方的數即可
無心之舉,難免有紕漏之處,各路大神若有改進之言,在下洗耳恭聽。
好了廢話不多說了,上程式碼,我會盡量把註釋加全。
#include<iostream> #include<cstdlib> #include<ctime> #include<conio.h>//程式碼中的getche()在這個標頭檔案裡,只取一個字元 #include<windows.h>//程式碼中用到了部分windowsAPI函式 #define LANDMINE 16 using namespace std; int field[16][16],show[16][16]={{0}};//field陣列是指整個地圖的實際情況,show陣列是指地圖的探測情況,0-未翻開且未標記,1-翻開,2-標記 char answer[16][16];//正確答案,當用戶失敗時輸出 int mine[40];//mine陣列用於儲存地雷位置,具體為16*行數+列數 void gotoxy(int x,int y)/*該函式用於移動游標,其中SetConsoleCursorPosition函式請看這裡<a target=_blank href="http://baike.sogou.com/v73182107.htm?fromTitle=SetConsoleCursorPosition">http://baike.sogou.com/v73182107.htm?fromTitle=SetConsoleCursorPosition</a> */ { COORD pos; pos.X=2*x; pos.Y=y; SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),pos); } void color(int a)/*該函式用於改變輸出字元顏色,其中SetConsoleTextAttribute函式請看這裡<a target=_blank href="http://baike.sogou.com/v72432554.htm?fromTitle=SetConsoleTextAttribute">http://baike.sogou.com/v72432554.htm?fromTitle=SetConsoleTextAttribute</a> */ { SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),a); } int once(int mine[],int key,int j)//佈雷時用到,觀看取到隨機數之前是否出現過,出現過則返回0,否則為1 { int i; for(i=0;i<j;i++) { if(mine[i]==key) return 0; } return 1; } int count(int field[][16],int i,int j)/*這個函式我知道我寫的很麻煩= =用於得到佈雷後非雷格子對應的數字,由於中間部分、邊緣和頂點有一些很小的差別,我這就分了好多情況……*/ { int d=0; int k,l; if(i>0&&i<15&&j>0&&j<15) { for(k=i-1;k<=i+1;k++) { for(l=j-1;l<=j+1;l++) { if(field[k][l]==LANDMINE) d++; } } } else if(i==0&&j>0&&j<15) { for(k=i;k<=i+1;k++) { for(l=j-1;l<=j+1;l++) { if(field[k][l]==LANDMINE) d++; } } } else if(i==15&&j>0&&j<15) { for(k=i-1;k<=i;k++) { for(l=j-1;l<=j+1;l++) { if(field[k][l]==LANDMINE) d++; } } } else if(j==0&&i>0&&i<15) { for(k=i-1;k<=i+1;k++) { for(l=j;l<=j+1;l++) { if(field[k][l]==LANDMINE) d++; } } } else if(j==15&&i>0&&i<15) { for(k=i-1;k<=i+1;k++) { for(l=j-1;l<=j;l++) { if(field[k][l]==LANDMINE) d++; } } } else if(i==0&&j==0) { for(k=0;k<=1;k++) { for(l=0;l<=1;l++) { if(field[k][l]==LANDMINE) d++; } } } else if(i==0&&j==15) { for(k=0;k<=1;k++) { for(l=14;l<=15;l++) { if(field[k][l]==LANDMINE) d++; } } } else if(i==15&&j==0) { for(k=14;k<=15;k++) { for(l=0;l<=1;l++) { if(field[k][l]==LANDMINE) d++; } } } else if(i==15&&j==15) { for(k=14;k<=15;k++) { for(l=14;l<=15;l++) { if(field[k][l]==LANDMINE) d++; } } } return d; } void Arrange()//用於得到這個地圖的情況 { int i,j; for(i=0;i<16;i++) { for(j=0;j<16;j++) field[i][j]=0; } srand((unsigned)time(NULL)); for(i=0;i<40;i++) { int t=rand()%256; while(once(mine,t,i)==0) t=rand()%256; mine[i]=t;//每取一個隨機數進行一次判定,如果前面出現過就重新取,否則就取該數 } for(i=0;i<40;i++) field[mine[i]/16][mine[i]%16]=LANDMINE; for(i=0;i<16;i++) { for(j=0;j<16;j++) { if(field[i][j]!=LANDMINE) field[i][j]=count(field,i,j); } } } void an(int field[][16])//得到答案矩陣 { int i,j; for(i=0;i<16;i++) { for(j=0;j<16;j++) { if(field[i][j]==LANDMINE) answer[i][j]='L'; else answer[i][j]=(char)(field[i][j]+48); } } } void point(int i,int j)//點開格子時的處理 { int k,l; if(show[i][j]==2) show[i][j]==2; else if(field[i][j]==LANDMINE)//若觸雷則直接gg,啊不對,應該叫顯示失敗,並輸出正確答案,然後直接退出 { cout<<"不好意思,你輸了!下次走運!"<<endl; an(field); cout<<"正確答案為:"<<endl; for(int p=0;p<16;p++) { for(int q=0;q<16;q++) { if(answer[p][q]=='L') { color(12); cout<<"●"; } else { color(10); cout<<answer[p][q]<<" "; } } cout<<endl; } color(15); system("pause"); exit(0); } else if(field[i][j]==0)//若點開為空格子則開始遞迴演算法 { show[i][j]=1; if(i>0&&i<15&&j>0&&j<15) { for(k=i-1;k<=i+1;k++) { for(l=j-1;l<=j+1;l++) { if(show[k][l]==0) { if(field[k][l]!=LANDMINE) { show[k][l]=1; point(k,l); } } } } }//周圍未點開的非雷格子全部點開並遞迴,下面同理,只是不同位置的不同情況而已 else if(i==0&&j>0&&j<15) { for(k=i;k<=i+1;k++) { for(l=j-1;l<=j+1;l++) { if(show[k][l]==0) { if(field[k][l]!=LANDMINE) { show[k][l]=1; point(k,l); } } } } } else if(i==15&&j>0&&j<15) { for(k=i-1;k<=i;k++) { for(l=j-1;l<=j+1;l++) { if(show[k][l]==0) { if(field[k][l]!=LANDMINE) { show[k][l]=1; point(k,l); } } } } } else if(j==0&&i>0&&i<15) { for(k=i-1;k<=i+1;k++) { for(l=j;l<=j+1;l++) { if(show[k][l]==0) { if(field[k][l]!=LANDMINE) { show[k][l]=1; point(k,l); } } } } } else if(j==15&&i>0&&i<15) { for(k=i-1;k<=i+1;k++) { for(l=j-1;l<=j;l++) { if(show[k][l]==0) { if(field[k][l]!=LANDMINE) { show[k][l]=1; point(k,l); } } } } } else if(i==0&&j==0) { for(k=0;k<=1;k++) { for(l=0;l<=1;l++) { if(show[k][l]==0) { if(field[k][l]!=LANDMINE) { show[k][l]=1; point(k,l); } } } } } else if(i==0&&j==15) { for(k=0;k<=1;k++) { for(l=14;l<=15;l++) { if(show[k][l]==0) { if(field[k][l]!=LANDMINE) { show[k][l]=1; point(k,l); } } } } } else if(i==15&&j==0) { for(k=14;k<=15;k++) { for(l=0;l<=1;l++) { if(show[k][l]==0) { if(field[k][l]!=LANDMINE) { show[k][l]=1; point(k,l); } } } } } else if(i==15&&j==15) { for(k=14;k<=15;k++) { for(l=14;l<=15;l++) { if(show[k][l]==0) { if(field[k][l]!=LANDMINE) { show[k][l]=1; point(k,l); } } } } } } else show[i][j]=1;//若為非零非雷格子則直接點開該格子 } int judge(int mind[],int show[][16])//判定勝利函式 { int i,j; int ans=0,flag=0; for(i=0;i<40;i++) { if(show[mind[i]/16][mind[i]%16]==2) ans++; } for(i=0;i<16;i++) { for(j=0;j<16;j++) { if(show[i][j]==2) flag++; } } if(ans==40&&flag==40) return 1; else return 0;//中心思想為若標記的個數與地雷的個數相等則返回1,否則返回0 } void draw(int show[][16])//該函式用於描繪整個地圖 { gotoxy(1,0); color(10); cout<<'A'<<" "<<'B'<<" "<<'C'<<" "<<'D'<<" "<<'E'<<" "<<'F'<<" "<<'G'<<" "<<'H'<<" "<<'I'<<" "<<'J'<<" "<<'K'<<" "<<'L'<<" "<<'M'<<" "<<'N'<<" "<<'O'<<" "<<'P'<<" "; gotoxy(0,1); int i,j; for(i=0;i<16;i++) { gotoxy(0,i+1); color(10); cout<<(char)(i+65)<<" "; for(j=0;j<16;j++) { if(show[i][j]==0) { color(11); cout<<"■"; } else if(show[i][j]==1) { color(14); cout<<field[i][j]<<" "; } else if(show[i][j]==2) { color(12); cout<<"★"; } } } } int main() { Arrange(); char ch,c; int row=8,col=8; int i,j; PlaySound("馬克西姆-克羅埃西亞狂想曲.wav",NULL,SND_FILENAME|SND_ASYNC|SND_LOOP);/*PlaySound函式用於播放音訊,詳情點選這裡<a target=_blank href="http://baike.sogou.com/v10930259.htm?fromTitle=playsound">http://baike.sogou.com/v10930259.htm?fromTitle=playsound</a> */ MessageBox(NULL,"製作人:天妒\n背景音樂:馬克西姆-克羅埃西亞狂想曲","掃雷 V1.0",MB_OK); MessageBox(NULL,"按F或L鍵來決定標記地雷還是直接探測\n偶數次標記地雷取消標記\n之後WSAD四鍵來移動格子,其他鍵確定格子位置\n地雷全部標記正確則獲勝\n注意區分大小寫!\n祝你成功!","遊戲規則",MB_OK);/*MessageBox函式用於顯示對話方塊,詳情點選這裡<a target=_blank href="http://baike.sogou.com/v7685382.htm?fromTitle=messagebox">http://baike.sogou.com/v7685382.htm?fromTitle=messagebox</a> */ clock_t start,finish;//用於計時 long long totaltime; start=clock(); while(judge(mine,show)!=1) { gotoxy(0,0); draw(show); color(15); gotoxy(20,3); cout<<"標記地雷還是直接探測?"; gotoxy(20,4); cout<<"F--標記地雷"; gotoxy(20,5); cout<<"L--直接探測"; gotoxy(20,6); ch=getche(); while(ch!='F'&&ch!='L') { gotoxy(20,7); cout<<"輸入有誤!"; gotoxy(20,6); ch=getche(); } if(ch=='F') { gotoxy(20,7); cout<<"輸入無誤!"; gotoxy(20,10); cout<<"按W A S D移動游標,其他鍵決定格子"; gotoxy(0,18); c=getche(); while(c=='W'||c=='A'||c=='S'||c=='D') { switch(c)//WASD控制選擇的格子位置 { case 'W':row=row-1<0?0:row-1;break; case 'A':col=col-1<0?0:col-1;break; case 'S':row=row+1>15?15:row+1;break; case 'D':col=col+1>15?15:col+1;break; } gotoxy(20,15); cout<<"當前格子位置:"<<(char)(row+65)<<" "<<(char)(col+65); gotoxy(0,18); c=getche(); } show[row][col]=2-show[row][col]; if(judge(mine,show)==1) { cout<<"恭喜您!您獲勝了!"<<endl; gotoxy(0,20); finish=clock(); totaltime=finish-start; cout<<"您本次掃雷用時:"<<(long long)(totaltime/CLOCKS_PER_SEC)<<"s"<<endl;//勝利則顯示用時 system("pause"); exit(0); } } else if(ch=='L') { gotoxy(20,7); cout<<"輸入無誤!"; gotoxy(20,10); cout<<"按W A S D移動游標,其他鍵決定格子"; gotoxy(20,15); cout<<"當前格子位置:"<<(char)(row+65)<<" "<<(char)(col+65); gotoxy(0,18); c=getche(); while(c=='W'||c=='A'||c=='S'||c=='D') { switch(c) { case 'W':row=row-1<0?0:row-1;break; case 'A':col=col-1<0?0:col-1;break; case 'S':row=row+1>15?15:row+1;break; case 'D':col=col+1>15?15:col+1;break; } gotoxy(20,15); cout<<"當前格子位置:"<<(char)(row+65)<<" "<<(char)(col+65); gotoxy(0,18); c=getche(); } point(row,col); } } system("pause"); return 0; }