C語(yǔ)言實(shí)現(xiàn)經(jīng)典windows游戲掃雷的示例代碼
1. 前言
大家好,我是努力學(xué)習(xí)游泳的魚。今天我們會(huì)用C語(yǔ)言實(shí)現(xiàn)一個(gè)經(jīng)典的windows小游戲:掃雷。掃雷是一款單機(jī)小游戲,我上中學(xué)時(shí)特喜歡在電腦課上玩,研究應(yīng)對(duì)各種情況的思路,每次通關(guān)最高難度的關(guān)卡都會(huì)開心好一陣?,F(xiàn)在學(xué)會(huì)了C語(yǔ)言,總算可以自己實(shí)現(xiàn)掃雷了。話不多說(shuō),咱們開始吧。
2. 準(zhǔn)備工作
我們新建一個(gè)項(xiàng)目,并創(chuàng)建三個(gè)文件:
- test.c - 負(fù)責(zé)測(cè)試游戲代碼。
- game.c - 負(fù)責(zé)游戲功能的具體實(shí)現(xiàn)。
- game.h - 負(fù)責(zé)頭文件的包含,符號(hào)的定義,函數(shù)的聲明。
在test.c和game.c里都要#include "game.h"
測(cè)試游戲時(shí),玩一把肯定不過癮,會(huì)想要再來(lái)一把,這就需要用到do while循環(huán)。
void menu()
{
printf("****************************\n");
printf("******** 1. play *******\n");
printf("******** 0. exit *******\n");
printf("****************************\n");
}
int main()
{
int input = 0;
do
{
menu();
printf("請(qǐng)選擇(1/0):>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戲\n");
break;
default:
printf("選擇錯(cuò)誤,重新選擇!\n");
break;
}
} while (input);
return 0;
}
3. 設(shè)計(jì)思路
接下來(lái)講解掃雷游戲(game函數(shù))實(shí)現(xiàn)的思路。
1.布置雷 - 10個(gè)。
2.掃雷 - 玩法如下:
輸入坐標(biāo),此時(shí)分兩種情況:
- 是雷 – 就被炸了,游戲結(jié)束。
- 不是雷 – 就告訴你這個(gè)坐標(biāo)周圍8個(gè)坐標(biāo)上總共有多少個(gè)雷。
直到把所有非雷的位置全部都找出來(lái),游戲結(jié)束,掃雷成功。
我們假設(shè)掃雷的棋盤是9×9的。我們布置雷的信息要想全部存起來(lái),就需要使用9×9的二維數(shù)組。
怎么布置雷呢?假設(shè)要布置10個(gè)雷,我們就隨機(jī)生成10個(gè)坐標(biāo),把數(shù)組的這10個(gè)位置都置成1,其余位置存儲(chǔ)0。實(shí)際排查的時(shí)候,只需要顯示周圍8個(gè)坐標(biāo)有幾個(gè)1就行了。
但是這樣設(shè)計(jì)有一個(gè)問題,假設(shè)有一個(gè)位置周圍只有1個(gè)雷,那就顯示1。我們?nèi)绾闻袛噙@個(gè)1是表示雷的1,還是顯示周圍有一個(gè)雷的1呢?這就有歧義了。
如何解決這個(gè)問題呢?我們可以再搞一個(gè)一樣大的數(shù)組。兩個(gè)數(shù)組,一個(gè)放布置好的雷的信息,另一個(gè)放排查出的雷的信息。對(duì)于后者,如果某個(gè)位置沒有排查過,就存儲(chǔ)星號(hào),以保持神秘感;如果排查過了,并且不是雷,就存儲(chǔ)周圍雷的個(gè)數(shù)。由于星號(hào)是一個(gè)字符,為了保持類型的統(tǒng)一,雷的個(gè)數(shù)也要用數(shù)字字符來(lái)存儲(chǔ)(如某位置周圍有3個(gè)雷,就存儲(chǔ)字符3),那存儲(chǔ)排查出的雷的信息的數(shù)組就是一個(gè)9×9的char類型的數(shù)組。還是為了保持類型的統(tǒng)一,存儲(chǔ)雷的信息的數(shù)組中,我們用字符0表示非雷,字符1表示雷,該數(shù)組也是一個(gè)9×9的char類型的數(shù)組。
階段總結(jié)一下:
- char mine[9][9] 負(fù)責(zé)存儲(chǔ)布置好的雷的信息,字符1表示雷,字符0表示非雷。
- char show[9][9] 負(fù)責(zé)存儲(chǔ)排查出的雷的信息,星號(hào)表示未排查,數(shù)字字符表示已排查。
但是,這樣設(shè)計(jì)仍然有問題。如果我們要排查數(shù)組邊上或角上的位置,我們需要訪問該位置周圍的8個(gè)位置,就有可能越界訪問了。
如何解決這個(gè)問題呢?我們可以把存放雷的信息的數(shù)組開大一圈,這樣訪問時(shí),排查邊上或角上的數(shù)據(jù)就不會(huì)越界了。為了保持兩個(gè)數(shù)組的一一對(duì)應(yīng),另一個(gè)數(shù)組也開大一圈。經(jīng)過以上的分析,如果實(shí)際使用的棋盤大小是9×9的,兩個(gè)數(shù)組就應(yīng)該定義為11×11的。
4. 定義數(shù)組
為了以后修改這點(diǎn)方便,我們定義幾個(gè)宏,ROW和COL為實(shí)際使用的大小(9×9),ROWS和COLS為實(shí)際定義數(shù)組的大?。?1×11)。
#define ROW 9 #define COL 9 #define ROWS (ROW + 2) #define COLS (COL + 2)
接著定義兩個(gè)數(shù)組。
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
5. 初始化
mine數(shù)組由于要存儲(chǔ)雷的信息,沒布置雷前,我們想把這個(gè)數(shù)組全部初始化成0。show數(shù)組用于存放排查出來(lái)的雷的信息,一開始應(yīng)全部初始化成星號(hào)。所以我們需要一個(gè)初始化函數(shù)。由于兩個(gè)數(shù)組初始化的內(nèi)容不一樣,所以我們?cè)O(shè)計(jì)函數(shù)時(shí),需要把初始化的內(nèi)容當(dāng)做參數(shù)傳過去。
init_board(mine, ROWS, COLS, '0'); init_board(show, ROWS, COLS, '*');
具體的實(shí)現(xiàn),只需遍歷數(shù)組就行了。
void init_board(char arr[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
for (; i < rows; ++i)
{
int j = 0;
for (; j < cols; ++j)
{
arr[i][j] = set;
}
}
}
6. 打印
對(duì)兩個(gè)數(shù)組進(jìn)行初始化后,我們想把它們打印出來(lái)看看。
初始化時(shí),我們需要初始化整個(gè)數(shù)組(11×11),但是打印以及后面的操作,我們基本只關(guān)心中間的9×9,周圍的一圈只是為了防止越界。
show_board(mine, ROW, COL); show_board(show, ROW, COL);
具體的實(shí)現(xiàn),也是遍歷數(shù)組除去最外面一圈的元素,那下標(biāo)應(yīng)從1開始,最大是row或col。
void show_board(char arr[ROWS][COLS], int row, int col)
{
int i = 0;
for (i = 1; i <= row; ++i)
{
int j = 0;
for (j = 1; j <= col; ++j)
{
printf("%c ", arr[i][j]);
}
printf("\n");
}
}
打印出來(lái)效果如下:

但是這樣打印不夠完美,我們每次還要去數(shù)某個(gè)位置是第幾行第幾列,所以最好把行標(biāo)和列標(biāo)也打印出來(lái)。
每次打印一行前,我們都打印下行號(hào)printf("%d ", i);
在所有信息打印前,我們把列標(biāo)打印出來(lái)。
for (i = 0; i <= col; ++i)
{
printf("%d ", i);
}
printf("\n");
當(dāng)然,我們可以在打印的最前和最后加上分割行。printf("------------掃雷------------\n");
下面是打印函數(shù)完整的代碼。
void show_board(char arr[ROWS][COLS], int row, int col)
{
int i = 0;
printf("------------掃雷------------\n");
for (i = 0; i <= col; ++i)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; ++i)
{
printf("%d ", i);
int j = 0;
for (j = 1; j <= col; ++j)
{
printf("%c ", arr[i][j]);
}
printf("\n");
}
printf("------------掃雷------------\n");
}
打印效果如下:

7. 布置雷
我們需要在mine數(shù)組里布置雷。set_mine(mine, ROW, COL);
假設(shè)雷的個(gè)數(shù)是EASY_COUNT。#define EASY_COUNT 10
具體的實(shí)現(xiàn),我們需要寫一個(gè)循環(huán),每次隨機(jī)生成一個(gè)坐標(biāo),如果這個(gè)位置不是雷,就在這個(gè)位置放雷,知道把所有的雷放完為止。
void set_mine(char mine[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
int x = 0;
int y = 0;
while (count)
{
x = rand() % row + 1;
y = rand() % col + 1;
if ('0' == mine[x][y])
{
mine[x][y] = '1'; // 布置雷
--count;
}
}
}不要忘記在調(diào)用rand函數(shù)之前要調(diào)用srand函數(shù),并給srand函數(shù)傳遞用time函數(shù)生成的時(shí)間戳。srand((unsigned int)time(NULL));rand函數(shù)和srand函數(shù)需要引用頭文件stdlib.h,time函數(shù)需要引用頭文件time.h。
我們可以把生成雷的信息打印出來(lái)。show_board(mine, ROW, COL);

8. 排查雷
布置好雷后,就開始排查雷。排查雷需要同時(shí)操作兩個(gè)數(shù)組。find_mine(mine, show, ROW, COL);
排查雷時(shí),可以通過一個(gè)循環(huán)反復(fù)獲取坐標(biāo),并對(duì)坐標(biāo)進(jìn)行判斷。
- 首先判斷坐標(biāo)的合法性,橫縱坐標(biāo)都必須在1到row(col)之間。
- 如果坐標(biāo)合法,再看這個(gè)坐標(biāo)有沒有排查過,如果show數(shù)組在該位置還是星號(hào),說(shuō)明沒有排查過。
- 如果坐標(biāo)還沒排查過,再看是不是雷,是雷的話游戲結(jié)束,不是雷的話就顯示該坐標(biāo)周圍有幾個(gè)雷。
循環(huán)會(huì)在兩種情況下結(jié)束,
一種是踩到雷了,直接break出去,
另一種是找到所有非雷的位置,就排雷成功了??偣灿衦ow×col個(gè)位置,一共有EASY_COUNT個(gè)雷,那非雷的位置個(gè)數(shù)就是兩者相減。判斷是否排雷成功,可以在循環(huán)條件中判斷。
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("請(qǐng)輸入要排查的坐標(biāo):>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if ('*' == show[x][y])
{
if ('1' == mine[x][y])
{
printf("很遺憾,你被炸死了\n");
show_board(mine, row, col);
break;
}
else
{
int count = get_mine_count(mine, x, y);
show[x][y] = count + '0';
show_board(show, row, col);
++win;
}
}
else
{
printf("該坐標(biāo)已被排查\n");
}
}
else
{
printf("坐標(biāo)非法,重新輸入\n");
}
}
if (row * col - EASY_COUNT == win)
{
printf("恭喜你,排雷成功\n");
show_board(mine, row, col);
}
}我們用get_mine_count函數(shù)來(lái)獲取某個(gè)位置(坐標(biāo)為x,y)周圍8個(gè)坐標(biāo)雷的個(gè)數(shù)。由于字符1的ASCII碼值減去字符0的ASCII碼值是1,而mine數(shù)組里存放的就是字符1和字符0。所以我們只需要把mine數(shù)組中,該位置周圍八個(gè)坐標(biāo)存儲(chǔ)的字符加起來(lái),再減去字符0的ASCII碼值的八倍,就能算出一共有多少個(gè)雷了。由于get_mine_count函數(shù)只在fine_mine函數(shù)中使用,所以加上static。
static int get_mine_count(char mine[ROWS][COLS], int x, int y)
{
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';
}
到此為止,整個(gè)掃雷游戲就寫完啦。
9. 完整代碼
game.h
#pragma once #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 arr[ROWS][COLS], int rows, int cols, char set); // 打印 void show_board(char arr[ROWS][COLS], int row, int col); // 布置雷 void set_mine(char mine[ROWS][COLS], int row, int col); // 排查雷 void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void init_board(char arr[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
for (; i < rows; ++i)
{
int j = 0;
for (; j < cols; ++j)
{
arr[i][j] = set;
}
}
}
void show_board(char arr[ROWS][COLS], int row, int col)
{
int i = 0;
printf("------------掃雷------------\n");
for (i = 0; i <= col; ++i)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; ++i)
{
printf("%d ", i);
int j = 0;
for (j = 1; j <= col; ++j)
{
printf("%c ", arr[i][j]);
}
printf("\n");
}
printf("------------掃雷------------\n");
}
void set_mine(char mine[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
int x = 0;
int y = 0;
while (count)
{
x = rand() % row + 1;
y = rand() % col + 1;
if ('0' == mine[x][y])
{
mine[x][y] = '1'; // 布置雷
--count;
}
}
}
static int get_mine_count(char mine[ROWS][COLS], int x, int y)
{
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';
}
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("請(qǐng)輸入要排查的坐標(biāo):>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if ('*' == show[x][y])
{
if ('1' == mine[x][y])
{
printf("很遺憾,你被炸死了\n");
show_board(mine, row, col);
break;
}
else
{
int count = get_mine_count(mine, x, y);
show[x][y] = count + '0';
show_board(show, row, col);
++win;
}
}
else
{
printf("該坐標(biāo)已被排查\n");
}
}
else
{
printf("坐標(biāo)非法,重新輸入\n");
}
}
if (row * col - EASY_COUNT == win)
{
printf("恭喜你,排雷成功\n");
show_board(mine, row, col);
}
}
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()
{
// 掃雷游戲的具體實(shí)現(xiàn)
// 存儲(chǔ)布置好的雷的信息
char mine[ROWS][COLS] = { 0 };
// 存放排查出來(lái)的雷的信息
char show[ROWS][COLS] = { 0 };
// 初始化棋盤
init_board(mine, ROWS, COLS, '0');
init_board(show, ROWS, COLS, '*');
// 打印棋盤
//show_board(mine, ROW, COL);
// 布置雷
set_mine(mine, ROW, COL);
show_board(show, ROW, COL);
// 排查雷
find_mine(mine, show, ROW, COL);
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("請(qǐng)選擇(1/0):>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戲\n");
break;
default:
printf("選擇錯(cuò)誤,重新選擇!\n");
break;
}
} while (input);
return 0;
}
以上就是C語(yǔ)言實(shí)現(xiàn)經(jīng)典windows游戲掃雷的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于C語(yǔ)言掃雷游戲的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C++ 面向?qū)ο蟪绦蛟O(shè)計(jì)--內(nèi)存分區(qū)詳解
這篇文章主要介紹了剖析C++的面向?qū)ο缶幊趟枷?C++的面向?qū)ο筇匦允瞧鋵?duì)C語(yǔ)言的重要拓展之處,需要的朋友可以參考下,希望能夠給你帶來(lái)幫助2021-08-08
C++編程析構(gòu)函數(shù)拷貝構(gòu)造函數(shù)使用示例詳解
這篇文章主要為大家介紹了C++編程構(gòu)造函數(shù)中析構(gòu)函數(shù)及拷貝構(gòu)造函數(shù)的使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助2021-11-11
C語(yǔ)言數(shù)據(jù)結(jié)構(gòu)之堆排序的優(yōu)化算法
堆排序Heap?Sort就是利用堆進(jìn)行排序的方法,下面這篇文章主要給大家介紹了關(guān)于C語(yǔ)言數(shù)據(jù)結(jié)構(gòu)之堆排序的優(yōu)化算法的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-04-04
C/C++實(shí)現(xiàn)快速排序(兩種方式)圖文詳解
這篇文章主要介紹了C/C++實(shí)現(xiàn)快速排序的方法,這幾天在找工作,被問到快速排序,結(jié)果想不出來(lái)快速排序怎么弄的;回來(lái)搜索了一下,現(xiàn)在記錄下來(lái),方便以后查看2021-08-08

