一、源碼簡介
這是一個可以進行掃雷游戲的小程序,采用C語言進行編寫。
上下左右控制光標位置,按j鍵進行標記,按k進行點擊探雷,并且當光標 放在數(shù)字上,且周圍的雷都已經(jīng)被正確標記時,按k可以點開周圍所有的空白,不過出錯會結(jié)束游戲。
雷區(qū)長寬為25格,初始有10雷,每過一關(guān)增加20雷。
編譯環(huán)境:VC6.0(采取純C語言寫法)
第三方庫:無
二、運行截圖
三、源碼解析
我們先來看游戲的主體邏輯。
雖然下面的代碼很長,但邏輯還是較為清晰的。
以下循環(huán)
若游戲未開始,初始化。
若游戲開始,則檢測鍵盤輸入
按下ASDW則移動光標
第一次按下K,則初始化雷區(qū)
按下K則點開空白,或者清雷,并檢測是否勝利
按下j則進行標記
游戲結(jié)束跳出循環(huán)
void Gamerun()//游戲主體 { int first; while(1) { if(gamestate==0)//初始化 { first=0; m_time=time(NULL);//時間初始化 Init_display();//初始化顯示區(qū)域 CONSOLE_CURSOR_INFO cursor_info = { 1,0 }; SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE),&cursor_info);//設(shè)置指定控制臺屏幕緩沖區(qū)的光標的大小和可見性 gamestate=1; } else if(gamestate==1)//游戲中 { RemainderMine();//計算剩余的雷 Draw_display(); MoveCursor(); PressJ(); if(GetAsyncKeyState('K')&1)//如果按下K { if(first==0) { Init_mine(pos.y,pos.x); OpenDisplay(pos.y,pos.x); first++; if(Victory())//判定是否獲勝 { Showmine(); Draw_display(); UINT uint=MessageBox(NULL,TEXT("恭喜過關(guān)!是否繼續(xù)。"),TEXT("提示"),1); if(uint==IDOK) { count+=20; level++; gamestate=0; } else if(uint==IDCANCEL) { gamestate=2; } else; } else; } else { if(TreadMine(pos.y,pos.x))//判斷是否踩到雷 { Showmine(); Draw_display(); UINT uint=MessageBox(NULL,TEXT("掃雷失??!是否重新開始?"),TEXT("提示"),MB_OKCANCEL|MB_ICONERROR); if(uint==IDOK) { gamestate=0; } else if(uint==IDCANCEL) { gamestate=2; } else; } else { OpenDisplay(pos.y,pos.x); } if(OpenNumDisplay1(pos.y,pos.x))//按K也可以清雷,返回值為1表示踩雷 { Showmine(); Draw_display(); UINT uint=MessageBox(NULL,TEXT("掃雷失敗!是否重新開始?"),TEXT("提示"),MB_OKCANCEL|MB_ICONERROR); if(uint==IDOK) { gamestate=0; } else if(uint==IDCANCEL) { gamestate=2; } else; } else; if(Victory())//判定是否獲勝 { Showmine(); Draw_display(); UINT uint=MessageBox(NULL,TEXT("恭喜過關(guān)!是否繼續(xù)。"),TEXT("提示"),1); if(uint==IDOK) { count+=20; level++; gamestate=0; } else if(uint==IDCANCEL) { gamestate=2; } else; } else; } } else; } else if(gamestate==2)//游戲結(jié)束 { printf("GAME END!"); break; } else; } }
下面看一些重要部分的實現(xiàn)。
改變光標位置以及定義為輸出模式
void Pos(int x,int y)//定義一個設(shè)置光標位置到(x,y)的函數(shù) { COORD pos; HANDLE hOutput; pos.X=x; pos.Y=y; hOutput=GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleCursorPosition(hOutput,pos); }
初始化雷區(qū)實現(xiàn)原理:
void Init_mine(int m, int n)//初始化雷區(qū) { int num=count; int x,y; srand((unsigned)time(NULL));//利用時間獲取隨機種子 while(num>0) { x=rand()%height+1;//1-25 y=rand()%width+1;//1-25 if(mine[x][y]==0&&(m!=x||n!=y))//避免重復(fù) { mine[x][y]=1; num--; } else; } }
這個函數(shù)在第一次按下k的時候調(diào)用,輸入值為光標的位置。在生成雷的時候,如果與之前的雷或者光標重合,那么會重新生成一個雷,這樣做可以避免直接失敗。
檢測某一點周圍的雷數(shù)目,以及標記數(shù)目
這兩者的原理都是一樣的。直接對光標周圍八個點進行判定,若是則++。
繪制游戲畫面
void Draw_display()//繪制游戲畫面 { Pos(7, 7);//光標定位 int i,j; for(i=1;i<=height;i++)//逐行逐列輸出游戲畫面 { for(j=1;j<=width;j++) { if(j==pos.x&&i==pos.y) SetConsoleTextAttribute(ColorHandle,0X5|0XC);//對不同類型的圖像設(shè)置不同顏色 else if(map[i][j]==10) SetConsoleTextAttribute(ColorHandle,0X94); else SetConsoleTextAttribute(ColorHandle,0X97); switch(map[i][j]) { case 0:printf(" ");break; case 1:printf("1 ");break; case 2:printf("2 ");break; case 3:printf("3 ");break; case 4:printf("4 ");break; case 5:printf("5 ");break; case 6:printf("6 ");break; case 7:printf("7 ");break; case 8:printf("8 ");break; case 9:printf("■");break;//未知區(qū)域 case 10:printf("▲");break;//標記 case 11:printf("雷");break;//雷 } if(j==pos.x&&i==pos.y) SetConsoleTextAttribute(ColorHandle,0X7);//關(guān)閉顏色 else if(map[i][j]==10) SetConsoleTextAttribute(ColorHandle,0X7); else SetConsoleTextAttribute(ColorHandle,0X7); } Pos(7,7+i);//光標移到下一行 } Pos(7,3); printf("未排除的雷:%d ",RemMine);//打印各種文字信息 Pos(7,5); printf("時間:%ld 秒 ",long(time(NULL)-m_time)); Pos(27,3); printf("關(guān)卡:%d ",level); }
采用雙層循環(huán)結(jié)構(gòu),對區(qū)域的每個點進行一次判定,將對應(yīng)的標識及顏色輸出就可以了。注意不要混淆圖像數(shù)組和雷的信息數(shù)組。
展開無雷區(qū)域
在掃雷游戲中,當我們第一次點擊時,很有可能點開一大片區(qū)域。其實現(xiàn)如下面的代碼所示。
void OpenDisplay(int x,int y)//單擊后展開顯示無雷區(qū)域 { int i,j; int offsetX[] = { 0,1,1,1,0,-1,-1,-1 }; int offsetY[] = { -1,-1,0,1,1,1,0,-1 }; if(map[x][y]==9&&x>0&&x<=width&&y>0&&y<=height)//中心點顯示有幾顆雷 { map[x][y]=Judge_mine(x,y); if(map[x][y]>0)//某點周圍有雷就不展開 { return; } else; } else if(map[x][y]==10||(map[x][y]>=1&&map[x][y]<=8))//某點為標記或數(shù)字 不展開 { return; } else; for(i=0;i<8;i++)//周圍8點 { if(x>0&&x<=width&&y>0&&y<=height) { if(map[x+offsetX[i]][y+offsetY[i]]==10&&mine[x+offsetX[i]][y+offsetY[i]]!=1)//展開的時候,如果周圍某點為標記且不是雷 { map[x+offsetX[i]][y+offsetY[i]]=9;//改為未知區(qū)域 } if(mine[x+offsetX[i]][y+offsetY[i]]==0&&map[x+offsetX[i]][y+offsetY[i]]==9)//無雷且未知,則展開 { map[x+offsetX[i]][y+offsetY[i]]=Judge_mine(x+offsetX[i],y+offsetY[i]);//得到某點周圍的雷的個數(shù) if(map[x+offsetX[i]][y+offsetY[i]]==0)//該點無雷且無數(shù)字 { OpenDisplay(x+offsetX[i],y+offsetY[i]);//遞歸 } } } } return; }
具體來說,就是采取遞歸的算法。先判定該點是否產(chǎn)生變化,如果產(chǎn)生,則對周圍8個點同樣做出一次判定。如果這些點周圍的雷數(shù)為0,則遞歸調(diào)用該函數(shù)。
清雷動作
當一個數(shù)字周圍的雷全部被點開之后,在其上按k會點開周圍無雷的區(qū)域
int OpenNumDisplay1(int x,int y)//將數(shù)字周圍區(qū)域顯示出來。返回值表明是否因為該動作踩雷,1為是 { int i,j; int offsetX[]={ 0,1,1,1,0,-1,-1,-1 }; int offsetY[]={ -1,-1,0,1,1,1,0,-1 }; if(map[x][y]<9&&map[x][y]>0)//1-8 { if(Judge_mine(x,y)==Judge_cur(x,y))//若周圍標記數(shù)量等于周圍雷的數(shù)量,展開 { for(i=0;i<8;i++) { if(x>0&&x<=width&&y>0&&y<=height)//越界判斷 { if(map[x+offsetX[i]][y+offsetY[i]]==9||map[x+offsetX[i]][y+offsetY[i]]==10)//為標記和未知區(qū)域時,進行判定 { if(map[x+offsetX[i]][y+offsetY[i]]==10&&mine[x+offsetX[i]][y+offsetY[i]]==1)//標記正確 { continue; } else if(map[x+offsetX[i]][y+offsetY[i]]==10&&mine[x+offsetX[i]][y+offsetY[i]]!=1) //標記錯誤 { return 1;//踩雷 } else; } else; } else; } for(i=0;i<8;i++) { if(x>0&&x<=width&&y>0&&y<=height)//越界判斷 { if(map[x+offsetX[i]][y+offsetY[i]]==9)//為未知區(qū)域則正常展開 { OpenDisplay(x+offsetX[i],y+offsetY[i]);//展開 } } } } } return 0; }
輸入為按下k時的光標位置。判定周圍標記數(shù)是否與雷數(shù)相同,是則檢查每一個標記處是否為雷,若全部對應(yīng),則清雷,若有不對應(yīng)的,則游戲結(jié)束。
其余定義的函數(shù)還有檢測是否踩雷,計算剩余的雷,判定是否勝利,顯示雷,移動光標,做標記。這些函數(shù)一般都是用雙重循環(huán),或者直接改變變量的方法進行的。這里暫時不給出源碼,但完整源碼也會有對應(yīng)的內(nèi)容。
四、完整源碼
C語言網(wǎng)提供由在職研發(fā)工程師或ACM藍橋杯競賽優(yōu)秀選手錄制的視頻教程,并配有習(xí)題和答疑,點擊了解:
一點編程也不會寫的:零基礎(chǔ)C語言學(xué)練課程
解決困擾你多年的C語言疑難雜癥特性的C語言進階課程
從零到寫出一個爬蟲的Python編程課程
只會語法寫不出代碼?手把手帶你寫100個編程真題的編程百練課程
信息學(xué)奧賽或C++選手的 必學(xué)C++課程
藍橋杯ACM、信息學(xué)奧賽的必學(xué)課程:算法競賽課入門課程
手把手講解近五年真題的藍橋杯輔導(dǎo)課程