大家好,即三子棋后一款游戏——扫雷,这个游戏就比三子棋可玩性高很多了,相对应的有个算法会比较难想到,但是跟三子棋的游戏框架很像。废话少说,接下来我们就开始分析扫雷如何实现吧。
目录
事先准备和用户交互画面和三子棋是相同的,我就不复制粘贴了,可以去看我的上一篇博客,这里直接说这两步之后的程序。
一、棋盘的初始化
我们这里用使用两个棋盘,一个是我们安放地雷的棋盘,一个是用户用来排雷的棋盘,这样分开设置有利于我们设计游戏
我们要将两个棋盘设置为11*11,但只显示1-10行、列的数据,方便我们排查雷。初始化代码如下:
char mine[ROW][COL];
char show[ROW][COL];
//初始化棋盘mine数组全初始化0,shou数组全初始化为*
inti_board(mine, '0');
inti_board(show, '*');
我们将初始化函数设置为,传什么就赋值什么,这个还是比较好实现的,11*11的数组全部附上就行了
//数组初始化
void inti_board(char board[ROW][COL], char c)
{
int i = 0;
int j = 0;
for (i = 0; i < ROW; i++)
{
for (j = 0; j < COL; j++)
{
board[i][j] = c;
}
}
}
二、棋盘的打印
我这里打印的棋盘是我设计过的,容易对应坐标,打印其实没什么,主要是个人意愿
但是这个打印要做到传入show数组则打印用户界面,传入mine数组则打印开发页面
这里我放上我的打印函数,因为这个不是扫雷的重点。
void print_board(char board[ROW][COL])
{
printf("***********************************************\n\n");
int i = 0;
int j = 0;
for (i = 1; i <= row; i++)
{
if (i == 1)
printf(" ");
printf(" %d ", i);
}
printf("\n");
printf(" ");
for (i = 1; i <= row; i++)
{
if (i<row)
printf("----");
if (i == row)
printf("---");
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf(" %d ", i);
for (j = 1; j <=col; j++)
{
printf("|");
printf(" %c ", board[i][j]);
if (j == col)printf("|");
}
printf("\n");
printf(" ");
for (j = 1; j <= col; j++)
{
if (j<col)
printf("----");
if (j == col)
printf("---");
}
printf("\n");
}
printf("\n");
printf("***********************************************\n");
printf("\n");
}
三、存放地雷。
这个跟三子棋电脑走子是差不多的,也是用rand函数生成10对坐标来插入雷放到mine数组中,也没什么太大的难度
//安置雷
void set_bomb(char board[ROW][COL])
{
int x;
int y;
int count = 10;//count是安放雷的数量
while (count)
{
x = rand() % col;
y = rand() % row;
if (x >= 1 && x <= 9 && y >= 1 && y <= 9 && board[x][y] == '0')
{//位子没有被占用,而且是显示的棋盘内
count --;
board[x][y] = '1'; //放1代表是雷
}
}
}
四、查找雷
查找雷是我们今天的重点,我们要确定好好方向和流程:
1、用户输入坐标(设置循环判断坐标的合法性)
2、对坐标进行判断
2.1检查这个坐标的mine数组是不是雷,是雷就打印一下棋盘然后直接return;游戏结束
2.2不是雷,我们进行判断周围是不是雷,并将检查到的雷的数量进行返回,显示在show数组用户输入的位子上(并进行递归展开),游戏继续。
(这里给大家一个小思路,如果想对雷进行标记,可以输入0 0,输入0 0就进入标记页面,再输入要标记坐标,将show数组上对应的坐标进行更改就行了)
1.1和2.1还是很简单的,我们重点来实现2.2
排查不是雷坐标周围的雷数
我们将用户输入的坐标传入到排查统计雷的函数中,思路是:
因为我们将雷设置为1,非雷设置为0,所以我们将9个坐标的值全部相加,再减去8个‘0’的值,就可以算出要返回的值了,因为0的ascii码值为48,1的ascii码值为49,我们只要返回48,就是返回’0‘,表示周围一圈也是无雷,返回49,就是返回'1',表示1个雷,以此类推。并可以把返回的值放到show数组对应的坐标上,这样下次显示的时候也就显示出来了。
代码实现如下:
int count_mine(char mine[ROW][COL], int x, int y)
{
int sum = 0;
for (int i = -1; i <= 1; i++)
for (int j = -1; j <= 1; j++)
sum += mine[x - i][y - j];
return sum - 8 * 48;
}
递归展开
一圈都不是雷
这时我们就要思考,如果周围一圈全不是雷,那是不是应该要展开一片呢,我们可以怎么设定这个一圈都展开的递归公式呢?
先来看看这个递归应该如何设计:
return的地方都是需要用if判断的,而这些子坐标是什么意思呢,接下来看一下的这个图,这8个子坐标就代表着母坐标向8个方向展开。
其实就是他周围的8个小方块啦,坐标也是很好写的,代码如下:
//递归展开
void spread_board(char mine[ROW][COL], char show[ROW][COL], int x, int y)
{ //排查一圈,发现有雷,将show上的位子设为雷的个数
int i = count_mine(mine, x, y);
if (i != '0')
{
show[x][y] = i;
return;
}
//排查一圈,找到起点空格,停止.
if (show[x][y] == ' ')
{
return;
}
//超出边界
if (x <= 0 || x > row || y <= 0 || y > col)
return;
//排查一圈,发现没有雷,将show对应的位子设为空格
show[x][y] = ' ';
/*print_board(show);*/
spread_board(mine, show, x + 1, y);
spread_board(mine, show, x + 1, y - 1);
spread_board(mine, show, x + 1, y + 1);
spread_board(mine, show, x, y - 1);
spread_board(mine, show, x, y + 1);
spread_board(mine, show, x - 1, y - 1);
spread_board(mine, show, x - 1, y + 1);
spread_board(mine, show, x - 1, y);
}
接下来是输入排雷函数的代码:
//查找雷
void lookup_bomb(char show[ROW][COL], char mine[ROW][COL])
{
int x;
int y;
char count=0;
while (1)
{
printf("请输入排查的坐标或标记:\n");
scanf("%d %d", &x, &y);
while (1)
{
if (x == y && x == 0)
{
printf("请输入坐标来标记:\n");
scanf("%d %d", &x, &y);
show[x][y] = '@';
print_board(show);
printf("请输入排查的坐标或标记:\n");
scanf("%d %d", &x, &y);
}
if (0 != y && x != 0)
break;
}
if (x >= 1 && x <= 9 && y >= 1 && y <= 9)
{
if (mine[x][y] == '1')
{
system("cls");
printf("sorry,您被炸死了\n雷的分布如下:\n");
print_board(mine);
sign = 2;
return;
}
else
{
//查找该坐标周围的有多少雷
count = count_mine(mine, x, y);
if (count == '0')
{//统计周围没有雷,开始递归展开
spread_board(mine,show, x, y);
}
if (count != '0')
show[x][y] = count;
break ;
}
}
else
{
printf("输入有误,请重新输入:\n");
}
}
}
五、鉴定排雷状况,结束排雷
最后就剩下一个收尾工作了,我的想法和大家的不同,我是检查show数组上星号(*)和标记号(@)的总和是不是等于10个来停止的。因为,玩家要是将shou数组上81个符号检查到10个,而且一直没踩雷跳出,就是玩家排到了最后的10个,就可以安全退出恭喜玩家了。而我又设置了一个全局变量sign,在玩家踩雷后就将sign改为0,这样game函数中如果判断sign为0,直接就中断了游戏,用户踩雷了。这就是我的想法排雷结束思路,当然我的想法不是最好的,甚至可能有BUG,大家可以对我的代码采取适当的借鉴。,代码如下:
//检查游戏结束
int inspect_game(char show[ROW][COL])
{
int i = 0;
int j = 0;
int count=0;
for (i=1;i<=row;i++)
for (j = 1; j <= col; j++)
{
if (show[i][j] == '*'||show[i][j]=='@')
{
count++;
}
}
if (count <= 10)
return 1;
return 0;
}
这样,扫雷游戏游戏就设计完了,会发现游戏间总体思路是大同小异的,扫雷也就递归展开那块有点难想到,但是多练习一下游戏设计,也有利于理清编程思路,增强编程能力。
下一篇博客我会放下3个文件的全部代码。
最后,看到这里,留个赞呗~~