目录
一、游戏介绍
二、游戏设计思路
三、游戏的交互界面
?1、创建游戏菜单
2、创建玩家选择面板
四、雷盘的定义
五、初始化雷盘和打印雷盘?
1、初始化雷盘代码(在game.c文件中写):
2 、打印雷盘代码
?六、布置雷与排查雷
1、布置雷?
2、排查雷?
七、递归展开一片
?八、标记雷的位置
九、游戏测试
十、代码展示
(1)test,c:
(2)game.h
(3)game.c
一、游戏介绍
? ?扫雷游戏大家应该都玩过,上图就是一个网页版的扫雷游戏,它的规则就是让玩家选择一个方格,若这方格没有地雷?,那么方格会显示与它相邻八个方格中雷的个数,点开其中一个小方格之后,数字是几,就说明它周围的八个方位就有几个雷,玩家获胜条件就是把除了有地雷的方格外的其他方格都成功翻开,否则,只要触碰到地雷,游戏就失败了,今天我写的代码就是能够实现这个游戏。
二、游戏设计思路
? ?之前我的文章介绍过三字棋的实现过程,其实两者都有相似之处,也是分成三个文件来编写的:
test.c:游戏逻辑的测试,包含游戏菜单的打印,游戏设计的基本逻辑的展示。
game.c:游戏功能的具体实现,这部分是整个游戏的核心代码,一般不会展示给用户。
game.h:相关头文件的包含、符号的声明以及函数的声明。
? ?我的设计思路是先创建游戏的交互界面,里面包含游戏的菜单与玩家选择面板,这个跟我上篇三字棋的差不多,重点就是扫雷游戏的实现,这个就看我文章,容我一一道来。
三、游戏的交互界面
?1、创建游戏菜单
void menu() {
printf("**********菜单********\n");
printf("******1->扫雷游戏*****\n");
printf("******2->退出游戏*****\n");
}
2、创建玩家选择面板
void test() {
int input = 0;
do {
srand((unsigned int)time(NULL));//表示随机数生成函数通过时间戳来生成随机数
scanf("%d", &input);
if (input == 1)
printf("游戏开始\n");
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n:");
break;
default:
printf("输入错误,请重新输入\n:");
break;
}
} while (input);
}
int main() {
menu();
test();
return 0;
}
代码说明:在test()函数当中,我运用了do.....while语句来实现游戏的进入与结束,保证游戏界面至少进入一次,用swich分支来实现如果输入0,游戏结束,输入1,输入其他数字,就会提醒输入错误,请重新输入,游戏开始,游戏内容由game()函数实现游戏逻辑。
四、雷盘的定义
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
char mine[ROWS][COLS] = { 0 };//存放布置好的雷的信息
char show[ROWS][COLS] = { 0 };//存放排查出的雷的信息
? ? ? ?与三字棋不同的是,三字棋可以用一个棋盘就可以搞定玩家与电脑下棋,而扫雷游戏不同,它需要设置两个雷盘,一个存放布置好雷的信息,一个存放排查出雷的信息。这样就可以避免二者的相互干扰或者相互覆盖。同时,用宏定义来定义雷盘的大小以及雷的个数,宏定义在前面的三子棋游戏中也有介绍。在这里使用宏常量主要是为了方便程序的修改,增加了程序的可塑性。?就像我这里通过宏定义将行列通设定为9,也就是9*9的雷盘。假如后面我想要玩12*12的雷盘,我只需要将宏定义中的9改为12即可,这样就省去了在程序中大量修改的精力,使代码可塑性更高。
? ? ? ?另外,大家是不是也有一些疑问,为什么我这里会定义两个不同的ROW与COL,这主要为后面的排雷做铺垫。
?如图:当我们排查2位置时,如果2处不是雷,那么我们就会依次检查2周围8个坐标是否有地雷,如果有,就会把地雷的数量显示在1位置处;但是当我们排查1位置时,我们发现,?数组排查雷时会发生越界,所以为了避免数组越界,我们就需要增加一系列限制条件,这样做无疑是比较麻烦的,所以有的大佬就想出了这样一种办法:在定义数组长度时我们直接在上下左右四个方向各多给一行的空间,因此,我们创建11*11 的棋盘这样就很好的解决了这个问题。不得不说,这种方法实在巧妙!
五、初始化雷盘和打印雷盘?
1、初始化雷盘代码(在game.c文件中写):
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
2 、打印雷盘代码
void PrintBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
printf("------扫雷游戏------\n");
for (i = 1; i <= row; i++)
{
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("------扫雷游戏------\n");
}
? ? ?写完这两个函数之后 ,记得一定要在game.h中去声明,game.c中去调用,打印结果如图所示:
?不然发现,这对玩家的游戏体验很不友好,没有加行和列的标识,使用我需要对代码进行一下改进:
void PrintBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
printf("------扫雷游戏------\n");
for (i = 0; i <= row; 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、布置雷?
布置雷的代码有三个需要注意的地方 :
一是使用宏定义定义雷的个数,便于修改
二是用于随机生成坐标的rand函数配合srand函数在main()函数中声明了一次,rand()%row+1 与rand()%col+1 保证了生成了横纵坐标在合理范围之内。
三是我们在布置雷的时候需要检查该位置是否已经有雷,避免重复布置。这里规定雷为‘1’。
布置雷的代码:?
void SetMine(char mine[ROWS][COLS], int row, int col)
{
//布置10个雷
int count = MINE_COUNT;
while (count)
{
//生产随机雷的下标
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == '0') //检测该位置是否有雷
{
mine[x][y] = '1';
count--;
}
}
}
结果展示:?
打印结果可以看出数字‘1’就代表雷的位置。
2、排查雷?
(1)排雷原理
? ? ? 排查雷的时候我们首先需要让用户输入需要排查雷的坐标,然后判断坐标的合法性及坐标是否已经被排查,其次再判断该坐标周围是否有雷,用递归来检查周围雷的个数,所以,我还需要设置一个函数来检查周围是否有雷。
下面用一张图,来解析排雷原理:
? ? ? ??假设排查坐标为(x ,y ),我们可以如下图,依次返回其周围8 个坐标下对应的值,由于我们上面规定,雷为‘1’ ,非雷为‘0’ ,则字符相加减对应ASCLL码值相加减。
? ? ? ?例如:(x ,y )周围有1个雷,则7*'0'+'1'-8*'0' 即表示‘1’的ASCLL码值减‘0’的ASCLL码值,返回整数1 ,即周围有1个雷,这样,我们就有了检测周围有雷的一个思路。
代码如下:
//返回排查坐标周围雷的数量
//方法一
int get_mine_count(char mine[ROWS][COLS], int x, int y)
{
return mine[x - 1][y] +
mine[x - 1][y - 1] +
mine[x][y - 1] +
mine[x + 1][y - 1] +
mine[x + 1][y] +
mine[x + 1][y + 1] +
mine[x][y + 1] +
mine[x - 1][y + 1] - 8 * '0';
}
其实,我们还有一种思路,用循环的方法,代码如下:
get_mine_count(char mine[ROWS][COLS], int x, int y) {
int i = 0;
int count = 0;
for (i = -1; i <= 1; i++)
{
int j = 0;
for (j = -1; j <= 1; j++)
{
if (mine[x + i][y + j] == '1')
count++;
}
}
return count;
}
?接下来就开始游戏的主体代码了:
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;//用来统计排查过雷的个数
while(win < row * col - MINE_COUNT)
{
printf("请输入要排查雷的坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col) //判断坐标合法性
{
//坐标被排查过
if (show[x][y] == '*')
{
if (mine[x][y] == '1')//判断坐标是否为1,为1则被炸死
{
printf("很遗憾,你被炸死了\n");
PrintBoard(show, ROW, COL);
(mine, ROW, COL);
break;
}
else
{
int count = get_mine_count(mine, x, y);//不是雷情况下,统计x,y坐标周围有几个雷
show[x][y] = count + '0';
PrintBoard(show, ROW, COL); //显示出排查雷的信息
win++;
}
}
else
{
printf("该坐标已经被排查过了\n");
}
}
else
{
printf("坐标非法,请重新输入\n");
}
}
if (win == row * col - MINE_COUNT)
{
printf("恭喜你,排雷成功\n");
PrintBoard(mine, ROW, COL); //向玩家展示一下布雷棋盘
}
}
?这样,一个初步的扫雷游戏代码就写完了,我们开始试玩一下:
?但是我们可以对代码再进行一次优化。
七、递归展开一片
观察网页版的扫雷我们可以发现,当用户点击一个坐标,如果该坐标及其周围的坐标都没有雷,那么雷盘就会一次性展开一片,而这样设计也是比较合理的,因为如果每一个非雷坐标都需要玩家排查的话十分影响游戏体验;所以,这里我们就利用递归的实现模拟实现了这个功能。
满足这三个条件,即可实现
1、该坐标不是雷坐标
2、该坐标周围为0时才可以进入递归
3、?进入递归的坐标必须是未排查过的坐标,否则可能重复排查坐标,出现死递归
代码如下:
void Recursion(char show[ROWS][COLS], char mine[ROWS][COLS], int x, int y, int row, int col, int* win)
{
if (x >= 1 && x <= row && y >= 1 && y <= col)//判断坐标是否在排查范围内
{
int ret = get_mine_count(mine, x, y);//接受坐标周围雷的数量
if (ret == 0)//递归条件--周围雷数为0
{
(*win)++;//每排查一个坐标,排查次数加1,为判断输赢做准备
show[x][y] = '0';//显示周围雷数
int i = 0;
int j = 0;
//用两个循环遍历周围8个坐标
for (i = -1; i <= 1; i++)
{
for (j = -1; j <= 1; j++)
{
if (show[x + i][y + j] == '*')//递归的坐标必须是未排查过的坐标,
//防止死递归
{
Recursion(show, mine, x + i, y + j, row, col, win);
}
}
}
}
else
{ //条件不满足退出递归
(*win)++;//排查坐标,次数加1
show[x][y] = ret + '0';//显示周围雷数
}
}
}
运行结果:
?八、标记雷的位置
要增加可玩性,还需要增加标记功能,作用是把玩家确定的雷坐标标记出来,或者是把不确定的雷坐标标记出来。它的作用只是做记号,相比于递归展开,标记功能显然更容易实现。
这就需要写一个标记雷位置的函数:
void MarkMine(char board[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
while (1)
{
printf("请输入你想要标记位置的坐标->");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col) //判断该坐标是否合法
{
if (board[x][y] == '*') //判断该坐标是否被排查
{
board[x][y] = '#';
break;
}
else
{
printf("该位置不能被标记,请重新输入!\n");
}
}
else
{
printf("输入错误,请重新输入!\n");
}
}
}
然后,再重新写一下FindMine()函数就行了,
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0; //用来标记是否取得胜利
int* pw = &win;
char ch = 0; //用来接受是否需要标记雷
while (win < row * col - MINE_COUNT)
{
printf("请输入你想要排查的坐标->");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col) //判断坐标合法性
{
if (mine[x][y] == '1')
{
system("cls");
printf("很遗憾,你被炸死了!\n");
PrintBoard(mine, row, col); //被炸死了就打印mine数组,让用户知道自己怎么死的
break;
}
else
{
if (show[x][y] != '*') //判断是否重复排查
{
printf("该坐标已被排查,请重新输入!\n");
continue; //直接进入下一次循环
}
else
{
Recursion(mine, show, row, col, x, y,pw); //递归展开一片
system("cls"); //清空屏幕
PrintBoard(show, row, col); //打印棋盘
printf("需要标记雷的位置请输入y/Y,否则请按任意键->");
while ((ch = getchar()) != '\n'); //清理缓冲区
scanf("%c", &ch);
if (ch == 'Y' || ch == 'y')
{
MarkMine(show, row, col); //标记雷
system("cls");
PrintBoard(show, row, col);
}
else
{
continue;
}
}
}
}
else
{
printf("输入错误,请重新输入!\n");
}
}
if (win == row * col - MINE_COUNT)
{
system("cls");
printf("恭喜你,排雷成功!\n");
PrintBoard(show, row, col);
return;
}
}
?为了让扫雷界面更简洁,在每次打印雷盘前增加了清屏操作:system("cls");
九、游戏测试
十、代码展示
(1)test,c:
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void menu() {
printf("**********菜单********\n");
printf("******1->扫雷游戏*****\n");
printf("******2->退出游戏*****\n");
}
void game() {
//定义用于存放雷和显示雷的数组
char mine[ROWS][COLS];
char show[ROWS][COLS];
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
SetMine(mine, ROW, COL);//布置雷
system("cls");
PrintBoard(show, ROW, COL);//打印雷盘
/*PrintBoard(mine, ROW, COL);*/
FindMine(mine, show, ROW, COL);//开始扫雷游戏
}
void test() {
int input = 0;
do {
srand((unsigned int)time(NULL));
scanf("%d", &input);
if (input == 1)
printf("游戏开始\n");
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n:");
break;
default:
printf("输入错误,请重新输入\n:");
break;
}
} while (input);
}
int main() {
menu();
test();
return 0;
}
(2)game.h
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include <stdlib.h>
#include <time.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define MINE_COUNT 10 //将雷个数定义成define定义的标识符常量,方便以后更改雷的数量。
//char mine[ROWS][COLS] = { 0 };//存放布置好的雷的信息
//char show[ROWS][COLS] = { 0 };//存放排查出的雷的信息
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);//雷盘初始化
void PrintBoard(char board[ROWS][COLS], int row, int col); //打印棋盘
void SetMine(char mine[ROWS][COLS], int row, int col); //布置雷盘
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);//排查雷
(3)game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
void PrintBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
printf("------扫雷游戏------\n");
for (i = 0; i <= row; 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");
}
void SetMine(char mine[ROWS][COLS], int row, int col)
{
//布置10个雷
int count = MINE_COUNT;
while (count)
{
//生产随机雷的下标
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == '0') //检测该位置是否有雷
{
mine[x][y] = '1';
count--;
}
}
}
get_mine_count(char mine[ROWS][COLS], int x, int y) {
int i = 0;
int count = 0;
for (i = -1; i <= 1; i++)
{
int j = 0;
for (j = -1; j <= 1; j++)
{
if (mine[x + i][y + j] == '1')
count++;
}
}
return count;
}
void Recursion(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int* pw)
{
if (x >= 1 && x <= row && y >= 1 && y <= col) //判断坐标是否为排查范围内
{
int num = get_mine_count(mine, x, y); //获取坐标周围雷的个数
if (num == 0)
{
(*pw)++;
show[x][y] = '0'; //如果该坐标周围没有雷,就把该坐标置成空格,并向周围八个坐标展开
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] == '*') //限制递归条件,防止已经排查过的坐标再次递归,从而造成死递归
Recursion(mine, show, row, col, i, j, pw);
}
}
}
else
{
(*pw)++;
show[x][y] = num + '0';
}
}
}
void MarkMine(char board[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
while (1)
{
printf("请输入你想要标记位置的坐标->");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col) //判断该坐标是否合法
{
if (board[x][y] == '*') //判断该坐标是否被排查
{
board[x][y] = '#';
break;
}
else
{
printf("该位置不能被标记,请重新输入!\n");
}
}
else
{
printf("输入错误,请重新输入!\n");
}
}
}
//void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
//{
// int x = 0;
// int y = 0;
// int win = 0;//用来统计排查过雷的个数
// while (win < row * col - MINE_COUNT)
// {
// printf("请输入要排查雷的坐标:>");
// scanf("%d %d", &x, &y);
// if (x >= 1 && x <= row && y >= 1 && y <= col) //判断坐标合法性
// {
// //坐标被排查过
// if (show[x][y] == '*')
// {
// if (mine[x][y] == '1')//判断坐标是否为1,为1则被炸死
// {
// printf("很遗憾,你被炸死了\n");
// PrintBoard(show, ROW, COL);
// break;
// }
// else
// {
// Recursion(mine, show, row, col, x, y, &win);
// int count = get_mine_count(mine, x, y);//不是雷情况下,统计x,y坐标周围有几个雷
// show[x][y] = count + '0';
// PrintBoard(show, ROW, COL); //显示出排查雷的信息
// win++;
// }
// }
// else
// {
// printf("该坐标已经被排查过了\n");
// }
// }
// else
// {
// printf("坐标非法,请重新输入\n");
// }
// }
// if (win == row * col - MINE_COUNT)
// {
// printf("恭喜你,排雷成功\n");
// PrintBoard(mine, ROW, COL); //向玩家展示一下布雷棋盘
// }
//}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0; //用来标记是否取得胜利
int* pw = &win;
char ch = 0; //用来接受是否需要标记雷
while (win < row * col - MINE_COUNT)
{
printf("请输入你想要排查的坐标->");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col) //判断坐标合法性
{
if (mine[x][y] == '1')
{
system("cls");
printf("很遗憾,你被炸死了!\n");
PrintBoard(mine, row, col); //被炸死了就打印mine数组,让用户知道自己怎么死的
break;
}
else
{
if (show[x][y] != '*') //判断是否重复排查
{
printf("该坐标已被排查,请重新输入!\n");
continue; //直接进入下一次循环
}
else
{
Recursion(mine, show, row, col, x, y,pw); //递归展开一片
system("cls"); //清空屏幕
PrintBoard(show, row, col); //打印棋盘
printf("需要标记雷的位置请输入y/Y,否则请按任意键->");
while ((ch = getchar()) != '\n'); //清理缓冲区
scanf("%c", &ch);
if (ch == 'Y' || ch == 'y')
{
MarkMine(show, row, col); //标记雷
system("cls");
PrintBoard(show, row, col);
}
else
{
continue;
}
}
}
}
else
{
printf("输入错误,请重新输入!\n");
}
}
if (win == row * col - MINE_COUNT)
{
system("cls");
printf("恭喜你,排雷成功!\n");
PrintBoard(show, row, col);
return;
}
}
这就是扫雷的全部代码了,如果博客里面有什么问题希望大家指出来,博客到这里就结束了,希望对大家有帮助,如果你觉得写的不错,希望大家点个赞,给个关注,谢谢大家了。
|