掃雷
- 🐱?👓掃雷介紹
- 🛀本文內容前提
- 👩?🦲test.c檔案代碼分析
- 🍖游戲開始,
- 🍇列印選單函式
- 🚓實作game()函式
- 🥚game.h檔案代碼分析
- 💨檔案包含
- 💲define識別符號常量
- 🏳?🌈函式宣告
- 🧷game.c檔案代碼分析
- 🛩初始化函式
- 🦼列印棋盤函式
- 🧘?♂?埋雷函式
- 👵排查雷函式
- 🤸?♂?計算坐標周圍雷個數
- 🙏🙏總結
- 🛴PS
- 🛸test.c
- 🛸game.c
- 🛸game.h
🐱?👓掃雷介紹
《掃雷》是一款大眾類的益智小游戲,于1992年發行,游戲目標是在最短的時間內根據點擊格子出現的數字找出所有非雷格子,同時避免踩雷,踩到一個雷即全盤皆輸,
掃雷網頁版界面:

掃雷VS2019編譯器實作界面:

🛀本文內容前提
- 網頁版中的掃雷是通過滑鼠點擊實作,本文是通過輸入坐標實作,所以在列印棋盤時加上了每行每列的序號,方便輸入,
- 本文省略了避免第一次被炸死、非雷處展開以及計算時間等功能,屬于掃雷基礎,實作基礎的底層邏輯,但余下的功能都不難實作,有時間興趣可以深入研究,
代碼封裝:
為了實作代碼的封裝處理,工程中給出三個不同的檔案:
- test.c檔案 - 實作開始進入界面以及簡單函式呼叫等等基礎功能,
- game.c檔案 - 實作主要進行掃雷程序的功能函式,完成test.c檔案中呼叫函式的實作,
- game.h檔案 - 存放預編譯指令、函式宣告等等,
👩?🦲test.c檔案代碼分析
test.c檔案中實作整個游戲的代碼行程,將每一個功能封裝成函式形式,但不給出函式實作邏輯,使代碼精簡,
🍖游戲開始,
游戲開始時需要注意:
要在開始列印出游戲選單,供用于選擇,
- 輸入1 - 進入游戲;
- 輸入0 - 退出游戲;
- 輸入其他 - 給出警告,并通過回圈讓玩家重新輸入,
int main()
{
int input = 0;
do
{
menu();
printf("請選擇輸入:(0/1)");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("您已退出游戲\n");
break;
default:
printf("選擇錯誤,請重試\n");
break;
}
} while (input);
return 0;
}
注意細節問題,進入game()函式、退出游戲后要break跳出switch,
這里呼叫了一個menu函式,用于列印選單,
🍇列印選單函式
由于本文介紹的是基于C語言實作的掃雷工程,只制作一個較為簡易的選單,如果有學前端的朋友可以自行搭建較為美觀的UI界面,
void menu()
{
printf("***********************\n");
printf("****** 1. play ******\n");
printf("****** 0. exit ******\n");
printf("***********************\n");
}
展示效果:

🚓實作game()函式
因為這里是test.c檔案,所以不會在此檔案中將所有的功能實作代碼展示,只展示需要實作的功能而呼叫的函式,
void game()
{
char mine[ROWS][COLS] = { 0 }; //存放雷的資訊
char show[ROWS][COLS] = { 0 }; //存放排查鋪的雷的資訊
//初始化陣列
init_board(mine, ROWS, COLS, '0'); // 0
init_board(show, ROWS, COLS, '*'); // *
//列印棋盤
display_board(show, ROW, COL);
//埋雷
set_mine(mine, ROW, COL);
//display_board(mine, ROW, COL);
//開始排雷
find_mine(mine, show, ROW, COL);
}
下面一一介紹每個函式實作的功能 :
- 初始化陣列
init_board(mine, ROWS, COLS, '0'); // 0
init_board(show, ROWS, COLS, '*'); // *
由于掃雷需要兩個棋盤:
- 一個存放雷的底層棋盤,
- 一個展示給用戶的頂層棋盤,
所以需要定義兩個二維陣串列示兩個棋盤,也就需要分別給這兩個二維陣列進行初始化,但實作初始化的函式只需要一個,所以這里呼叫兩次同一個函式,但傳的引數不同,
提前說明:
這里的ROWS和COLS表示11,ROW和COL表示9,
我們定義的棋盤是9 ? 9規模的,但是在進行排雷的時候,需要在排查坐標上下左右8個坐標進行排查,如果該坐標位于邊界,則會導致陣列越界的情況,所以需要在定義陣列時多定義兩行兩列,
其中mine陣串列示后臺存放雷的陣列, - 初始化時全設為 ’ 0 ';
show陣串列示展示給用戶的陣列, - 初始化時全設為 ’ * ';
棋盤中0表示無雷,而1表示有雷,
注意,這里的0和1是字符,
將mine陣列初始化為0表示當前棋盤全部無雷,
- 列印棋盤函式,
display_board(show, ROW, COL);
列印棋盤函式需要對棋盤做一些處理調整,讓他成為我們想要的樣子,
比如:
- 用數字表示每行每列的位置,
- 用 - - - 分割線將每行隔開,
- 用 | 分割線將每列隔開,
其中,傳參的時候只需要傳ROW和COL即可,而需要傳ROWS和COLS,因為列印棋盤展示的應該是show陣列,是給用戶看的所以不需要把我們額外加的兩行兩列展示出來,
- 設定雷函式,
set_mine(mine, ROW, COL);
這里要實作的功能是將雷埋進我們已經初始化好了的mine陣列中,因為玩家玩游戲時,使用的是9?9的棋盤,所以這里我們傳參的時候也只需要傳ROW和COL即可,
將雷 - 也就是字符 ‘ 1 ’埋進陣列也就是實作替換,后面將詳細介紹,
- 排雷函式,
find_mine(mine, show, ROW, COL);
排雷函式依然是用戶執行操作,對想要排查雷的位置輸入坐標,物件是9?9的棋盤,傳參依然只需要傳ROW和COL即可,
注意這里需要傳兩個陣列,因為需要在用戶輸入坐標后判斷改坐標在mine陣列里有沒有雷,做出相應反應后在show陣列上展示并列印在界面上,
🥚game.h檔案代碼分析
game.h檔案包含:
- 預處理指令,
- 檔案包含,
- 全域變數,
- #define識別符號常量等等,
💨檔案包含
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
解釋:
- 標準輸入輸出函式幾乎任何都會用到,所以在game.h檔案里包含,其他檔案只需要包含game.h檔案即可,
- “ stdlib.h ”在設定偽隨機值得時候會用到,后面介紹game.h檔案的時候細講,
- “ time.h ”是在設定偽隨機值時使用的時間戳函式,需要包含該檔案之后使用,
💲define識別符號常量
#define ROW 9
#define COL 9
#define ROWS ROW + 2
#define COLS COL + 2
#define EASY_COUNT 10
ROW、COL、ROWS和COLS在前面已經提到:
- ROW - COL :表示棋盤真正的大小,
- ROWS - COLS :為了不讓陣列越界而多設定了兩行兩列的陣列大小,
- 所謂EASY_COUNT就是簡單難度下的雷的個數,這里設定為10個雷,
🏳?🌈函式宣告
//初始化陣列
void init_board(char board[ROWS][COLS], int rows, int cols, char set);
//列印棋盤
void display_board(char board[ROWS][COLS], int row, int col);
//埋雷
void set_mine(char board[ROWS][COLS], int row, int col);
//開始排雷
void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
將所有的函式宣告放在game.h檔案中,其他檔案包含game.h檔案,一勞永逸,非常方便,
注意:函式的宣告需要和函式的定義一模一樣!!
🧷game.c檔案代碼分析
這個檔案中存放的代碼是真正實作掃雷程式功能的代碼,實作每個函式的實作邏輯,以完成相對應的功能,
🛩初始化函式
初始化函式就是將我們定義的兩個棋盤初始化為我們想要的樣子,
- 將mine陣列內的元素全部初始化為0,表示整個棋盤中沒有雷,將
- 將show陣列內的元素全部初始化為’ * ',表示所有位置都沒有被排查過,
只需要通過兩層回圈即可遍歷到二維陣列中的全部元素,將其元素全部改變為我們想要的即可,
void init_board(char board[ROWS][COLS], int rows, int cols, char set)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
這里的引數需要多一個set,也就是我們想要將其初始化的內容,
🦼列印棋盤函式
先展示一下棋盤的樣子:

棋盤中的‘ * ’為我們上一個函式初始化后的元素,表示全都待排查,
上面棋盤可以看出,我們需要實作的功能:
- 列印出每一行和每一列的行標和串列,
- 列印出show陣列中每個元素的內容,
- 列印出 “ — ”分割線和 ‘ | ’分割線,
void display_board(char board[ROWS][COLS], int row, int col)
{
//列印第一行的列標以及分割線
for (int i = 0; i <= row; i++)
{
printf(" %d ", i);
if (i < row)
{
printf("|");
}
}
printf("\n");
//控制分割線
for (int i = 0; i <= row; i++)
{
printf("---");
if (i < row)
{
printf("|");
}
}
printf("\n");
//列印資料及控制分隔線
for (int i = 1; i <= row; i++)
{
printf(" %d |", i);
for (int j = 1; j <= col; j++)
{
printf(" %c ", board[i][j]);
if (j < col)
{
printf("|");
}
}
printf("\n");
if (i < row)
{
for (int k = 0; k <= col; k++)
{
printf("---");
if (k < col)
{
printf("|");
}
}
}
printf("\n");
}
}
需要注意這里的列印資料部分,為了控制列印行標從1開始,所以回圈變數控制是從1開始的,后面用戶輸入控制也是從1開始的,、
只需要在控制的時候仔細一點就可以完成!
🧘?♂?埋雷函式
這個函式的功能實作:
- 在初始化好的mine棋盤上進行隨機埋雷,埋雷個數由EASY_COUNT控制,
- 亂數由srand和rand函式通過時間戳產生,需要呼叫“ stdlib.h ”和“ time.h ”頭檔案;
- 需要注意如果當前產生隨機坐標在之前已經放過雷了,就需要重新再生成隨機坐標一次,直到放進為止才算成功一次,
void set_mine(char mine[ROWS][COLS], int row, int col)
{
int cnt = EASY_COUNT;
while (cnt > 0)
{
int x = rand() % row + 1; //1-9
int y = rand() % col + 1; //1-9
if ('0' == mine[x][y])
{
mine[x][y] = '1';
cnt--;
}
}
}
在上一個函式已經提到過,用戶眼中的棋盤坐標是從1開始的,而我們控制的棋盤也是從1開始的,所以這里在對row和col取模之后需要加一,
👵排查雷函式
排查雷函式比較復雜,也是最為關鍵的一步,我們需要通過回圈控制一直排雷;
排查結果如下:
- 首先我們需要定義一個變數win,表示成功排查出雷的個數,當排查出的雷的個數等于坐標總數減去雷的個數時才算勝利,
- 如果排查的坐標是字符 ’ 1 ',也就是我們的雷,就要提醒用戶游戲失敗,并將埋雷情況展示,也就是列印埋好雷的mine陣列,
- 如果排查的坐標不是雷,則代表成功排查一次,使win變數自增1, 并且通過函式算出該坐標周圍八個位置的雷的個數,把該值放進對應坐標的show陣列里,提供給玩家,
- 如果玩家輸入的坐標不在棋盤內,則應該提醒玩家輸入坐標非法,并通過回圈使其重新輸入,依次增加代碼的健壯性,
void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
while (win < row * col - EASY_COUNT)
{
printf("請輸入您要查找的坐標:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '0')
{
int count = get_mine_count(mine, x, y);
show[x][y] = count + '0';
win++;
display_board(show, ROW, COL);
}
else
{
printf("抱歉,你被炸死了\n");
display_board(mine, ROW, COL);
break;
}
}
else
{
printf("坐標非法,請重新輸入\n");
}
}
if (win == row * col - EASY_COUNT)
{
printf("恭喜您,排雷成功,\n");
}
}
這里我們注意get_mine_count函式的回傳值是int型,但我們定義的陣列是字符型別,所以需要加上字符 ‘ 0 ’以讓其轉換為字符,如數字3轉換為字符‘ 3 ’,
🤸?♂?計算坐標周圍雷個數
該函式用于計算提供的陣列坐標上下左右8個位置的雷的個數,因為雷是用字符‘ 1 ’設定的,而無雷是用字符‘ 0 ’設定的,是需要將8個位置的坐標元素加起來再減去8個字符‘ 0 ’即可得到字符‘ 1 ’的個數,
int get_mine_count(char mine[ROWS][COLS], int x, int y)
{
return mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1] + mine[x][y - 1] + mine[x][y + 1] + mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] - 8 * '0';
}
注意這里傳過來的實參是字符坐標,所以形參也需要對應,
并且該函式是在game.c檔案內被呼叫且是在該檔案內使用的,所以只需要將該函式定義在被呼叫函式之前即可,不需要被宣告,
🙏🙏總結
以上就是本文全部內容,整個游戲代碼原始碼將會放在全文最后ps部分,如果有講的不對或者不充分的地方希望大家可以在評論區留言,如果覺得博主寫的還行可以留下各位的👍點贊👍+👀關注👀+?收藏?嗷~
🛴PS
🛸test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
printf("***********************\n");
printf("****** 1. play ******\n");
printf("****** 0. exit ******\n");
printf("***********************\n");
}
void game()
{
char mine[ROWS][COLS] = { 0 }; //存放雷的資訊
char show[ROWS][COLS] = { 0 }; //存放排查鋪的雷的資訊
//初始化陣列
init_board(mine, ROWS, COLS, '0'); // 0
init_board(show, ROWS, COLS, '*'); // *
//列印棋盤
display_board(show, ROW, COL);
//埋雷
set_mine(mine, ROW, COL);
//display_board(mine, ROW, COL);
//開始排雷
find_mine(mine, show, ROW, COL);
}
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();
printf("請選擇輸入:(0/1)");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("您已退出游戲\n");
break;
default:
printf("選擇錯誤,請重試\n");
break;
}
} while (input);
return 0;
}
🛸game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void init_board(char board[ROWS][COLS], int rows, int cols, char set)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
void display_board(char board[ROWS][COLS], int row, int col)
{
//列印第一行的列標以及分割線
for (int i = 0; i <= row; i++)
{
printf(" %d ", i);
if (i < row)
{
printf("|");
}
}
printf("\n");
//控制分割線
for (int i = 0; i <= row; i++)
{
printf("---");
if (i < row)
{
printf("|");
}
}
printf("\n");
//列印資料及控制分隔線
for (int i = 1; i <= row; i++)
{
printf(" %d |", i);
for (int j = 1; j <= col; j++)
{
printf(" %c ", board[i][j]);
if (j < col)
{
printf("|");
}
}
printf("\n");
if (i < row)
{
for (int k = 0; k <= col; k++)
{
printf("---");
if (k < col)
{
printf("|");
}
}
}
printf("\n");
}
}
void set_mine(char mine[ROWS][COLS], int row, int col)
{
int cnt = EASY_COUNT;
while (cnt > 0)
{
int x = rand() % row + 1; //1-9
int y = rand() % col + 1; //1-9
if ('0' == mine[x][y])
{
mine[x][y] = '1';
cnt--;
}
}
}
int get_mine_count(char mine[ROWS][COLS], int x, int y)
{
return mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1] + mine[x][y - 1] + mine[x][y + 1] + mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] - 8 * '0';
}
void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
while (win < row * col - EASY_COUNT)
{
printf("請輸入您要查找的坐標:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '0')
{
int count = get_mine_count(mine, x, y);
show[x][y] = count + '0';
win++;
display_board(show, ROW, COL);
}
else
{
printf("抱歉,你被炸死了\n");
display_board(mine, ROW, COL);
break;
}
}
else
{
printf("坐標非法,請重新輸入\n");
}
}
if (win == row * col - EASY_COUNT)
{
printf("恭喜您,排雷成功,\n");
}
}
🛸game.h
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ROW 9
#define COL 9
#define ROWS ROW + 2
#define COLS COL + 2
#define EASY_COUNT 10
//初始化陣列
void init_board(char board[ROWS][COLS], int rows, int cols, char set);
//列印棋盤
void display_board(char board[ROWS][COLS], int row, int col);
//埋雷
void set_mine(char board[ROWS][COLS], int row, int col);
//開始排雷
void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/304589.html
標籤:其他
上一篇:Python中的流程控制陳述句
下一篇:??酷炫漂亮回應式風景旅游網頁設計??(國慶旅游主題-HTML+CSS+JavaScript/javaweb前端大作業)
