文章目录
- 一.分析
- 二.游戏代码结构
- 2.1主程序
- 2.1.1main()函数
- 2.1.2menu()函数
- 2.1.3game()函数
- 2.2game.h
- 2.3game.c
- 2.3.1初始化地图Init_Map()
- 2.3.2打印地图Print_map()
- 2.3.3布置雷Set_Beng()
- 2.3.4判断是否为雷BengBeng()
- 2.3.5计算坐标周围雷的个数Count_Beng
- 三.结尾
一.分析
扫雷我想大部分应该都玩过吧,游戏规则我在这里就不做解释了。
首先我们得需要两个二维数组来存放我们所需的内容,
第一个二维数组里存放的内容是让玩家看的见的内容:
像这样,我们把这些灰色的方格用 * 代替,为了让玩家知道要在这里输入坐标,然后输入坐标后,要返回一个信息:这个坐标周围有几个雷。我们第一个数组目前就存放这些东西。
第二个数组存放的信息就是雷的位置了,通过rand函数来随机存放我们需要的雷的个数
如果我们希望格子是7 * 7的,那我们必须让地图的大小变成9 * 9的。
这是因为,我们需要有一个检查玩家输入的这个坐标周围的信息,如果地图范围只有7 * 7的话,那是不是边缘的信息在检查的时候就越界了?这样的话我们直接往外扩大一圈,然后初始化成0是不是就解决了。
而且我们两个地图的大小必须相同,这样可以起到一个映射的效果,因为玩家在第一个地图输入的坐标,该位置也可以反映到第二个地图的位置上,然后通过检查第二个地图里该坐标周围的雷的个数,然后在显示出来。
只用红框里面这部分,外面不用。
放置雷的方法:
可以将第二个数组里的内容全部初始化成0,然后随机将某些位置的0变成1,这样是为了在判断一个坐标周围位置的雷的时候,可以把他们的坐标(每个坐标要减去‘0’)加起来返回。
二.游戏代码结构
2.1主程序
#include "game.h"void menu()
{printf("************************\n");printf("****** 1.play ******\n");printf("****** 0.exit ******\n");printf("************************\n");
}void game()
{char uvis[ROWS][COLS];//让玩家看不到的地图char vis[ROWS][COLS];//能让玩家看到的地图//初始化地图Init_Map(uvis, vis);//打印可看见的地图Print_map(vis);//打印不可看见的地图//Print_map(uvis);//布置雷Set_Beng(uvis);Print_map(uvis);while (1){//输入坐标并判断是否为雷int flag = 0;flag = BengBeng(vis, uvis);if (flag == -1){printf("很遗憾,你被炸死了\n");break;}else if (flag == 1){system("cls");Print_map(vis);Print_map(uvis);}elsebreak;}
}int main()
{int input = 0;srand((unsigned int)time(NULL));do{menu();printf("请输入->");scanf("%d", &input);switch (input){case 1:system("cls");game();break;case 0:break;default:printf("输入错误,请重新输入:\n");break;}} while (input);return 0;
}
为了更好的维护写的代码,我把和实现游戏有关的代码全部写到了game.c这个文件里。现在我们先分析这个文件里的代码。
2.1.1main()函数
int main()
{int input = 0;srand((unsigned int)time(NULL));do{menu();//菜单printf("请输入->");scanf("%d", &input);switch (input){case 1:system("cls");game();break;case 0:break;default:printf("输入错误,请重新输入:\n");break;}} while (input);return 0;
}
main函数最外面的框架是do…while循环。循环里面是一个switch的选择语句。
我们通过玩家输入0,1来操作,如果输入1,则开始游戏,如果输入0就是退出游戏,如果输入其它就提示输入错误,重新输入。
srand((unsigned int)time(NULL));这行代码是为了和后面的rand函数相关联,到时候在解释,现在可以先跳过不用看。
system(“cls”);这是一个清屏的代码,主要是为了清理掉上次玩的游戏显示的界面,让画面变得更简洁。
2.1.2menu()函数
void menu()
{printf("************************\n");printf("****** 1.play ******\n");printf("****** 0.exit ******\n");printf("************************\n");
}
就是一个菜单,打印出来是这个效果:
2.1.3game()函数
void game()
{//ROWS,COLS我会在game.h头文件处解释char uvis[ROWS][COLS];//让玩家看不到的地图char vis[ROWS][COLS];//能让玩家看到的地图//初始化地图Init_Map(uvis, vis);//打印可看见的地图Print_map(vis);//打印不可看见的地图//Print_map(uvis);//布置雷Set_Beng(uvis);Print_map(uvis);while (1){//输入坐标并判断是否为雷int flag = 0;flag = BengBeng(vis, uvis);if (flag == -1){printf("很遗憾,你被炸死了\n");break;}else if (flag == 1){system("cls");Print_map(vis);Print_map(uvis);}elsebreak;}
}
游戏实现的步骤都在这里面,但是每个函数如何实现的放在了game.c文件里面。
2.2game.h
在这里我先把.h头文件里的内容给大家展示一下,以防一会在介绍.c文件里函数时,有些东西大家不知道。
//这是一会需要用到的库函数
#include <stdlib.h>
#include <stdio.h>
#include <time.h>//对一些参数做的宏定义//ROW,COL分别是行和列,是你希望地图的大小
#define ROW 9
#define COL 9//这两个是实际需要的行和列,刚在说过了,需要大一圈
#define ROWS ROW + 2
#define COLS COL + 2//这是你希望设置的炸弹的个数
#define BENG 10//下面就是一会需要用到的函数//初始化地图
void Init_Map(char uvis[ROW][COLS], char vis[ROWS][COLS]);//打印地图
void Print_map(char map[ROWS][COLS]);//布置雷
void Set_Beng(char uvis[ROWS][COLS]);//判断是否为雷
int BengBeng(char vis[ROWS][COLS], char uvis[ROWS][COLS]);
2.3game.c
#include "game.h"//初始化地图
void Init_Map(char uvis[ROW][COLS], char vis[ROWS][COLS])
{//玩家看到的地图数组内容全部初始化成*//看不到的数组里初始化为空格int i = 0;int j = 0;for (i = 0; i < ROWS; i++){for (j = 0; j < COLS; j++){vis[i][j] = '*';uvis[i][j] = '0';}}
}//打印地图
void Print_map(char map[ROWS][COLS])
{int i = 0;int j = 0;//打印列号for (j = 0; j <= COL; j++)printf("%d ", j);printf("\n");for (i = 1; i <= ROW; i++){//打印行号printf("%d ", i);for (j = 1; j <= ROW; j++){printf("%c ", map[i][j]);//map对应的那两个数组大小都是11*11,这里只是把一个数组中间9*9的拿出来//用来存放玩家输入的信息,所以存放的信息都是从1开始。}printf("\n");}
}//布置雷
void Set_Beng(char uvis[ROWS][COLS])
{int amount = BENG;while (amount){char x = rand() % ROW + 1;char y = rand() % COL + 1;if (uvis[x][y] == '0'){uvis[x][y] = '1';amount--;}}
}int Count_Beng(int x, int y, char uvis[ROWS][COLS])
{//因为外边两圈全初始化成0了,行列数只是打印出来//并没有改变数组里的内容return uvis[x + 1][y] +uvis[x - 1][y] +uvis[x][y + 1] +uvis[x][y - 1] +uvis[x - 1][y - 1] +uvis[x - 1][y + 1] +uvis[x + 1][y + 1] +uvis[x + 1][y - 1] - 8 * '0';}//判断是否为雷
int BengBeng(char vis[ROWS][COLS], char uvis[ROWS][COLS])
{while(1){//玩家输入坐标printf("请输入坐标->");int x = 0;int y = 0;scanf("%d %d", &x, &y);int count = 0;int count1 = 0;if (x >= 1 && x <= ROW && y >= 1 && y <= COL){if(uvis[x][y] != '1'){vis[x][y] = Count_Beng(x, y, uvis) + '0';system("cls");Print_map(vis);Print_map(uvis);count1++;}else{return -1;}}else{printf("输入不合法\n");}if (count1 == ROW * COL - BENG){printf("很遗憾,你没被炸死\n");return 0;}return 1;}
}
2.3.1初始化地图Init_Map()
void Init_Map(char uvis[ROW][COLS], char vis[ROWS][COLS])
{//玩家看到的地图数组内容全部初始化成*//看不到的数组里初始化为空格int i = 0;int j = 0;for (i = 0; i < ROWS; i++){for (j = 0; j < COLS; j++){vis[i][j] = '*';uvis[i][j] = '0';}}
}
用两个for循环把二维数组都遍历一遍。
2.3.2打印地图Print_map()
先给你们看下地图的样子:
void Print_map(char map[ROWS][COLS])
{int i = 0;int j = 0;//打印列号for (j = 0; j <= COL; j++)printf("%d ", j);printf("\n");for (i = 1; i <= ROW; i++){//打印行号printf("%d ", i);for (j = 1; j <= ROW; j++){printf("%c ", map[i][j]);//map对应的那两个数组大小都是11*11,这里只是把一个数组中间9*9的拿出来//用来存放玩家输入的信息,所以存放的信息都是从1开始。}printf("\n");}
}
先要在第一行把列号打印出来,也就是i=0的位置上。
后面每一行在打印之前也就是每一行的j=0的位置上把每一行的行号打印出来。
剩下的位置就是数组里的元素的位置,但是记住我们是从坐标[1,1]开始打印的。就是把11 * 11其中9 * 9位置上的信息打印出来。玩家在输入坐标的时候也是从[1,1]-[ROW,COL]。
行,列坐标是直接打印出来的,不会影响我们外面那一圈的内容。
2.3.3布置雷Set_Beng()
void Set_Beng(char uvis[ROWS][COLS])
{int amount = BENG;while (amount){char x = rand() % ROW + 1;char y = rand() % COL + 1;if (uvis[x][y] == '0'){uvis[x][y] = '1';amount--;}}
}
这里的rand函数是和刚在main函数里的srand函数一起用的。
srand()括号里面的内容里可以随便写一个值,可以把它当成种子,如果没有就默认为1,rand()每次用的时候都会检查是否调用过srand()函数,如果有就会产生一个随机值。但是这个种子如果不变的话,rand函数产生的随机值就固定了,换句话说,他就第一次随机,以后的值和第一次的一样。
这样的话我们就必须让srand函数里面的种子也是个随机值/不同值。这样就麻烦了,这不是套娃吗?所以为了满足我们的需求,我们通过time()函数来当作种子。
time():获取当前日历时间作为time_t类型的值.
我们直接把时间当成种子带进去,时间可不是固定的每分每秒都在运行。这样就会让我们每秒产生的随机值都不一样。
因为time函数的返回值是time_t类型的,而srand需要的类型是unsigned int类型的,所以我们在使用时要强制类型转换。
time函数的参数填NULL空指针就行。
但是这个随机值的取值返回太大,所有我们必须把它限制在我们想要的范围来作为炸弹的坐标,我们发现炸弹的坐标应该在1 ~ ROW,和1 ~ COL之间。而一个任意数%x,这个取值范围是0 ~ x-1.所以为了让炸弹的坐标在我们希望的范围中就给%之后的值+1.
再布置炸弹的前提下是必须这个位置上的内容是‘0’,因为我们初始化的时候就全设为’0’,这样防止重复布置雷。我们将布置成功后那个坐标的内容置成‘1’。然后没布置成功一个amount–。布置的个数是我们需要的数量就停下来。
2.3.4判断是否为雷BengBeng()
int BengBeng(char vis[ROWS][COLS], char uvis[ROWS][COLS])
{while(1){//玩家输入坐标printf("请输入坐标->");int x = 0;int y = 0;scanf("%d %d", &x, &y);int count = 0;int count1 = 0;if (x >= 1 && x <= ROW && y >= 1 && y <= COL){if(uvis[x][y] != '1'){vis[x][y] = Count_Beng(x, y, uvis) + '0';system("cls");Print_map(vis);//Print_map(uvis);count1++;}else{return -1;}}else{printf("输入不合法\n");}if (count1 == ROW * COL - BENG){printf("很遗憾,你没被炸死\n");return 0;}return 1;}
}
玩家每走一步,我们都判断一下结果。
首先判断的是玩家输入的坐标是否合法,只有合法了,才能真正的判断这个位置是否为雷,或者这个位置周围有多少雷。
如果这一个位置的坐标不是雷,我们就通过函数Count_Beng来计算这个位置周围有多少雷,然后记录并打印下来,让玩家知道。
如果这一个位置的坐标就是雷的话,之间返回-1
玩家每成功下好一步,变量count1就++,这样的话在最后判断count1是否等于ROW * COL - BENG。
ROW * COL - BENG是棋盘的所有坐标之和-炸弹的个数,也就是地图上玩家所有不是炸弹的坐标的个数,如果满了就返回0说明,游戏结束,玩家赢了。
如果既没有返回0,也没有返回-1,说明游戏还要继续,还没结束,这样我们就返回1,以供game()函数里判断。
用函数Count_Beng计算出来的结果要+‘0’。因为我们返回的是一个整型,而数组里存放的是一个字符,所以+‘0’,让数字变成字符,这样才能打印出来让玩家看到。
2.3.5计算坐标周围雷的个数Count_Beng
int Count_Beng(int x, int y, char uvis[ROWS][COLS])
{//因为外边两圈全初始化成0了,行列数只是打印出来//并没有改变数组里的内容return uvis[x + 1][y] +uvis[x - 1][y] +uvis[x][y + 1] +uvis[x][y - 1] +uvis[x - 1][y - 1] +uvis[x - 1][y + 1] +uvis[x + 1][y + 1] +uvis[x + 1][y - 1] - 8 * '0';
}
我们把该坐标周围8个位置的坐标- 8 * ‘0’加起来之间返回即可,因为我们当初布置雷的时候把雷的位置里的内容记成了’1’,如果减去’0’也就是数字1,周围有几个雷,就有几个1,全加起来就是雷的个数。
三.结尾
以上就是扫雷的全部内容了。源码在上面已经列出来了,需要的可以自取。但是为了看到自己布置雷的位置,所以把玩家不该看到的地图也打印出来了,你们如果不想要注释掉就行。