目录
游戏介绍。
越界访问问题引出。
总体思路。
游戏的主体。
初始化函数的编写。
实现打印功能。
布置雷。
查找雷的环节
⭐计算某位置周围雷数量
⭐标记功能
释放一大片。
游戏完整代码
首先给我们一个雷盘,刚开始所有点都是不可见的,然后玩家猜测,如果周围八格没有雷就显示空白,然后周围坐标也没有雷的话展开至有雷的坐标,通过周围八格有几个雷就显示数字几,通过数字来判断周围雷的位置,还可以设置标记,如果排除所有的雷就算游戏成功。
说明:为了防止在后边出现问题,我们在前边就提出这个问题,如果玩家输入的是期盼边缘的位置,那么遍历周围八个位置那就会有越界的情况,例如
如何解决呢?可以扩展一下数组,例如要用到三乘三的数组,我们可以创建一个四乘四的数组,只用到里面的三乘三的部分。在初始化时要传入四乘四的部分,这样就不用很麻烦特殊位置特殊处理,还不用担心越界的问题。
我们在game.h里定义一下,需要用到哪个就传哪个
#define ROW 9
#define COL 9
#define COLS COL+2
#define ROWS ROW+2
⛳️我们仍然用分文件的方式编写,棋盘的话,利用数组来模拟实现,假设字符数组刚开始全部都为*,玩家选中该位置后,排查该位置周围的8个位置,如果有雷则改为相应雷的数量,然而我们一个字符数组既要是*,又要改变为雷的数量,那么雷放在哪里呢,再设立一个字符数组,命名为mine数组,里面装着雷,如果该位置为雷,就设立为字符1,在玩家输入坐标后,我们判断该位置是否为雷,不是雷就遍历周围的八个位置,得到雷的数量,在第一个字符数组中打印出来,第一个字符命名为show数组。
int main()
{
srand((unsigned int)time(NULL));
int key = 0;
do
{
menu();
printf("请选择;>");
scanf("%d", &key);
switch (key)
{
case 1:
printf("游戏开始\n");
game();
break;
case 0:break;
default:
printf("选择错误,重新选择\n");
}
} while(key);
return 0;
}
do while循环先打印菜单,菜单我们就省略了,在这里我们用输入的key来判断是否进入游戏循环(srand函数是因为后边要随机生成雷的位置。)
接下来要进行game函数的实现
⭐ 首先来创建两个数组,一个命名为show,一个命名为mine
在game.c中实现,在game.h中声明。(下边实现某些功能的函数都是如此)
代码如下
void initboard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
注意:我们要初始化的数组有两个,因为要初始化的内容不同,如果在函数里写死的话,要用两个初始化函数才能初始化完成,我们可以多传一个参数,传过来不同的数组初始化为不同的值。还要注意的是传参行和列要传ROWS和COLS,初始化大的数组。
void displayboard(char board[ROWS][COLS], int row, int col)//打印出show数组元素
{
printf("--------扫雷游戏-------\n");
int i = 0;
for (i = 1; i < row+1; i++)
{
int j = 0;
for (j = 1; j < col+1; j++)
{
printf("%c ",board[i][j]);
}
printf("\n");
}
}
注意:传入相应的数组,还有行和列,这个时候我们就不需要看外围的了,直接传ROW和COL即可。
然而想让玩家很直观的看到每个位置的行和列,而不是一个一个位置数,可以逐行逐列打印行号和列号。优化代码如下。
void displayboard(char board[ROWS][COLS], int row, int col)//打印出show数组元素
{
printf("--------扫雷游戏-------\n");
int i = 0;
for (int i = 0; i < 10; i++)
{
printf("%d ", i);
}
printf("\n");//打印列号
for (i = 1; i < row+1; i++)
{
int j = 0;
printf("%d ", i );//打印行号
for (j = 1; j < col+1; j++)
{
printf("%c ",board[i][j]);
}
printf("\n");
}
}
初步工作完成
在game函数中创建数组:
char mine[ROWS][COLS] = {0};
char show[ROWS][COLS] = {0};
在game()中初始化函数如下
initboard(mine, ROWS, COLS, '0');
initboard(show, ROWS, COLS, '*');
在game()函数中,我们将两个字符数组都打印出来
displayboard(mine, ROW, COL);
displayboard(show, ROW, COL);
运行后如图
在mine数组中,随机更改部分0变为1,需要使用到srand函数产生随机值,假设有十个雷。
//随机生成雷
void PutMine(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')
{
board[x][y] = '1';
count--;
}
}
}
这里使用了rand,前边我们已经在主函数中添加了srand((unsigned int)time(NULL));这里写成了一个死循环,只有当生成10个雷之后,才能跳出循环,注意的是,生成雷的坐标在ROW和COL空间内。雷的个数count被定义为EASY_COUNT,这里是我们在game.h中定义的雷的个数,方便我们后续的改写。
当放置雷之后,接下来就到了,玩家输入坐标,然后判断该位置是否为雷,如果为雷,就提示玩家被炸死了,且打印雷的位置给玩家看,如果该位置不是雷,那就返回周围八个坐标雷的数量,在show数组打印出来。
void findMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col)
{
int x=0, y = 0;
int flag = 0;
while(flag1&&x<=row&&y>1,y<=col)
{
if (mine[x][y] == '1')
{
printf("很遗憾,您被炸死了\n");
displayboard(mine, ROW, COL);
break;
}
else
{
if (show[x][y] != '*')
{
printf("该坐标已被查找,请重新输入");
}
//不是雷且不重复,就统计雷的个数.
else
{
system("cls");//清空屏幕
int count = GetMineCounter(mine, x, y);
show[x][y] = count + '0';
displayboard(show, ROW, COL);
flag++;
}
}
}
else
{
printf("坐标非法,请重新输入\n");
}
}
if (flag == row*col-EASY_COUNT)
{
printf("恭喜您,排雷成功\n");
displayboard(mine, ROW,COL);
}
}
判断循环是否结束,就是判断是否查找出所有的雷
①如果玩家输入的位置是雷,就提示游戏结束,同时打印出雷的数组,让玩家看具体那个位置是雷
②如果输入已经输入过的坐标,那就提示玩家该坐标已经被查找过。
如果输入的坐标不正确,就提示玩家,不找出所有雷或者不被炸死,循环就不会结束,跳出循环后。
③如果是被炸死,那么就没有找出所有的雷。如果没被炸死,flag一直加加,知道等于总数减去雷的数量,这是就输出排雷成功。
system("cls")清空屏幕,让游戏更加美观。
想要知道该坐标周围雷的数量,利用GetMineCounter函数来解决,在game.h中声明,看代码,原理很简单。
GetMineCounter(char mine[ROWS][COLS], int x, int y)
{
int i = 0;
int j = 0;
int count = 0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
if (mine[i][j] == '1')
{
count++;
}
}
}
return count;
}
返回的是雷的数量,为int型的,可以根据ASCLL,在findmine函数里返回值加上字符‘0’,就将其转化为字符类型。
打开网页版排雷小游戏,上面有标记雷的功能,而且选中一个不是雷的位置会展开很大一片
接下来实现这两个功能。
在查找一个位置后,直接弹出选项,是否需要标记某一位置,我们在findmine函数中没有踩到雷时加入以下几行代码。
void findMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col)
{
int x=0, y = 0;
int flag = 0;
while(flag1&&x<=row&&y>1,y<=col)
{
if (mine[x][y] == '1')
{
printf("很遗憾,您被炸死了\n");
displayboard(mine, ROW, COL);
break;
}
else
{
if (show[x][y] != '*')
{
printf("该坐标已被查找,请重新输入");
}
//不是雷且不重复,就统计雷的个数.
else
{
int count = GetMineCounter(mine, x, y);
show[x][y] = count + '0';
displayboard(show, ROW, COL);
flag++;
printf("是否需要标记某位置:(1/0)");
int map = 0;
scanf("%d", &map);
if (map == 1)
{
int a = 0, b = 0;
scanf("%d %d", &a, &b);
show[a][b] = '#';
}
system("cls");
displayboard(show, ROW, COL);
}
}
}
else
{
printf("坐标非法,请重新输入\n");
}
}
if (flag == row*col-EASY_COUNT)
{
printf("恭喜您,排雷成功\n");
displayboard(mine, ROW,COL);
}
}
void ExplosionSpread(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int* p)
{
int num = GetMineCounter(mine, x, y);
if (num == 0)
{
(*p)++;//++操作符的优先级比取地址符号高,所以要加括号。
show[x][y] = '@';
int i = 0;
int j = 0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
if (show[i][j] == '*')//防止已经查找过的坐标再次查找,变成死递归
{
ExplosionSpread(mine, show, row, col, i, j, p);
}
}
}
}
else
{
(*p)++;
show[x][y] = num + '0';
}
}
我们将周围没有雷的坐标改为@这样清晰明了一点(棋盘太简单了,设置为空格不好看),for循环的控制部分和遍历查找雷的数量相同,找该位置周围八个坐标,如果有满足条件的就进入函数,然后不断递归,就达到了想要的效果。如果这个位置周围有雷,那就在show数组里改变这个坐标,显示出这个坐标周围雷的个数。
小技巧:将雷的数量EASY_COUNT改为1,方便调试,在game函数里,提前把show数组提前打印出来,可以在输入猜测坐标前提前知道雷的位置。
运行后效果
game.h
#pragma once
//#pragma pack(1)
#include
#include
#include
#define EASY_COUNT 1
#define ROW 9
#define COL 9
#define COLS COL+2
#define ROWS ROW+2
void initboard(char board[ROWS][COLS], int rows,int cols,char set );
void displayboard(char board[ROWS][COLS], int row, int col);
//放置雷
void PutMine(char board[ROWS][COLS], int row, int col);
//查找雷
void findMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col);
int GetMineCounter(char mine[ROWS][COLS], int row, int col);
//大面积展开
void ExplosionSpread(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int* p);
game.c
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void initboard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
void displayboard(char board[ROWS][COLS], int row, int col)//打印出show数组元素
{
printf("--------扫雷游戏-------\n");
int i = 0;
for (int i = 0; i < 10; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i < row+1; i++)
{
int j = 0;
printf("%d ", i );
for (j = 1; j < col+1; j++)
{
printf("%c ",board[i][j]);
}
printf("\n");
}
}
//随机生成雷
void PutMine(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')
{
board[x][y] = '1';
count--;
}
}
}
//查找雷
void findMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col)
{
int x=0, y = 0;
int flag = 0;
int* p = &flag;
while(flag1&&x<=row&&y>1,y<=col)
{
if (mine[x][y] == '1')
{
printf("很遗憾,您被炸死了\n");
displayboard(mine, ROW, COL);
break;
}
else
{
if (show[x][y] != '*')
{
printf("该坐标已被查找,请重新输入");
}
//不是雷且不重复,就统计雷的个数.
else
{
ExplosionSpread(mine, show, row, col, x, y, p);
system("cls");
displayboard(show, ROW, COL);
flag++;
printf("是否需要标记某位置:(1/0)");
int map = 0;
while ((map = getchar()) != '\n');
scanf("%d", &map);
if (map == 1)
{
int a = 0, b = 0;
scanf("%d %d", &a, &b);
show[a][b] = '#';
}
system("cls");
displayboard(show, ROW, COL);
}
}
}
else
{
printf("坐标非法,请重新输入\n");
}
}
if (flag == row*col-EASY_COUNT)
{
printf("恭喜您,排雷成功\n");
displayboard(mine, ROW,COL);
}
}
//GetMineCounter(char mine[ROWS][COLS], int x, int y)
//{
// return (mine[x - 1][y] + mine[x][y - 1] + mine[x + 1][y] + mine[x + 1][y - 1] + mine[x - 1][y + 1] + mine[x - 1][y - 1] + mine[x][y + 1] + mine[x + 1][y + 1] -8*'0');
//}
GetMineCounter(char mine[ROWS][COLS], int x, int y)
{
int i = 0;
int j = 0;
int count = 0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
if (mine[i][j] == '1')
{
count++;
}
}
}
return count;
}
void ExplosionSpread(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int* p)
{
int num = GetMineCounter(mine, x, y);
if (num == 0)
{
(*p)++;//++操作符的优先级比取地址符号高,所以要加括号。
show[x][y] = '@';
int i = 0;
int j = 0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
if (show[i][j] == '*')//防止已经查找过的坐标再次查找,变成死递归
{
ExplosionSpread(mine, show, row, col, i, j, p);
}
}
}
}
else
{
(*p)++;
show[x][y] = num + '0';
}
}
test.c
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void menu()
{
printf("*****************************\n");
printf("******* 1.play *********\n");
printf("******* 2.exit *********\n");
printf("*****************************\n");
}
//在game函数里创建游戏的基本运行条件
void game()
{
//初始化键盘
char mine[ROWS][COLS] = {0};
char show[ROWS][COLS] = {0};
initboard(mine, ROWS, COLS, '0');
initboard(show, ROWS, COLS, '*');
displayboard(show, ROW, COL);
//displayboard(mine, ROW, COL);
//接下来随机生成雷
PutMine(mine, ROW, COL);
//displayboard(mine, ROWS, COLS);
// 可以观察布置雷怎么样。
displayboard(mine, ROW, COL);
//玩家查找雷
/*int a, b;
scanf("%d %d", &a, &b);*/
findMine(mine,show, ROW, COL);
}
int main()
{
srand((unsigned int)time(NULL));
int key = 0;
do
{
menu();
printf("请选择;>");
scanf("%d", &key);
switch (key)
{
case 1:
printf("游戏开始\n");
game();
break;
case 0:break;
default:
printf("选择错误,重新选择\n");
}
} while(key);
return 0;
}