目录
扫雷
整体框架
game.h对要用到的函数进行声明
test.c游戏运行测试框架
game.c游戏逻辑的实现
---存放棋盘
---初始化棋盘
---显示棋盘
---布置雷
---排查雷
---判断游戏是否结束
思考
1.实现展开一片
2.实现标记功能:
总结:
代码连接:https://gitee.com/zhy156/c-code-practice-warehouse.git
我还是将扫雷游戏分为两个模块1.整体框架2.游戏逻辑实现
并将它们分别写在了3个文件当中
game.h :对要用到的函数进行声明
game.c :对游戏逻辑的实现
test.c :用来测试游戏的运行
--显示菜单--玩家选择--游戏进行/退出
基本和上一篇三子棋游戏一样,这里不再赘述
接下来给出游戏进行的整体框架:
1.给出数组存放棋盘(这里我们创建两个棋盘:布置雷,排查雷)
2.初始化棋盘 (’1‘:雷,’0‘:非雷, ' * '::未操作)
3.布置雷
---随机生成10个雷的坐标
---判断坐标的合法性(这里直接限制坐标生成的范围免去判断)
--判断此位置是否被占用(是,重新生成坐标)
--将棋盘元素替换为雷的元素(将布置雷棋盘中的’0‘换为’1‘)4.排查雷
---玩家输入坐标
---判断坐标的合法性(非法,提醒玩家重新输入)
--判断该位置是否被占用(是,提醒玩家重新输入)
--判断该位置是否为雷(是,游戏结束)
--不是雷,给出这个雷周围8个元素雷的个数(排查)
5.判断游戏是否结束
我们引如一个计数器:win,每排查一个坐标,win++;直到win==显示棋盘大小-布置雷的个数
循环结束,
---判断win==显示棋盘大小-布置雷的个数
--相等:打印排雷成功
我们创建两个数组来存放棋盘,一个布置雷,一个给玩家排查
--这两个数组大小对应
--我们声明的数组会比棋盘多2行和2列
原因:对雷排查的时候,若该坐标不是雷,需要我们返回它周围8个元素雷的个数
当排查的坐标是边界的时候会访问越界
为了防止数组越界的问题:将其拓宽
因此创建的时候:
代码如下:
要求:
将mine数组全部初始化为’ 0 ‘
将show数组全部初始化为’ * ‘
实现:
通过多重循环遍历数组里所有元素,将其元素替换
我们设置一个函数,将要初始化的数组,初始化的字符传递给它
代码如下:
//初始化棋盘
void Init_board(char board[ROWS][COLS], int rows, int cols, char m)
{
int i, j;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = m;
}
}
}
使用多重循环遍历显示棋盘的所有元素并打印(注意要显示的是9*9的)
优化棋盘:打印出棋盘行数与列数
代码:
void Display_board(char board[ROWS][COLS], int row, int col)
{
int i, j;
for (i = 0; i < 10; 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");
}
1.随机生成10个坐标:
--通过使用时间戳来设置随机起点---srand((unsigned int)time(NULL))
--调用rand()函数来生成随机数
--将生成的随机数放入坐标X,Y中
注意生成坐标的合法性和避免重复放雷
--X=rand()%row+1(产生在棋盘范围内的坐标)
假设显示棋盘是9*9,row=9,生成的随机数%9会产生0-8的数字,+1就变(0,9)
Y同理
2.使用while循环再引入一个计数器:count:
--每放置完一个雷,count--;
--直到所有雷被放置完,循环结束
代码:
//放置雷
void Set_mine(char board[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
while (count)
{
int x = rand() % row + 1;//生成坐标在显示棋盘范围内
int y = rand() % col + 1;
if (board[x][y] == '0')//如果元素为’0‘才能被替换,也避免了重复放雷
{
board[x][y] = '1';
count--;
}
}
}
1.玩家排查雷:
----玩家输入坐标 创建x,y来接收坐标
2.判断坐标的合法性:使用条件语句与操作符if((x>.=1&&x<=row)&&(y>=1&&y<=col))
3.判断坐标是否为雷:if(mine[x][y]==' 1 ') 是游戏结束
4.若不为雷,判断该坐标是否被已排查1:if(show[x][y]!=' * ') 是,提醒玩家重新输入
5.以上条件都满足:将对应坐标的元素替换并标上它周围雷的个数:show[x][y]=count + '0'
引入int变量count来统计,该元素周围8个元素雷的个数,count=Get_count();(写一个函数来计算)
Get_count:
---使用多重循环遍历除该坐标的元素
注意:由于我们创建的是字符数组:字符’ 0 ‘ 与数字 0 是不一样的,它们的ASCII码值相差32
我们的目的是在字符数组中显示该元素周围雷的个数(显示的是字符,但也需要统计雷的个数)
因此要对它进行转换:
在Get_count函数中我们将mine数组与该点坐标传递过去,遍历该点周围元素并将其减去‘ 0 ’
,在将这个结果都加起来就能得到该点雷的数量
再将这个结果:count +‘ 0 ’ 转换为字符并与show数组中对应的元素替换即可
讲解:一个数字 加上‘ 0 ’能转换为对应的字符数字或者该数字+32也能转换
因为在ASCII码表中数字0与字符' 0 '相差 32 也相差‘ 0 ’
假设我们选中了圆圈的元素,要统计周围8个元素,使用遍历:假设该点坐标X,Y
该点的上一行:X-1 该点左边的一列:Y-1
该点的下一行:X+1 该点的右边一列: Y+1
遍历的范围就出来了:i:(X-1,X+1)
j:(Y-1,Y+1)
注意当i==X&&j==Y的时候是该点坐标,我们不需要统计,当满足条件的时候使用continue跳过即可
代码:
//得到该元素周围雷的个数
int Get_count(char mine[ROWS][COLS], int x, int y)
{
int i, j;
int ret = 0;
for (i = x - 1; i <= x + 1; i++)//遍历该点周围的元素
{
for (j = y - 1; j <= y + 1; j++)
{
if ((i == x) && (j == y))//遍历到自己就跳过
{
continue;
}
ret += mine[i][j] - '0';//计算周围雷的个数
}
}
return ret;
}
//排查雷
void Find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int win = 0;//统计排雷的次数
int count = 0;
int x, y;
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] == '1')
{
printf("很遗憾,你被炸死了\n");
win = 100;
Display_board(mine, ROW, COL);
break;//选中雷,退出循环
}
else if (show[x][y] != '*')
{
printf("该坐标你已选择,请重新选择\n");
}
else
{
count = Get_count(mine, x, y);//得到周围雷的个数
show[x][y] = count + '0';//将数字转换为字符数字并在对应的位置替换元素
Display_board(show, ROW, COL);//显示棋盘
win++;
}
}
else
{
printf("非法输入,请重新输入");
}
}
if (win == row * col - EASY_COUNT)
{
printf("恭喜你,排雷成功\n");
}
}
我们引如一个计数器:win,每排查一个坐标,win++;(与排查雷写在一起构成循环)
直到win==显示棋盘大小-布置雷的个数
循环结束,
---判断win==显示棋盘大小-布置雷的个数
--相等:打印排雷成功
代码能否优化?
1.我想要实现像网页版的扫雷,选取一个坐标后若周围没有雷就展开一片,怎么实现?
2.我像要实现标记雷的功能,该怎么实现?
---思路:通过递归实现
展开一片 的要求如下:
递归函数:
---1.选取的坐标周围没有雷
---2.没有雷我们show数组中对应的元素替换为‘ ’
win++
---3.判断坐标是否合法(因为要递归,将该点周围元素传进来时可能会造成数组越界)
---4.判断该坐标是否已经排查(减小重复计算)
---若满足以上条件,将该坐标的周围元素在传递给函数,再去判断
---5.若选取的坐标周围有雷:我们就将该元素周围雷的数量返回并替换show数组中对应的元素(和初阶排查雷一样)
win++
这样:如果选取的元素周围没有雷:执行234,满足条件,将该元素周围坐标传入函数
若满足条件1继续执行234,若又满足上面的限制条件,又将该元素周围坐标传入函数
直到传入的坐标周围有雷:停止递归,返回该雷的个数。
这样就能达到展开一片的效果
这里引入了win,每执行一步替换show数组操作,win++,用来判断是否终止循环
代码:
int Expand_blank(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int win)
{
int count = Get_count(mine, x, y);
int i, j;
if ((x >= 1 && x <= ROW) && (y >= 1 && y <= COL)) //限制坐标范围
{
if (count == 0) //判断这个点周围是否有雷,没有就将show对应的元素设为’‘,有的话给上数字
{
show[x][y] = ' ';
win++;
//将这个坐标周围的元素传递给Expand_blank函数,看其周围有没有雷,没有将show中对应的元素变为’‘,有的话给上数字
for (i = x - 1; i <= x + 1; i++) //这样就形成了递归,可以达到拓展一片的效果
{
for (j = y - 1; j <= y + 1; j++)
{
if ((i == x) && (j == y))
{
continue;//遍历到自己就跳过
}
if (show[i][j] == '*') //排除已经检查过的元素
{
Expand_blank(mine, show, i, j, win);
}
}
}
}
else
{
show[x][y] = count + '0';
win++;
}
}
return win;
}
//排查雷
void Find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int win = 0;//统计排雷的次数
int x = 0;
int y = 0;
int count = 0;//存放该坐标周围雷的个数
while (win < row * col - EASY_COUNT)//排雷次数达到要求退出循环
{
count = 0;
printf("请输入你要排查的坐标:");
scanf("%d%d", &x, &y);
if ((x >= 1 && x <= row) && (y >= 1 && y <= col))
{
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死了\n");
Display_board(mine, ROW, COL);
break;//选中雷,退出循环
}
else if (show[x][y] != '*')
{
printf("该坐标你已选择,请重新选择\n");
}
else
{
win = Expand_blank(mine, show, x, y, win);
Display_board(show, ROW, COL);//展开一片后显示棋盘
}
}
else
{
printf("非法输入,请重新输入\n");
}
}
if (win == row * col - EASY_COUNT)
{
printf("恭喜你,排雷成功\n");
}
}
这里我在排查雷里去返回该元素周围雷的个数时,使用递归函数,来达到展开一片的效果
将其与判断是否结束循环写在一起:引入计数器:win,每当show数组中有一个元素被替换,win++,直到win==显示棋盘大小-雷的个数;
因此设置了 int Expand_blank(mine,show,x,y,win)函数,让它返回win,来对循环终止进行判断
效果展示:
思路:玩家进入游戏,显示棋盘后,给玩家提供选择:选1:排查雷,选2:标记雷
实现:
----在循环中,创建input变量,接收玩家输入的数字,
----使用switch()语句,根据玩家的输入进行不同的选择
case 1: 排查雷 break;
case 2:标记雷 break;
defaut:提醒玩家正确输入;break;
这里还需要补充一个标记函数
代码:
//标记雷
void sign_mine(char show[ROWS][COLS], int row, int col)
{
int x, y;
printf("请输入你要标记雷的坐标:");
while (1)
{
scanf("%d%d", &x, &y);
if ((x >= 1 && x <= row) && (y >= 1 && y <= col))
{
show[x][y] = 'M';
Display_board(show, ROW, COL);
break;
}
else
{
printf("非法输入,请重新输入:");
}
}
}
接下来就要思考如将它放在循环里,并在选择排查雷的时候若选到雷了,游戏直接结束
---同样进行”判断游戏是否结束“:引入计数器win,当win==显示棋盘大小-雷数量的时候,游戏胜利,
---这里标记雷是不影响win的加减的,因为只是标记,并没有去检查show数组中的对应元素
---这里注意:当在排查雷函数里选中雷过后,我们直接将win赋为>显示棋盘大小-雷个数,直接终止循环,并打印”很遗憾,你踩到地雷了“
接下类进行判断:如果win==显示棋盘大小-雷数量才打印游戏胜利
这里需要修改以下排查雷函数:让其为int类型,并返回win的大小来判断是否结束循环
//找到雷
int get_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int win)
{
int x = 0;
int y = 0;
while (1)
{
printf("请输入你要排查的坐标:");
scanf("%d%d", &x, &y);
if ((x >= 1 && x <= row) && (y >= 1 && y <= col))
{
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死了\n");
win = 100;
Display_board(mine, ROW, COL);
break;//选中雷,退出循环
}
else if (show[x][y] != '*')
{
printf("该坐标你已选择,请重新选择\n");
}
else
{
win = Expand_blank(mine, show, x, y, win);
Display_board(show, ROW, COL);//展开一片后显示棋盘
break;
}
}
else
{
printf("非法输入,请重新输入\n");
}
}
return win;
}
整体框架:
执行效果展示:
C语言简易实现扫雷所用到的知识:
----数组:用来存放棋盘
----循环:用来遍历数组中的元素并显示
----条件语句和操作符: 用来增加限制条件
----递归 ;用来将大问题拆分为类似的小问题并解决 (实现展开一片的功能)
##所用知识罗列:
1.数组:
----二维数组
----#define 定义常量来规定数组大小
2.循环
----for():用来遍历数组里的所有元素并展示
----while() : 搭配限制条件来实现埋雷,结束循环
----do_while():让程序至少运行一次,给玩家提供选择
3.条件语句与操作符
----与循环搭配使用起到:排除已筛查元素,判断坐标的合法性,判断坐标是否被占用,判断输入是否合法
----选择语句switch搭配循环:根据玩家的输入,进行不同结果反映(排查雷或者标记雷)
4.递归:搭配循环语句,条件语句来起到重复调用自己,并给上递归结束的限制条件,来实现递归