一、写在前面
在学习C语言的过程中,我们不光要学会C语言的语法知识,更要学会如何将所学知识运用到我们实际项目当中去,时隔多日,继C语言实现三子棋之后,小杨又来带大家以C语言中的数组与函数两个知识点来实现另一个趣味小游戏——扫雷。
二、成品展示
1.打开游戏,出现游戏菜单:选1玩游戏,选0退出游戏,其他数字提示非法。
2.选择1,进入游戏界面
3.系统打印雷区,我峨嵋你输入坐标,选择自己排查雷的位置
4.例如我们输入2 2,游戏还未结束,提示游戏继续,选择的位置打印出2,表示在其周围有2个雷待扫除,我们继续输入要扫描的位置,直到雷区所有没有雷的位置全部被扫出来,剩下的未展示的全部为雷,游戏胜利,如果在游戏中输入的坐标位置为雷,则游戏结束,游戏失败。
三、功能模块设计
3-1逻辑设计
我们要实现扫雷游戏,在进行游戏时,我们可以使用一个结构体二维数组来表示雷区,结构体成员包括该位置是不是雷(is_mine),周围雷的个数(mine_num),该位置是否被扫除(is_check);在进行游戏的过程中,玩家每选择一个位置,系统检查该位置是否为雷,如果为雷,则游戏直接结束,游戏判定为输,否则将该位置的状态设为已检查,再打印雷区;在打印雷区中,未被扫除的位置打印' * ',已经被扫除的地方打印周围的雷的个数,然后检查雷区中不是雷的位置是否已经被全部找出,如果已经全部找出,游戏结束,判定为胜利;
3-2模块设计
在进行扫雷游戏的过程中,我们将整个游戏抽离为五个模块,进入系统,打印菜单,为打印菜单模块,每次进行游戏,在雷区随机生成指定数量的雷,并进行隐藏,为初始化雷区模块,玩家进行扫雷操作,为进行扫雷操作模块,每次扫雷结束,都要判断雷区内的所有安全区域是否全部被确定出来,为判断游戏结果模块,如果所有安全位置都被确定出来,则游戏结束,玩家获胜,否则游戏继续,每次进行扫雷操作后,都将打印当前雷区扫描情况。
四、数据结构设计
4-1 定义雷区
我们定义一个结构体来表示一个位置的属性(该位置是否为雷、周围雷的个数、该位置是否已经被扫查);
//定义结构体
typedef struct {
int is_mine;//代表这个位置是否是雷
int mine_num;//代表周围一圈雷的个数
int is_check;//表示这个位置是否已经被检查过了
}Mine;
我们再定义一个(ROW+2)*(COL+2)的数组,为什么是(ROW+2)*(COL+2)而不是ROW*COL的数组呢,因为这个数组是从mine[1][1]开始才是我们真正的雷区,在我们统计·该位置周围雷的个数时,我们时将每一个位置周围一圈全部检查一遍,但是四条边上的并不适用这个策略,因为直接检查周围的位置时,会发生数组越界,所以四条边上和四个角我们都要采取不同的统计策略,但是这样会比较麻烦,且会大大增加代码量,所以为了将每个位置的查找策略统一起来,我们直接在每条边外面加一条安全位置,也就是最外面一条边雷的数量为0,这样我们就可以直接对里面数组进行统计出每个位置周围的雷的数量,如图所示,灰色的区域代表我们实际的雷区,黄色块围起来的区域代表我们的二维数组,#代表雷,雷全在实际雷区里面
定义雷区参数
//定义雷区的规格
#define ROW 10
#define COL 10
#define mine_Num 10//定义雷的个数
4-2函数功能描述
//打印菜单
void menu();
//执行游戏代码
void game();
//初始化雷区
void Init_mine(int mine[ROW + 2][COL + 2]);
//打印雷区
void show_mine(Mine mine[ROW + 2][COL + 2]);
//玩家进行扫雷
int play(Mine mine[ROW + 2][COL + 2]);
//判断游戏是否获胜
int is_win(Mine mine[ROW + 2][COL + 2]);
4-3功能实现流程图
五、功能实现与说明
5-1文件分布
5-1文件分布
为了方便封装游戏,我们把在项目中新建一个头文件"saolei.h"用于引用函数中需要的头文件以及声明我们的自定义函数,再写一个fun.c文件进行函数的定义,最后写一个main.c文件,进行游戏的测试。func文件和main.c文件需要包含头文件"saolei.h"如图:
5-2函数定义与说明
5-2-1 主函数
逻辑实现:在进入游戏后,我们先打印菜单让用户进行选择,再根据用户的选择执行不同的操作,并且在每次游戏过后,再次打印菜单让用户进行选择,所以我们可以使用do...while+switch来实现此功能;代码如下:
int main() {
int input = 0;
do {
menu();
scanf("%d", &input);
getchar();
switch (input) {
case 1:game(); break;
case 0:printf("Exit,See you next time\n"); break;
default:printf("ERROR,Reselet->:"); break;
}
system("CLS");
} while (input);
return 0;
}
打印菜单后玩家进行选择,如果选择为1,进行游戏,为0退出游戏,其他提示选择错误重新打印菜单选择;
5-2-2 打印菜单函数
功能说明: 此函数主要用于打印菜单,然后让玩家进行选择;代码如下:
//打印菜单
void menu() {
printf("**************************************************\n");
printf("* 1.play *\n");
printf("* 0.exit *\n");
printf("**************************************************\n");
printf("select->:");
}
5-2-3 初始化雷区函数
逻辑实现:在雷区中随机生成指定数量的雷,再将所有位置的雷隐藏起来(搜索状态改为未搜索),再将每一个位置周围雷的个数保存下来,在保存雷的个数时,我们只需要检查雷区中的每个位置周围,而不需要检查雷区外面的那一条边;
参考代码:
//初始化雷区,将雷放入雷区
void Init_mine(Mine mine[ROW+2][COL+2]) {
int count = 0;
while (count < mine_Num) {
int x = rand() % ROW+1;
int y = rand() % COL+1;
if (mine[x][y].is_mine == 0) {
mine[x][y].is_mine = 1;
count++;
}
}
//将周围雷的数量存储下来
for(int i=1;i<ROW+1;i++)
for (int j = 1; j < COL+1; j++) {
mine[i][j].mine_num = (mine[i-1][j-1].is_mine + mine[i-1][j].is_mine +
mine[i-1][j+1].is_mine + mine[i][j-1].is_mine + mine[i][j+1].is_mine +
mine[i+1][j-1].is_mine + mine[i+1][j].is_mine + mine[i+1][j+1].is_mine);
}
}
5-2-4 打印雷区函数
功能说明: 该函数用于打印雷区当前扫除情况,每个位置我们都占用三个格子,我们用“|”将水平方向上相邻的两个棋格隔离开来,用“---”将上下相邻的两个棋格隔离开来。
逻辑实现:传入雷区mine的地址,然后我们检查雷区每个位置的是否被检查,如果未被检查,则打印' * ',如果已经检查,就打印该位置周围雷的个数。
参考代码:
void show_mine(Mine mine[ROW + 2][COL + 2]) {
//将已扫描位置周围雷的数量记录下来,否则打印‘*’
char board[ROW][COL] = {0};
for (int i = 0; i < ROW; i++) {
for (int j = 0; j < COL; j++) {
if (mine[i + 1][j + 1].is_check == 0)
board[i][j] = '*';
else
board[i][j] = mine[i + 1][j + 1].mine_num + 48;
}
}
//打印雷区
printf(" ");
for (int i = 1; i <= COL; i++)
printf("\033[1;31m%-4d\033[0m", i);//打印列标
printf("\n\n");//打印完列标后换行
for (int i = 0; i < ROW; i++) {
printf("\033[1;31m%-4d\033[0m", i + 1);//打印行标
for (int j = 0; j < COL; j++) {
if (j < COL - 1)
printf(" %c |", board[i][j]);
else
printf(" %c \n", board[i][j]);
}
printf(" ");
if (i < ROW - 1)
for (int j = 0; j < COL; j++) {
if (j < COL - 1)
printf("---|");
else
printf("---\n");
}
}
printf("\n");
}
我们就可以得到以下结果:
5-2-5 进行扫雷操作函数
功能说明: 玩家通过输入一个坐标,来进行扫雷,如果该位置安全,则打印雷区,并且将改位置改为周围雷的数量,函数返回1,游戏继续,如果该位置为雷,则引爆地雷,函数返回0,游戏失败,返回到主菜单页面;
逻辑实现:传入雷区mine的地址,然后玩家输入一个坐标,然后系统对该坐标进行检查,看坐标是否合法,并对雷区进行相应的处理。
参考代码:
//玩家进行扫雷
int play(Mine mine[ROW+2][COL+2]) {
int flag = 1;//设置标志,1为输入不合法,0为1输入合法
while (flag) {
int x = 0, y = 0;
printf("Select the location you want to troubleshoot please:-> ");
scanf("%d%d", &x, &y);
getchar();
if (x > 0 && x <= COL && y > 0 && y <= ROW) {
if (mine[x][y].is_mine == 1)
return 0;
else
if (mine[x][y].is_check == 0) {
mine[x][y].is_check = 1;
flag = 0;
}
else
printf("Error,Reselect please!\n");
}
else
printf("Error,Reselect please!\n");
}
return 1;
}
5-2-6判断游戏是否结束函数
功能说明: 每次排查完一个安全位置,检查雷区中所有安全位置是否都被排查出来,如果是,返回1,游戏胜利,否则返回0,游戏继续。
逻辑实现:传入雷区mine的地址,系统对雷区每个位置进行检查,如果有安全区还未被排查出来,返回0,否则返回1
参考代码:
//判断玩家是否获胜
int is_win(Mine mine[ROW + 2][COL + 2]) {
for (int i = 1; i < ROW + 1; i++)
for (int j = 1; j < COL + 1; j++)
if (mine[i][j].is_mine==0&&mine[i][j].is_check == 0)
return 0;
return 1;
}
六、源码分享
1.saolei.h
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<time.h>
//定义雷区的规格
#define ROW 10
#define COL 10
#define mine_Num 10//定义雷的个数
//定义结构体
typedef struct {
int is_mine;//代表这个位置是否是雷
int mine_num;//代表周围一圈雷的个数
int is_check;//表示这个位置是否已经被检查过了
}Mine;
//打印菜单
void menu();
//执行游戏代码
void game();
//初始化雷区
void Init_mine(int mine[ROW + 2][COL + 2]);
//打印雷区
void show_mine(Mine mine[ROW + 2][COL + 2]);
//玩家进行扫雷
int play(Mine mine[ROW + 2][COL + 2]);
//判断游戏是否获胜
int is_win(Mine mine[ROW + 2][COL + 2]);
2.fun.c
#include "saolei.h"
//打印菜单
void menu() {
printf("**************************************************\n");
printf("* 1.play *\n");
printf("* 0.exit *\n");
printf("**************************************************\n");
printf("select->:");
}
//执行游戏逻辑
void game() {
Mine mine[ROW+2][COL+2] = { {0},{0},{0} };
srand((unsigned)time(NULL));
Init_mine(mine);
show_mine(mine);
int flag = 0;
do {
flag=play(mine);
if (flag) {
show_mine(mine);
if (is_win(mine)) {
printf("YOU WIN!\nReturn to menu in 3 seconds\n");
Sleep(3000);
return;
}
printf("Continue!\n");
}
else
printf("YOU LOST!\nReturn to menu in 3 seconds\n");
} while (flag);
Sleep(3000);
}
//初始化雷区,将雷放入雷区
void Init_mine(Mine mine[ROW+2][COL+2]) {
int count = 0;
while (count < mine_Num) {
int x = rand() % ROW+1;
int y = rand() % COL+1;
if (mine[x][y].is_mine == 0) {
mine[x][y].is_mine = 1;
count++;
}
}
//将周围雷的数量存储下来
for(int i=1;i<ROW+1;i++)
for (int j = 1; j < COL+1; j++) {
mine[i][j].mine_num = (mine[i-1][j-1].is_mine + mine[i-1][j].is_mine +
mine[i-1][j+1].is_mine + mine[i][j-1].is_mine + mine[i][j+1].is_mine +
mine[i+1][j-1].is_mine + mine[i+1][j].is_mine + mine[i+1][j+1].is_mine);
}
}
void show_mine(Mine mine[ROW + 2][COL + 2]) {
//将已扫描位置周围雷的数量记录下来,否则打印‘*’
char board[ROW][COL] = {0};
for (int i = 0; i < ROW; i++) {
for (int j = 0; j < COL; j++) {
if (mine[i + 1][j + 1].is_check == 0)
board[i][j] = '*';
else
board[i][j] = mine[i + 1][j + 1].mine_num + 48;
}
}
//打印雷区
printf(" ");
for (int i = 1; i <= COL; i++)
printf("\033[1;31m%-4d\033[0m", i);//打印列标
printf("\n\n");//打印完列标后换行
for (int i = 0; i < ROW; i++) {
printf("\033[1;31m%-4d\033[0m", i + 1);
for (int j = 0; j < COL; j++) {
if (j < COL - 1)
printf(" %c |", board[i][j]);
else
printf(" %c \n", board[i][j]);
}
printf(" ");
if (i < ROW - 1)
for (int j = 0; j < COL; j++) {
if (j < COL - 1)
printf("---|");
else
printf("---\n");
}
}
printf("\n");
}
//玩家进行扫雷
int play(Mine mine[ROW+2][COL+2]) {
int flag = 1;//设置标志,1为输入不合法,0为输入合法
while (flag) {
int x = 0, y = 0;
printf("Select the location you want to troubleshoot please:-> ");
scanf("%d%d", &x, &y);
getchar();
if (x > 0 && x <= COL && y > 0 && y <= ROW) {
if (mine[x][y].is_mine == 1)
return 0;
else
if (mine[x][y].is_check == 0) {
mine[x][y].is_check = 1;
flag = 0;
}
else
printf("Error,Reselect please!\n");
}
else
printf("Error,Reselect please!\n");
}
return 1;
}
//判断玩家是否获胜
int is_win(Mine mine[ROW + 2][COL + 2]) {
for (int i = 1; i < ROW + 1; i++)
for (int j = 1; j < COL + 1; j++)
if (mine[i][j].is_mine==0&&mine[i][j].is_check == 0)
return 0;
return 1;
}
3.main.c
#include "saolei.h"
int main() {
int input = 0;
do {
menu();
scanf("%d", &input);
getchar();
switch (input) {
case 1:game(); break;
case 0:printf("Exit,See you next time\n"); break;
default:printf("ERROR,Reselet->:"); break;
}
system("CLS");
} while (input);
return 0;
}
那么本期分享就暂时到这里,欢迎各位萌新提出自己的疑惑,也欢迎各位大佬指出不足。