勤時當勉勵 歲月不待人
C/C++ 游戲開發(fā)
前言
Hello,這里是君兮_,今天更新一篇關于利用C語言實現(xiàn)掃雷游戲的博客。對于初學者來說,這也是一個非常容易上手的小項目,看完不妨自己試試哦!
廢話不多說,我們直接開始吧!
一. 掃雷游戲的介紹以及內部需要實現(xiàn)的功能解析
1.什么是掃雷游戲
相信很多人在小時候都玩過掃雷游戲,但是還是為了防止某些同學不知道掃雷具體是在做什么,我這里還是帶大家快速過一遍哦
-
這是網(wǎng)頁上隨便搜索出來的一個掃雷游戲,鏈接如下,感興趣的可以去試試:掃雷游戲
-
掃雷游戲玩法是這樣的
-
1.如圖所示,上面顯示的是此時棋盤上有幾顆雷
-
2.當我們點擊某個坐標時,它會提示周邊有幾顆雷
-
3**.當我們點擊的坐標中有雷時,我們就被“炸死”了,此時游戲結束**
- 4.當我們把所有沒有雷的坐標都點開后,說明我們排雷成功,游戲結束
- 這里就不放成功的圖片啦,以博主的實力(咳咳)過個掃雷游戲肯定是灑灑水啦~(其實是試了十分鐘一次沒過放棄了)
2.掃雷游戲所需的幾個步驟
講完什么是掃雷游戲后,咱們來講講我們要利用C語言實現(xiàn)這個”簡單“小游戲的需要的幾個步驟然后咱們一步一步實現(xiàn)。
根據(jù)以往的慣例,咱們還是畫圖說明吧
- 好了,分析完具體要實現(xiàn)哪幾個部分,咱們來分步實現(xiàn)這個小游戲
二.掃雷游戲的具體實現(xiàn)
1.打印菜單
- 非常簡單的一步,我就直接放代碼啦
void menu()//打印菜單
{
printf("*****************************\n");
printf("*********** 1.play **********\n");
printf("*********** 0.exit **********\n");
printf("*****************************\n");
}
菜單上的選擇功能
- 對于分支選擇的功能,switch語句非常滿足我們的需要,代碼如下:
int main()
{
srand((unsigned int)time(NULL));生成隨機數(shù)所需代碼,后面會講到
int input = 0;
do {
menu();//先打印菜單提示玩家選擇開始或結束游戲
printf("請選擇:> ");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戲\n");
break;
default:
printf("輸入錯誤,請重新輸入\n");
break;
}
} while (input);//退出即輸入0,結束循環(huán)
return 0;
}
- 上面代碼中,玩家輸入1即開始游戲,輸入0即退出游戲,輸入其它數(shù)則提醒玩家輸入錯誤并給與玩家重新選擇的機會。
2.初始化以及打印棋盤
- 現(xiàn)在我們來進行下一步,初始化及打印棋盤。
- 在開始前我們先分析一下我們現(xiàn)在需要做的:
- 1.我們希望打印兩個棋盤,第一個棋盤中埋地雷,這個棋盤玩家應該是不可見的.第二個棋盤就像上面網(wǎng)頁版掃雷游戲一樣填充格子,玩家應該是可見的并且能夠往上面輸入坐標掀開格子判斷是否有雷
- 2.在第二個棋盤中出了打印格子,我們還應打印一些語句提醒玩家應該怎樣玩游戲并把棋盤的行列標注出來。
- 3.分析完這兩點后,我們發(fā)現(xiàn),
二維數(shù)組
能很好的滿足我們的要求,此時我們不妨定義兩個二維數(shù)組來實現(xiàn)兩個棋盤的初始化與打印。 - 4.于此同時,我們把沒有雷的地方初始化為‘0’,把玩家能看到的棋盤初始化為‘*’。
做完了具體分析,我們來用代碼具體實現(xiàn)一下。
注意:在定義或者使用某個函數(shù)時,我們應該優(yōu)先在頭文件中聲明,下面是初始化函數(shù)與打印棋盤函數(shù)在頭文件中的聲明:
#define Row 9
#define Col 9
#define Rows Row+2
#define Cols Col+2
void InitBoard(char mine[Rows][Cols], int rows, int cols, char set);
void DisplayBoard(char board[Rows][Cols], int row, int col);
- 我們現(xiàn)在打印的是一個9*9的棋盤,為了方便我們以后對這個棋盤大小的更改,我們用常數(shù)9定義了Row與Col,如果我們以后想更改棋盤大小只需改變定義Row與Col的常數(shù)大小即可。
- 前面我們提到了,我們會把周圍地雷的數(shù)量放在中間這個格子中,為了防止出現(xiàn)圖中這種情況,我們需要為這個棋盤多加看不見的兩行兩列,也就是傳進去數(shù)組的大小應該加2.這就是上面?zhèn)鬟M的字符數(shù)組為:
//(Rows,Cols大小分別為Row+2,Col+2)
char mine[Rows][Cols]
char board[Rows][Cols]
的原因。
初始化函數(shù)InitBoard
void InitBoard(char mine[Rows][Cols], int rows, int cols, char set)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
mine[i][j] = set;
}
}
}
- 代碼里set起到的作用就是初始化棋盤,當傳進去需要埋雷的棋盤時set為’0’,當傳進去玩家見到的棋盤時,初始化為‘*’
打印棋盤的DisplayBoard函數(shù)
- 此時我們多傳進去的數(shù)組的行數(shù)與列數(shù)可以用作修飾棋盤為我們的玩家做行與列的提示
- 代碼如下:
void DisplayBoard(char board[Rows][Cols], int row, int col)
{
printf("******掃雷游戲開始*****\n");//提示玩家游戲開始
int i = 0;
for (i = 0; i <= row; i++)
{
printf("%d ", i);//打印列數(shù)
}
printf("\n");//打印完列數(shù)換行
for (i = 1; i<=row; i++)
{
int j = 0;
printf("%d ", i);//打印行數(shù)
for (j = 1; j <=col; j++)
{
printf("%c ", board[i][j]);//打印棋盤
}
printf("\n");//每打印完一行換行
}
}
- 來看看具體效果
- 怎么說?效果是不是非常好?如果你看懂了,我們繼續(xù)
3.設置地雷
- 把棋盤設置好后,我們就該考慮一下我們地雷的設置以及如何放置了。
- 首先,我們想到,如果在埋雷的棋盤中我們把所有坐標都給初始化為‘0’了,那我們就把有雷的地方設為‘1’吧(這里很重要,雖然設為別的也可以,但是這里設置為‘1’,自然是有原因的,我們稍后會講到)。
- 那我們的設好的雷應該怎么放呢?
- 對于咱們這個掃雷游戲來說,自然希望每一次雷放置的位置都是不同的,不然一旦玩的次數(shù)多了,玩家摸清了我們放雷的老底,這個游戲豈不是玩不下去了?
那這個時候,我們就需要使用隨機數(shù)函數(shù)了
#include<stdio.h>//使用該函數(shù)所需頭文件
#include<time.h>//使用time函數(shù)所需頭文件
rand();
srand((unsigned int)time(NULL));//把時間函數(shù)置空傳給srand同時由于srand要求參數(shù)必須為unsigned int型,把time(NULL)強制類型轉換一下
- 在三子棋那篇博客的隨機數(shù)章節(jié)中,我具體全面的講解了該函數(shù)應該怎么配合時間戳使用,這里就不再做過多介紹了,詳細請看以下鏈接:【C語言】三子棋詳解(包教包會的那種)
設置地雷SetMine函數(shù)
上面分析了這么多,我們來通過代碼具體實現(xiàn)一下:
- 引用該函數(shù)時先在頭文件聲明一下:
#define EasyCount 10//埋10個地雷
void SetMine(char mine[Rows][Cols], int row, int col);
- 具體代碼:
void SetMine(char mine[Rows][Cols], int row, int col)
{
int count = EasyCount;//埋地雷的個數(shù)
int i = 0;
while (count)
{
//生成在1-9范圍內的整數(shù)
int x = rand() % row + 1;
int y = rand() % row + 1;
//判斷該位置是否已被埋雷
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;//埋雷成功,所需埋的地雷減1
}
}
}
- 上面代碼中還是有很多需要注意的地方的:
- 1.我們?yōu)榱伺卸ㄎ覀兊降仔枰穸嗌賯€雷,定義了一個count,每當成功埋雷一次,count就減1,其中為了方便我們以后對所需埋雷數(shù)量的修改,我們在頭文件中把一個常數(shù)賦給了EasyCount并讓count引用,這樣改變常數(shù)的大小即可改變我們想要埋雷的多少。
- 2.我們不想把雷埋在棋盤外,為了防止這種情況的發(fā)生,我們把生成的隨機數(shù)對棋盤大小取模再+1,確保我們的雷埋在棋盤中。
- 效果如下:
- 這樣咱們是不是就把雷埋進棋盤中了。
4.玩家游玩及判斷輸贏
當我們做好棋盤上的一切準備時,就該考慮玩家應該怎么玩了。
- 第一,提示并引導玩家輸入想要排雷的坐標。
- 第二 ,判斷輸入的坐標是否合法,總不能排雷排到棋盤外吧。如果輸入坐標不合法就提示玩家重新輸入
- 第三,輸入正確坐標后,我們得判斷該坐標是否有雷,即對于埋雷的棋盤,這里存放的是不是‘1’,是‘1’,說明此格子有雷,但被“炸死”我們也得讓玩家“死”的明白,此時把埋雷的棋盤打印一遍并結束游戲。
- 第四,不是雷,為了讓游戲變得簡單一點,我們可以把玩家輸入坐標周圍所有不是雷的格子全部顯示出來,并在附近有雷的格子上顯示此時附近所埋的地雷數(shù),如圖所示:
- 我們還是先把這部分拆分一下,先實現(xiàn)在中間的格子中顯示周圍地雷數(shù)
獲取周圍地雷數(shù)函數(shù)GetMineCount
- 先在頭文件聲明該函數(shù):
int GetMineCount(char mine[Rows][Cols], int x, int y);
- 具體代碼:
int GetMineCount(char mine[Rows][Cols], int x, int y)
{
return mine[x - 1][y] + mine[x][y - 1] + mine[x - 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] + mine[x][y + 1] + mine[x + 1][y - 1] + mine[x - 1][y + 1]-8*'0';
}
-
這里咱們把有地雷的坐標設為‘1’就起作用了。
-
如圖,此時把周圍所有有地雷的位置的‘1’加起來,是不是就得到周圍存在的地雷數(shù)了?這就是上面一定要在埋雷坐標里存放‘1’的原因。
-
好了好了,我知道這里其實還有很多疑問,我來一一解釋一下:
-
1. 為啥在最后要減去‘0’呢?
-
注意:由于棋盤在初始化時是字符數(shù)組,咱們存進去的其實是字符‘1’,而此時我們希望傳出去的是周圍所有數(shù)字1之和,因此這里需要把所有‘1’相加之后再減去‘0’,這樣傳出的就是一個數(shù)字了。
-
2.既然我們這里需要一個整型,為啥在初始化時傳進去的不是一個整型數(shù)組呢?
-
在咱們初始化時,初始化函數(shù)InitBoard是同時需要把兩個棋盤都初始化的,由于玩家初始化時棋盤中為字符‘*’,因此定義InitBoard所需傳進的數(shù)組必須是一個字符型的。當然,從實際上講你也可以再重新定義一個函數(shù)進行埋雷棋盤的初始化,但是你覺得是重新定義一個函數(shù)簡單還是把‘0’,‘1’轉為對應的數(shù)字簡單?答案顯而易見。
遞歸函數(shù)ExpandBoard函數(shù)
這里比較難以理解,我盡量講的細一點,如果看完還沒理解的話,歡迎在評論區(qū)或者私信我指出你的疑問,我會第一時間回復的。
- 當我們完成了在中間顯示周圍地雷數(shù)的目標后,為了使游戲難度降低一點,我們需要完成這樣一件事:
- 判斷該坐標周圍的八個格子(即紅色處)的周圍除了該坐標剩下7個坐標中是否有雷,如果有雷,就在該格子處顯示周圍的雷的個數(shù),不再進入下一次遞歸。
-
如果沒有雷,就把該坐標周圍的格子全部點亮,就像這樣。然后繼續(xù)遞歸判斷
- 有雷不再遞歸點亮格子的情況:
- 總結:也就是說,遞歸的條件是中間的格子為空,然后把它周邊的8個格子當新一層的遞歸函數(shù)中的中心格子來計算,如果還為空就繼續(xù)向外遞歸,不為空就停止了(即中間的格子為空時,把周邊的8個格子帶入下一層的遞歸中)。 - 注意:在遞歸的過程中,不要把上一層已經(jīng)判斷過的空格子再判斷一次(即此時里面的值已經(jīng)不為’*'了),否則就會讓該遞歸陷入死循環(huán)!?。?/font>
- 代碼實現(xiàn)如下:
- 頭文件中聲明:
void ExpandBoard(char board[Rows][Cols], char mine[Rows][Cols], int row, int col, int* win);
- 該函數(shù)定義:
void ExpandBoard(char board[Rows][Cols], char mine[Rows][Cols], int x, int y, int* win)
{
if (x >= 1 && x <= Row && y >= 1 && y <= Col)//坐標必須在棋盤內
{
int ret = GetMineCount(mine, x, y);//獲取周圍雷的個數(shù)
if (ret == 0)周圍沒雷
{
board[x][y] = '0';//把沒雷的地方展開點亮為0
int i = 0;
//向四周八個格子遞歸
for (i = x - 1; i <= x + 1; i++)
{
int j = 0;
for (j = y - 1; j <= y + 1; j++)
{
if (board[i][j] == '*')//防止已經(jīng)展開的部分再次遞歸,進入死循環(huán)
{
ExpandBoard(board, mine, i, j, win);//遞歸
}
}
}
}
else
{
board[x][y] = ret + '0';//周圍有雷,把雷的個數(shù)以字符的形式放在中間格子中
}
(*win)++;//記錄展開的沒有雷的格子的數(shù)量,方便判斷輸贏
}
}
- 建議上面那段話多讀幾遍,最好自己把網(wǎng)頁版的掃雷游戲玩幾次,結合它的展開方式理解上面關于遞歸的那段話
- 當我第一次看上面關于遞歸的那段代碼時,不禁感嘆程序員就挺“禿然”的。
- 加油,就剩最后一步辣
-
如果你能堅持到最后,那我覺得這件事情------------------------------
- 小小放松一下,咱們繼續(xù)
判斷輸贏函數(shù)FindMine
當理解了上面兩個函數(shù)后,下面判斷輸贏的這個函數(shù)就比較好理解了。
- 直接放代碼:
- 頭文件中對函數(shù)的聲明:
void FindMine(char board[Rows][Cols], char mine[Rows][Cols], int row, int col);
- 該函數(shù)的定義:
void FindMine(char board[Rows][Cols],char mine[Rows][Cols], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
while(win < row * col - EasyCount)//排的雷小于放置的雷,就繼續(xù)排雷
{
printf("請輸入要掃雷的坐標:>\n");
scanf("%d %d",&x,&y);
if (x >= 1 && x <= row && y >= 1 && y <= col)//判斷排雷坐標合法性
{
if (board[x][y] == '*')//判斷該坐標是否排查過
{
if (mine[x][y] == '1')
{
printf("恭喜你,被雷炸死了\n");
DisplayBoard(mine, Row, Col);//打印埋雷棋盤,“死”的明白
break;
}
else
{
ExpandBoard(board, mine, x, y, &win);//遞歸展開
DisplayBoard(board, Row, Col);//打印展開后的棋盤
printf("--------------還需翻開%d格--------------\n", row * col - EasyCount - win);//提示一下還要排多少格才能成功
}
}
else
{
printf("該坐標已被排查過了,請重新輸入\n");
}
}
else
printf("坐標非法,請重新輸入\n");
}
if (win == (Row * Col - EasyCount))//所有雷都被找到
{
printf("恭喜你,排雷成功!\n");
DisplayBoard(mine, Row, Col);
}
}
- 前面都基本解釋過了,這里不做過多展開。有不明白的地方自己看上面代碼注釋即可。
5.game函數(shù)
當我們把所有模塊功能都寫好后,是時候寫一個函數(shù)把所有模塊封裝在一起了 ,這就是我們接下來的game函數(shù)文章來源:http://www.zghlxwxcb.cn/news/detail-500228.html
- 在主函數(shù)所在文件中,不需要聲明,代碼如下:
void game()
{
char mine[Rows][Cols] = {0};//存放埋雷棋盤的數(shù)組
char board[Rows][Cols] = {0};//存放玩家看到的棋盤的數(shù)組
InitBoard(mine, Rows, Cols,'0');//初始化埋雷棋盤為'0'
InitBoard(board, Rows,Cols,'*');//初始化玩家看到的棋盤為‘*’
SetMine(mine, Row, Col);
//DisplayBoard(mine, Row, Col);//打印埋雷棋盤,一般玩家不可見,自己調試的時候使用
DisplayBoard(board, Row, Col);//打印玩家棋盤
FindMine(board,mine, Row, Col);//判斷輸贏的函數(shù)
}
三.試玩一下掃雷游戲
- 注意哦,當程序有bug的時候,不要傻愣愣的只設10個雷,這樣你測一天都測不完的。
- 測試結果如下:(棋盤中有80個雷)
- 正常結果如圖(10個雷):
四.源碼
- 把源碼放這里哦,有需要自取。
game.h(頭文件)
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#define Row 9
#define Col 9
#define Rows Row+2
#define Cols Col+2
#define EasyCount 10
void InitBoard(char mine[Rows][Cols], int rows, int cols, char set);
void DisplayBoard(char board[Rows][Cols], int row, int col);
void SetMine(char mine[Rows][Cols], int row, int col);
void FindMine(char board[Rows][Cols], char mine[Rows][Cols], int row, int col);
int GetMineCount(char mine[Rows][Cols], int x, int y);
void ExpandBoard(char board[Rows][Cols], char mine[Rows][Cols], int row, int col, int* win);
test.c(主函數(shù)文件)
#include"game.h"
void menu()//打印菜單
{
printf("*****************************\n");
printf("*********** 1.play **********\n");
printf("*********** 0.exit **********\n");
printf("*****************************\n");
}
void game()
{
char mine[Rows][Cols] = {0};//存放埋雷棋盤的數(shù)組
char board[Rows][Cols] = {0};//存放玩家看到的棋盤的數(shù)組
InitBoard(mine, Rows, Cols,'0');//初始化埋雷棋盤為'0'
InitBoard(board, Rows,Cols,'*');//初始化玩家看到的棋盤為‘*’
SetMine(mine, Row, Col);
//DisplayBoard(mine, Row, Col);//打印埋雷棋盤,一般玩家不可見,自己調試的時候使用
DisplayBoard(board, Row, Col);//打印玩家棋盤
FindMine(board,mine, Row, Col);//判斷輸贏的函數(shù)
}
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do {
menu();
printf("請選擇:> ");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戲\n");
break;
default:
printf("輸入錯誤,請重新輸入\n");
break;
}
} while (input);
return 0;
}
game.c(定義函數(shù)的文件)
#include"game.h"
void InitBoard(char mine[Rows][Cols], int rows, int cols, char set)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
mine[i][j] = set;
}
}
}
void DisplayBoard(char board[Rows][Cols], int row, int col)
{
printf("******掃雷游戲開始*****\n");
//for(i=0;i<)
int i = 0;
for (i = 0; i <= row; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i<=row; i++)
{
int j = 0;
printf("%d ", i);
for (j = 1; j <=col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
void SetMine(char mine[Rows][Cols], int row, int col)
{
int count = EasyCount;
int i = 0;
while (count)
{
int x = rand() % row + 1;
int y = rand() % row + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
int GetMineCount(char mine[Rows][Cols], int x, int y)
{
return mine[x - 1][y] + mine[x][y - 1] + mine[x - 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] + mine[x][y + 1] + mine[x + 1][y - 1] + mine[x - 1][y + 1]-8*'0';
}
void ExpandBoard(char board[Rows][Cols], char mine[Rows][Cols], int x, int y, int* win)
{
if (x >= 1 && x <= Row && y >= 1 && y <= Col)
{
int ret = GetMineCount(mine, x, y);
if (ret == 0)
{
board[x][y] = '0';
int i = 0;
for (i = x - 1; i <= x + 1; i++)
{
int j = 0;
for (j = y - 1; j <= y + 1; j++)
{
if (board[i][j] == '*')
{
ExpandBoard(board, mine, i, j, win);
}
}
}
}
else
{
board[x][y] = ret + '0';
}
(*win)++;
}
}
void FindMine(char board[Rows][Cols],char mine[Rows][Cols], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
while(win < row * col - EasyCount)
{
printf("請輸入要掃雷的坐標:>\n");
scanf("%d %d",&x,&y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (board[x][y] == '*')
{
if (mine[x][y] == '1')
{
printf("恭喜你,被雷炸死了\n");
DisplayBoard(mine, Row, Col);
break;
}
else
{
ExpandBoard(board, mine, x, y, &win);
DisplayBoard(board, Row, Col);
printf("--------------還需翻開%d格--------------\n", row * col - EasyCount - win);
}
}
else
{
printf("該坐標已被排查過了,請重新輸入\n");
}
}
else
printf("坐標非法,請重新輸入\n");
}
if (win == (Row * Col - EasyCount))
{
printf("恭喜你,排雷成功!\n");
DisplayBoard(mine, Row, Col);
}
}
總結
- 今天的內容到此為止啦,掃雷游戲真的不算特別難,我希望看到這里的人都可以親手獨立實現(xiàn)一下(能堅持下來的大家都是好樣的),只有你能夠獨立的把代碼寫出來,才能算真正的學會了。
- 好了,如果你有任何的疑問歡迎在評論區(qū)指出,也歡迎隨時私信我與我討論,我看到會第一時間回復的,我們下次再見啦?。?!
寫到這里已經(jīng)11000+了,這篇文章從早上開始寫一直寫到現(xiàn)在,主要花時間的地方是斟酌語句,我自己第一次看別人有關的文章時候雀氏是沒怎么懂的,所以我希望我自己寫的時候能配合圖片啊啥的為大家解釋的清楚容易理解一點。
新人創(chuàng)作不易,如果覺得這篇文章內容對你有所幫助的話,點個三連再走吧,謝謝大家了!文章來源地址http://www.zghlxwxcb.cn/news/detail-500228.html
到了這里,關于【C語言】萬字教學,帶你分步實現(xiàn)掃雷游戲(內含遞歸函數(shù)解析),劍指掃雷,一篇足矣的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!