本篇博客會講解,如何使用C語言實(shí)現(xiàn)掃雷小游戲。
0.思路及準(zhǔn)備工作
- 使用2個二維數(shù)組mine和show,分別來存儲雷的位置信息和排查出來的雷的信息,前者隱藏,后者展示給玩家。假設(shè)盤面大小是9×9,這2個二維數(shù)組都要開大一圈,也就是大小是11×11,這是為了更加方便的數(shù)邊角上雷的個數(shù),防止越界。
- mine數(shù)組中用字符1表示雷,字符0表示非雷。show數(shù)組中用*表示該位置沒有被排查過,數(shù)字字符表示周圍一圈(8個位置)有幾個雷,空格表示周圍一圈沒有雷,!表示該位置被標(biāo)記了。
- 如果玩家排查的位置是雷,那么,游戲失敗。當(dāng)玩家把所有非雷的位置找出來后,掃雷成功。
先定義一些符號,后面會用。
// 掃雷盤面的有效區(qū)域大小
// 雷會在該區(qū)域中生成,玩家只能在該區(qū)域內(nèi)排查或者標(biāo)記雷
// 同時是實(shí)際展示的區(qū)域大小
#define ROW 9
#define COL 9
// 實(shí)際的盤面大小
// 防止掃描周圍8個坐標(biāo)時出現(xiàn)越界訪問
// 此時哪怕掃描有效區(qū)域的周圍哪怕不進(jìn)行判斷也不會越界
// 因?yàn)樽钔饷嬗幸蝗ΡWo(hù)措施
#define ROWS (ROW + 2)
#define COLS (COL + 2)
// 雷的個數(shù)
#define EASY_COUNT 10 // 簡單難度
2個數(shù)組分別是:
// 存儲雷的位置信息
// '1' - 雷
// '0' - 非雷
char mine[ROWS][COLS] = { 0 };
// 展示給玩家的信息
// '*' - 未排查
// '1'~'9' - 該位置已被排查,且該位置周圍有雷
// 數(shù)字字符表示周圍雷的個數(shù)
// 空格 - 該位置已被排查,且該位置周圍沒有雷
// '!' - 該位置被玩家標(biāo)記,可能是雷,也可能不是雷
// '!'所在位置并沒有被排查,不算作已排查位置
char show[ROWS][COLS] = { 0 };
1.初始化
我們分別把mine和show數(shù)組初始化成全字符0和全*??梢岳枚S數(shù)組在內(nèi)存中連續(xù)存放的特點(diǎn),使用memset函數(shù)來設(shè)置內(nèi)存中的值。
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
// board為二維數(shù)組,在內(nèi)存中連續(xù)存放
// 使用memset把rows*cols的空間初始化為set
memset(board, set, rows * cols * sizeof(char));
}
2.打印盤面
打印時使用2層循環(huán)來遍歷二維數(shù)組,同時把行標(biāo)和列標(biāo)都打印出來。注意打印時,只需打印中間的9×9的位置,為了區(qū)分,我用rows和cols來表示多了一圈后的行和列,用row和col表示有效的盤面大小。
void PrintBoard(char board[ROWS][COLS], int row, int col)
{
// 打印分割行
printf("********* 掃雷 *********\n");
// 打印列標(biāo),0是占位的
for (int i = 0; i <= row; ++i)
{
printf("%d ", i);
}
printf("\n");
// 打印數(shù)據(jù),每行前面打印行標(biāo)
for (int i = 1; i <= row; ++i)
{
// 打印行標(biāo)
printf("%d ", i);
// 打印數(shù)據(jù)
for (int j = 1; j <= col; ++j)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
// 打印分割行
printf("********* 掃雷 *********\n");
}
打印效果:
3.設(shè)置雷
可以使用rand函數(shù)隨機(jī)生成10個雷,注意如果該位置已經(jīng)生成雷,就重新再生成坐標(biāo),不能重復(fù)。
void SetMine(char mine[ROWS][COLS], int row, int col)
{
// 待放置的雷的個數(shù)
int count = EASY_COUNT;
// 布置雷
while (count)
{
// 產(chǎn)生隨機(jī)坐標(biāo)
int x = rand() % row + 1; // 1~row
int y = rand() % col + 1; // 1~col
// 該位置如果沒有布置雷,則放雷
if (mine[x][y] == '0')
{
mine[x][y] = '1';
--count;
}
}
}
4.排查雷
排查雷的邏輯就相對復(fù)雜點(diǎn)了,這里我分以下幾點(diǎn)來敘述。文章來源:http://www.zghlxwxcb.cn/news/detail-690938.html
- 使用GetMineCount函數(shù)來獲取周圍8個位置雷的個數(shù)。只需要把周圍8個坐標(biāo)的值加起來,由于都是字符’1’或字符’0’,還需要減去8個字符’0’,得到的就是字符’1’的個數(shù)。
- 使用如果滿足遞歸條件,就遞歸展開。展開的思路是,如果該位置沒有越界、自己不是雷、周圍沒有雷、且沒有被排查過,則遞歸展開上下左右。
- 使用count變量來保存待排查的位置的個數(shù),當(dāng)count減到0,則排雷成功。
- 玩家輸入排查的坐標(biāo)后,需要分別檢查是否合法、該位置是否被排查過、該位置是不是雷,如果檢查過后不是雷,再進(jìn)行正常的遞歸展開等。
- 如果玩家選擇標(biāo)記,若該位置未被排查過,可以切換標(biāo)記狀態(tài)。
// 獲取x,y坐標(biāo)周圍8個位置的雷的個數(shù)
static int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
// 既然只有8個坐標(biāo),直接加起來就行了
return mine[x - 1][y] // 上
+ mine[x - 1][y - 1] // 左上
+ 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] // 右上
- 8 * '0';
}
// 我也不想設(shè)計(jì)這么多參數(shù),但是似乎只能這樣了,沒想到更好的辦法
// x,y為排查的坐標(biāo)
// pcount指向count,count為玩家需要排查非雷位置的個數(shù)
// 當(dāng)count減到0時,玩家掃雷成功
static void ShowMessage(char mine[ROWS][COLS], char show[ROWS][COLS],
int row, int col, int x, int y, int* pcount)
{
// 是否展開的判斷
if (x >= 1 && x <= row && y >= 1 && y <= col // 坐標(biāo)合法性判斷
&& mine[x][y] == '0' // 該坐標(biāo)不是雷
&& (show[x][y] == '*' || show[x][y] == '!')) // 該位置沒有被排查過
{
// 獲取周圍8個坐標(biāo)雷的個數(shù)
int mineCount = GetMineCount(mine, x, y);
// 判斷周圍有沒有雷
if (mineCount == 0) // 周圍沒有雷
{
show[x][y] = ' ';
*pcount = *pcount - 1;
// 遞歸展開
ShowMessage(mine, show, row, col, x - 1, y, pcount); // 上
ShowMessage(mine, show, row, col, x + 1, y, pcount); // 下
ShowMessage(mine, show, row, col, x, y - 1, pcount); // 左
ShowMessage(mine, show, row, col, x, y + 1, pcount); // 右
}
else // 周圍有雷
{
show[x][y] = mineCount + '0';
*pcount = *pcount - 1;
}
}
}
// 若該位置未被排查,切換標(biāo)記狀態(tài)
// 若標(biāo)記,則取消標(biāo)記
// 若未標(biāo)記,則標(biāo)記
static void SignMine(char show[ROWS][COLS], int x, int y)
{
if (show[x][y] == '!')
{
// 取消標(biāo)記
show[x][y] = '*';
}
else if (show[x][y] == '*')
{
// 標(biāo)記
show[x][y] = '!';
}
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
// 坐標(biāo)
int x = 0;
int y = 0;
// 存儲玩家輸入的數(shù)據(jù)
int input = 0;
// 玩家需要排查的位置總數(shù),即非雷的位置總數(shù)
// 當(dāng)count減到0時,玩家掃雷成功
int count = row * col - EASY_COUNT;
while (count)
{
// 玩家輸入坐標(biāo)
printf("請輸入坐標(biāo):>");
scanf("%d %d", &x, &y);
// 判斷坐標(biāo)有效性
if (x < 0 || x > row || y < 0 || y > col)
{
printf("坐標(biāo)非法,請重新輸入\n");
continue;
}
// 選擇排查/標(biāo)記
while (1)
{
printf("你想要排查(1)還是標(biāo)記(0):>");
scanf("%d", &input);
// 判斷輸入有效性
if (input == 0 || input == 1)
{
break;
}
else
{
printf("選擇錯誤,請重新選擇\n");
}
}
if (input == 1)
{
// 排查
// 檢查該坐標(biāo)是否已被排查過
if (show[x][y] != '*' && show[x][y] != '!')
{
printf("該坐標(biāo)已被排查過\n");
}
else if (mine[x][y] == '0') // 判斷是否踩到雷
{
// 根據(jù)玩家排查的位置,顯示雷的信息
ShowMessage(mine, show, row, col, x, y, &count);
PrintBoard(show, row, col);
}
else // 踩到雷了
{
printf("你踩到雷了,掃雷失敗\n");
break;
}
}
else if (input == 0)
{
// 標(biāo)記
SignMine(show, x, y);
}
}
if (count == 0)
{
printf("恭喜你,掃雷成功!\n");
}
}
5.測試
// 打印菜單
void menu()
{
printf("************************\n");
printf("****** 1. play ******\n");
printf("****** 0. exit ******\n");
printf("************************\n");
}
void game()
{
// 存儲雷的位置信息
// '1' - 雷
// '0' - 非雷
char mine[ROWS][COLS] = { 0 };
// 展示給玩家的信息
// '*' - 未排查
// '1'~'9' - 該位置已被排查,且該位置周圍有雷
// 數(shù)字字符表示周圍雷的個數(shù)
// 空格 - 該位置已被排查,且該位置周圍沒有雷
// '!' - 該位置被玩家標(biāo)記,可能是雷,也可能不是雷
// '!'所在位置并沒有被排查,不算作已排查位置
char show[ROWS][COLS] = { 0 };
// 初始化盤面
InitBoard(mine, ROWS, COLS, '0'); // 初始化為全'0'
InitBoard(show, ROWS, COLS, '*'); // 初始化為全'*'
// 隨機(jī)布置雷,只在有效區(qū)域內(nèi)
SetMine(mine, ROW, COL);
// 打印盤面,只打印有效區(qū)域
//PrintBoard(mine, ROW, COL);
PrintBoard(show, ROW, COL);
// 玩家排查雷
FindMine(mine, show, ROW, COL);
}
// 測試游戲的邏輯
void test()
{
// 生成隨機(jī)數(shù)生成器起點(diǎn)
srand((unsigned int)time(NULL));
int input = 0; // 存儲玩家輸入的數(shù)據(jù)
do
{
menu();
printf("請選擇:>");
scanf("%d", &input);
switch (input)
{
case 1:
// 游戲的邏輯
game();
break;
case 0:
printf("退出游戲\n");
break;
default:
printf("選擇錯誤,重新選擇\n");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
總結(jié)
- 掃雷小游戲的實(shí)現(xiàn),需要2個二維數(shù)組,需要了解二維數(shù)組的相關(guān)知識,比如在內(nèi)存中的存儲方式。
- 初始化盤面,利用二維數(shù)組在內(nèi)存中連續(xù)存放的特點(diǎn),使用memset一步到位。
- 打印盤面以及后面的一部分邏輯,遍歷二維數(shù)組時使用2層for循環(huán),是一個常見的思路。
- 設(shè)置雷的位置采用隨機(jī)生成的方式,需要了解C語言如何生成隨機(jī)數(shù)的知識點(diǎn),我之前寫過一篇博客講解過。
- 排查雷時,需要通過反復(fù)的判斷語句,防止玩家輸入的坐標(biāo)不滿足需求。
- 尤其需要重點(diǎn)理解遞歸的思路,遞歸有限制條件,如該位置不是雷、周圍沒有雷、該位置沒有越界、該位置沒有被排查過等,同時不斷趨近于限制條件,遞歸上下左右時一定會接近邊界。
- 動手寫!
感謝大家的閱讀!文章來源地址http://www.zghlxwxcb.cn/news/detail-690938.html
到了這里,關(guān)于探秘C語言掃雷游戲?qū)崿F(xiàn)技巧的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!