用C語言實現(xiàn)簡單版9*9掃雷小游戲
一.掃雷的規(guī)則
玩家通過點擊棋盤上的格子來探雷,如果此處不是雷,則會顯示出一個數(shù)字代表以此格為中心的3×3的區(qū)域的雷數(shù);而如果此處是雷,玩家被炸死,游戲結(jié)束。當(dāng)玩家點擊完所有非雷區(qū)時,玩家勝利。


二.代碼實現(xiàn)前的一些問題
1.棋盤尺寸=數(shù)組尺寸?
顯然我們會用數(shù)組來模擬棋盤。首先我們知道,當(dāng)玩家點了非雷區(qū)時,該區(qū)域要反饋附近區(qū)域雷數(shù)的信息,這就意味著程序需要對以此格為中心的3×3區(qū)域的空間進行排查。而這個過程存在一個問題:當(dāng)程序?qū)σ云灞P邊角為中心的區(qū)域進行搜查時,會出現(xiàn)數(shù)組越界的情況。
對于這個問題,有兩種解決方法:
(1)為搜查邊角寫另一種搜查函數(shù)
(2)擴大數(shù)組的尺寸
很明顯,用方法(1)解決問題會比較麻煩。所以我選擇方法(2)。方法(2)的實現(xiàn)非常簡單。例如有一個55的棋盤,我們只需要用一個7*7的數(shù)組模擬它就行了。(實際多出來的部分只會在搜查雷數(shù)的時候會用到)

同時方法(2)還有一個好處:數(shù)組下標與棋盤坐標可以一一對應(yīng),在后續(xù)代碼實現(xiàn),我們可以避免考慮數(shù)組下標與棋盤坐標的校正問題。我們都知道,數(shù)組的下標從0開始,但玩家真正需要訪問的是數(shù)組下標1-5的部分。

2.一個數(shù)組足矣?
這里我們模擬一個棋盤。我們用1代表雷,0代表非雷。

根據(jù)當(dāng)玩家點擊二行三列的格子時,這個格子會變成2,沒有問題。但當(dāng)玩家點擊二行四列的格子時,這個會變成1。這里會出現(xiàn)歧義。
而且如果只用一個數(shù)組,我們難以隱藏雷區(qū)信息,所以不妨使用兩個數(shù)組:一個用來模擬雷區(qū),一個用來模擬排查出雷的信息(提供給玩家)

對于一個5×5的棋盤,我們可以創(chuàng)建兩個char類型數(shù)組:
char mine[7][7]
char show[7][7]
三.代碼實現(xiàn)
在本程序中,我會把代碼寫在三個文件中,分別是test.c(測試游戲),game.c(游戲相關(guān)函數(shù)的定義),game.h(庫函數(shù)引用、函數(shù)聲明以及#define定義常量)
tips:若代碼塊第一行未標明所位于的文件,則按以下規(guī)定分辨:
(1).函數(shù)的使用->test.c
(2).函數(shù)的聲明->game.h
(3).函數(shù)的定義->game.c
0.初步完成頭文件
//game.h #include <stdio.h> #include <stdlib.h> #include <time.h> #define ROW 9 #define COL 9 #define ROWS ROW+2 #define COLS COL+2
如果想更改行和列,直接在此處修改即可。這只是初始版的頭文件,在后文會繼續(xù)補充。
Q:為什么有了ROW,還要定義ROWS?
A:在后續(xù)既要用到11×11的數(shù)組,也要用到9×9的數(shù)組,方便后續(xù)使用。
1.游戲的入口-菜單
大致思路:由于菜單至少打印一次,所以在主函數(shù)里用do-while循環(huán)進行菜單打印,再用switch語句根據(jù)玩家的選擇進行下一步
//test.c
#include "game.h"
void menu()
{
printf("**********************\n");
printf("****** 1.play ******\n");
printf("****** 0.exit ******\n");
printf("**********************\n");
}
void game()
{
}
int main()
{
int input = 0;
do{
menu();
printf("請選擇:>");
scanf("%d", &input);
switch (input)//根據(jù)玩家選擇進行不同操作
{
case 1:
game();
break;
case 0:
printf("退出游戲\n");
break;
default:
printf("輸入錯誤,請重新輸入\n");
break;
}
} while (input);
return 0;
}
效果如下:

2.棋盤的初始化
大致思路:創(chuàng)建兩個char類型的數(shù)組,再使用InitBoard()函數(shù)為其賦值。
這里我們發(fā)現(xiàn)給兩個數(shù)組初始化的內(nèi)容不同,可以用兩個函數(shù)來初始化。
//test.c
...
void game()
{
char mine[ROWS][COLS];
char show[ROWS][COLS];
InitBoard1(mine, ROWS, COLS);
InitBoard2(show, ROWS, COLS);
}
....
//game.c
#include "game.h"
void InitBoard1(char board[ROWS][COLS], int rows, int cols)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = '0';
}
}
}
void InitBoard2(char board[ROWS][COLS], int rows, int cols)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = '*';
}
}
}
//game.h ... void InitBoard1(char board[ROWS][COLS], int rows, int cols); void InitBoard2(char board[ROWS][COLS], int rows, int cols); ...
但其實只需要一個函數(shù)就能完成對兩個數(shù)組賦不同值的操作,我們只需要將數(shù)組需要賦的值作為InitBoard()函數(shù)的參數(shù),就能達成目的。
InitBoard(mine, ROWS, COLS,'0'); InitBoard(show, ROWS, COLS,'*');
void InitBoard(char board[ROWS][COLS], int rows, int cols,
char set)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
void InitBoard(char board[ROWS][COLS], int rows, int cols,char set);
3.展示棋盤
大致思路,將數(shù)組show的內(nèi)容打印出來呈現(xiàn)給玩家即可
void game()
{
char mine[ROWS][COLS];
char show[ROWS][COLS];
InitBoard(mine, ROWS, COLS,'0');//初始化
InitBoard(show, ROWS, COLS,'*');//初始化
DisplayBoard(show, ROW, COL);//展示棋盤
}
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
for (i = 1; i <= row; i++)
{
for (j = 1; j <= col; j++)
printf("%c ", board[i][j]);
printf("\n");
}
}
void DisplayBoard(char board[ROWS][COLS], int row, int col);
效果如下:

便于提高棋盤可讀性與美觀性,我們可以在DisplayBoard()函數(shù)里為我們的棋盤添加行數(shù)、列數(shù)、分隔線。
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
printf("------掃雷游戲------\n");
int i = 0;
int j = 0;
for (i = 0; i <= row; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ",i);
for (j = 1; j <= col; j++)
printf("%c ", board[i][j]);
printf("\n");
}
printf("------掃雷游戲------\n");
}
效果如下:

4.布置雷
大致思路:首先確定雷的個數(shù)。產(chǎn)生兩個1-9的隨機數(shù)x,y作為坐標,若mine[x][y]的值為'0',則將其改成'1',若mine[x][y]值為'1',則不修改值。將上述操作循環(huán)直至雷的個數(shù)達到需求量。在這里我們在BombSet()函數(shù)后面使用DisplayBoard()函數(shù),查看一下效果(正式游戲時需刪除DisplayBoard())。
//game.h ... #define BOMB_COUNT 10 ...
void game()
{
char mine[ROWS][COLS];
char show[ROWS][COLS];
InitBoard(mine, ROWS, COLS,'0');//初始化
InitBoard(show, ROWS, COLS,'*');//初始化
DisplayBoard(show, ROW, COL);//展示棋盤
BombSet(mine, ROW, COL, BOMB_COUNT);//布置雷
DisplayBoard(mine, ROW, COL);//展示棋盤
}
void BombSet(char board[ROWS][COLS], int row, int col, int count)
{
while (count)
{
int x = rand() % row + 1;//產(chǎn)生1-9的隨機數(shù)
int y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
void BombSet(char board[ROWS][COLS], int row, int col, int count);
但是光靠rand()函數(shù)是不能達到隨機的效果的,所以我們需要在主函數(shù)中使用srand()函數(shù)保證其隨機性。
srand((unsigned int)time(NULL));

5.掃雷
大致思路:(1).首先我們要根據(jù)玩家輸入的坐標來判斷坐標是否合法,其次踩沒踩雷。如果踩雷,游戲結(jié)束,打印數(shù)組mine(讓玩家“死的瞑目”);沒踩雷,進行下一步。
(2).我們要對以此坐標為中心的3×3區(qū)域進行排查,將雷數(shù)(char類型)賦給此坐標對應(yīng)的數(shù)組元素,再打印數(shù)組show,將信息反饋給玩家。
(3).重復(fù)上述步驟。直至當(dāng)非雷區(qū)被排查完時,玩家勝利。
void game
{
...
SearchBomb(mine, show, ROW, COL);//排雷
}
void SearchBomb(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int times = 0;//記錄下子次數(shù)
while (times+BOMB_COUNT<row*col)//當(dāng)下子次數(shù)+雷數(shù)<棋盤總格數(shù)時循環(huán)繼續(xù)
{
int x = 0;
int y = 0;
printf("請輸入您想排查的坐標:>");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)//坐標是否合法
{
if (mine[x][y] == '1')//判斷是否為雷
{
printf("游戲結(jié)束,您被炸死了。\n");
DisplayBoard(mine, ROW, COL);//打印mine
break;
}
else
{
show[x][y] = BombNum(mine, x, y);//獲取雷數(shù)
DisplayBoard(show, ROW, COL);//打印show
times++;
}
}
else
printf("坐標非法,請重新輸入!\n");
}
if (times + BOMB_COUNT == row*col)//判斷是否勝利
printf("恭喜您,您成功了!\n");
}
void SearchBomb(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
int BombNum(char board[ROWS][COLS], int x, int y)
{
int i = 0;
int j = 0;
int sum = 0;
for (i = x - 1; i <= x + 1;i++)
{
for (j = y - 1; j <= y + 1;j++)
sum += board[i][j];
}
return sum - 9 * '0'+'0';//由于雷是'1',非雷是'0',所以直接相減最后再加'0'使其成為一個字符
}
BombNum()也可以這樣定義
int BombNum(char board[ROWS][COLS], int x, int y)
{
return board[x-1][y-1]+board[x-1][y]+board[x-1][y+1]+
board[x][y-1]+board[x][y+1]+board[x+1][y-1]+board[x+1][y]+
board[x+1][y+1]-8*'0'+'0';
}
int BombNum(char board[ROWS][COLS], int x, int y);


四.空格展開的實現(xiàn)
如上的設(shè)計有一個缺點:需要一個一個點格子,過于麻煩。一般的掃雷機制會有空格展開的機制:

那么在C語言中可以怎樣實現(xiàn)它呢?此處我想到的是遞歸:如果玩家點擊的格子滿足一定條件,那么程序就會以它為中心向四面八方繼續(xù)排查,如此遞歸下去,最終就能達到我們想要的效果。
那么遞歸條件是什么呢?
(1).該格子不是雷。
(2).該格子的附近8格都不是雷。
(3).該格子沒被排查過。(防止出現(xiàn)死遞歸)
以下是代碼部分:
void SearchBomb(char mine[ROWS][COLS], char show[ROWS][COLS],
int row, int col)
{
int times = 0;//記錄排查格子的數(shù)目
int* p = ×//獲取times的地址,方便后續(xù)在另一個函數(shù)對其進行修改
while (times+BOMB_COUNT<row*col)//循環(huán)條件
{
int arr[ROW][COL] = { 0 };//用一個數(shù)組記錄一個格子是否被排查過,此處賦值為零,代表都未排查過
int x = 0;
int y = 0;
printf("請輸入您想排查的坐標:>");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= 9 && y >= 1 && y <= 9)//坐標是否合法
{
if (mine[x][y] == '1')//如果是雷
{
printf("很遺憾,您被炸死了!\n");
break;
}
else
{
Unfold(mine, show, x, y, arr, p);
DisplayBoard(show, ROW, COL);
}
}
else
printf("坐標非法,請重新輸入!\n");
}
if (times + BOMB_COUNT == row*col)//如果非雷區(qū)排查完
printf("恭喜您,勝利!\n");
}
void Unfold(char mine[ROWS][COLS] ,char show[ROWS][COLS], int x, int y,int arr[ROW][COL],int* pt)
{
if (arr[x - 1][y - 1] == 0 && x >= 1
&& x <= 9 && y >= 1 && y <= 9)//判斷此格是否被排查過以及坐標的合法性
{
(*pt)++;//排查次數(shù)+1
arr[x - 1][y - 1] = 1;//代表此格被排查
if (BombNum(mine, x, y) == '0')//如果附近八格沒有雷
{
show[x][y] = ' ';
int i = 0;
int j = 0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
Unfold(mine, show, i, j, arr, pt);//遞歸
}
}
}
else
{
show[x][y] = BombNum(mine, x, y);//有雷則計算附近雷的數(shù)目,賦值給數(shù)組show對應(yīng)的元素
}
}
}
void Unfold(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int arr[ROW][COL], int* pt);

五.源碼展示
game.h
#include <stdlib.h> #include <time.h> #define ROW 9 #define COL 9 #define ROWS ROW+2 #define COLS COL+2 #define BOMB_COUNT 10 void InitBoard(char board[ROWS][COLS], int rows, int cols, char set); void DisplayBoard(char board[ROWS][COLS], int row, int col); void BombSet(char board[ROWS][COLS], int row, int col, int count); void SearchBomb(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col); int BombNum(char board[ROWS][COLS], int x, int y); void Unfold(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int arr[ROW][COL], int* pt);
test.c
#include "game.h"
void menu()
{
printf("**********************\n");
printf("****** 1.play ******\n");
printf("****** 0.exit ******\n");
printf("**********************\n");
}
void game()
{
char mine[ROWS][COLS];
char show[ROWS][COLS];
InitBoard(mine, ROWS, COLS, '0');//初始化
InitBoard(show, ROWS, COLS, '*');//初始化
DisplayBoard(show, ROW, COL);//展示棋盤
BombSet(mine, ROW, COL, BOMB_COUNT);//布置雷
SearchBomb(mine, show, ROW, COL);//排雷
}
int main()
{
int input = 0;
do{
menu();
printf("請選擇:>");
scanf("%d", &input);
switch (input)//根據(jù)玩家選擇進行不同操作
{
case 1:
game();
break;
case 0:
printf("退出游戲\n");
break;
default:
printf("輸入錯誤,請重新輸入\n");
break;
}
} while (input);
return 0;
}
game.c
#include "game.h"
void InitBoard(char board[ROWS][COLS], int rows, int cols,
char set)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
printf("------掃雷游戲------\n");
int i = 0;
int j = 0;
for (i = 0; i <= row; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);
for (j = 1; j <= col; j++)
printf("%c ", board[i][j]);
printf("\n");
}
printf("------掃雷游戲------\n");
}
void BombSet(char board[ROWS][COLS], int row, int col, int count)
{
while (count)
{
int x = rand() % row + 1;//產(chǎn)生1-9的隨機數(shù)
int y = rand() % col + 1;
if (board[x][y] == '0')//不是雷
{
board[x][y] = '1';//布置雷
count--;
}
}
}
void SearchBomb(char mine[ROWS][COLS], char show[ROWS][COLS],
int row, int col)
{
int times = 0;//記錄排查格子的數(shù)目
int* p = ×//獲取times的地址,方便后續(xù)在另一個函數(shù)對其進行修改
while (times + BOMB_COUNT<row*col)//循環(huán)條件
{
int arr[ROW][COL] = { 0 };//用一個數(shù)組記錄一個格子是否被排查過,此處賦值為零,代表都未排查過
int x = 0;
int y = 0;
printf("請輸入您想排查的坐標:>");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= 9 && y >= 1 && y <= 9)//坐標是否合法
{
if (mine[x][y] == '1')//如果是雷
{
printf("很遺憾,您被炸死了!\n");
break;
}
else
{
Unfold(mine, show, x, y, arr, p);//展開
DisplayBoard(show, ROW, COL);//給玩家展示棋盤show
}
}
else
printf("坐標非法,請重新輸入!\n");
}
if (times + BOMB_COUNT == row*col)//如果非雷區(qū)排查完
printf("恭喜您,勝利!\n");
}
int BombNum(char board[ROWS][COLS], int x, int y)
{
return board[x - 1][y - 1] + board[x - 1][y] + board[x - 1][y + 1] +
board[x][y - 1] + board[x][y + 1] + board[x + 1][y - 1] + board[x + 1][y] +
board[x + 1][y + 1] - 8 * '0' + '0';
}
void Unfold(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int arr[ROW][COL], int* pt)
{
if (arr[x - 1][y - 1] == 0 && x >= 1
&& x <= 9 && y >= 1 && y <= 9)//判斷此格是否被排查過以及坐標的合法性
{
(*pt)++;//排查次數(shù)+1
arr[x - 1][y - 1] = 1;//代表此格被排查
if (BombNum(mine, x, y) == '0')//如果附近八格沒有雷
{
show[x][y] = ' ';
int i = 0;
int j = 0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
Unfold(mine, show, i, j, arr, pt);//遞歸
}
}
}
else
{
show[x][y] = BombNum(mine, x, y);//有雷則計算附近雷的數(shù)目,賦值給數(shù)組show對應(yīng)的元素
}
}
}
到此這篇關(guān)于用C語言實現(xiàn)簡單版9*9掃雷的文章就介紹到這了,更多相關(guān)C語言9*9掃雷小游戲內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++實現(xiàn)LeetCode(108.將有序數(shù)組轉(zhuǎn)為二叉搜索樹)
這篇文章主要介紹了C++實現(xiàn)LeetCode(108.將有序數(shù)組轉(zhuǎn)為二叉搜索樹),本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-07-07
如何利用C++實現(xiàn)mysql數(shù)據(jù)庫的連接池詳解
為了提高MySQL數(shù)據(jù)庫的訪問的瓶頸,除了在服務(wù)器端增設(shè)緩存服務(wù)器緩存常用的數(shù)據(jù)之外(如redis),還可以增加數(shù)據(jù)庫連接池,來提高MySQL Server的訪問效率,這篇文章主要給大家介紹了關(guān)于如何利用C++實現(xiàn)mysql數(shù)據(jù)庫的連接池的相關(guān)資料,需要的朋友可以參考下2021-07-07
C/C++中g(shù)etline函數(shù)案例總結(jié)
這篇文章主要介紹了C/C++中g(shù)etline函數(shù)案例總結(jié),本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-09-09

