文章目录
前言
扫雷游戏是许多人初玩电脑是常玩的游戏,没完过的话也可以试着玩一玩,这样对于编写扫雷游戏会有一个很好的思路的;在编写的过程中我们既可以增强自己的代码编写能力,又可以引起自己的兴趣。
一、游戏编写思路(游戏具备的各种功能)
- 为游戏设置一个菜单根据玩家选择开始游戏或退出
- 对布置雷的棋盘和显示的棋盘进行初始化
- 对棋盘进行打印,将棋盘与其中的元素显示出来
- 对棋盘中对雷进行设置
- 玩家进行操作,在棋盘中查找某个坐标
- 为玩家的操作做出反馈(包括:(1)此坐标没有雷,为玩家显示周围是否有几颗雷 (2)此坐标就周围无雷,进行爆炸性排除 (3)此坐标有雷,显示"你被炸死了" (4)玩家输入错误,显示“输入错误,请重新输入” (5)此处已被排查过,显示"此处已排查,请重新输入" (6)除雷外的位置都被找到,玩家赢了
- 待本局游戏结束后,再次打印菜单,玩家选择游戏或退出
二、功能实现
1.构建游戏菜单
游戏菜单的主要功能是供玩家选择“开始游戏”或“退出”
代码及实现结果如下:
void Menu()
{
printf("**************\n");
printf("****0.退出 ***\n");
printf("****1.Play ***\n");
printf("**************\n");
}
void Game()
{
printf("已进入游戏,本次执行完成\n");
}
int main()
{
int input = 0;
do
{
Menu();
printf("请输入你想要执行的操作:");
scanf_s("%d", &input);//玩家输入的值,与菜单和下面的switch语句对应
switch (input)
{
case 1:
printf("已进入游戏");
break;
case 0:
break;
default://0和1外的其他值
printf("输入错误,请重试\n");//输入了菜单中没有的值,要重新输入
}
} while (input);//对值进行判定,0为假不继续执行,非零为真,继续循环
}
在代码中,我们使用简单的printf函数之间将菜单全部打印在屏幕上,然后在主函数中,使用了do while 语句和switch 语句来对玩家输入的值做出响应。
代码详解:do while语句中循环至少会执行一次,为玩家打印出菜单以及对玩家输入的值做出响应,输入1则会进入Game函数中,这是我设计的游戏函数,之后的游戏相关功能我都会在此设置,在游戏完成后会打印本局游戏已结束,然后向下执行,由于while()中的判断值是玩家输入的值,输入值如果是1,则会继续进行循环(非零为真),再次跳出菜单供玩家选择,如果输入值是0,则会停止(0为假,不满足再次进入循环的条件),如果输入了其他值,在switch语句中的default子句(除case子句中的值 外的值会进入)会显示重新玩家重新输入值,然后再while()处进行判定(非零,满足条件),后进入循环。
2.棋盘初始化
棋盘初始化化的整体思路与遍历类似,就是将其中的元素都设定为一个值。
代码如下:
#define ROW 9
#define COL 9
#define ROWS ROW+2//初始化时在上下各多一行
#define COLS COL+2//初始化时在左右各多一列
//为了以后在排查时不会超限
//初始化
void InitMine(char mine[ROWS][COLS], int row, int col,char symbol)
{
int i = 0;
int j = 0;
for (i=0;i<row;i++)
{
for (j=0;j<col;j++)
{
mine[i][j] = symbol;//根据不同的符号需求设定不同符号
}
}
}
代码详解:由于我们需要设置两个棋盘:设置雷的棋盘和显示给玩家的棋盘(毕竟不能直接把雷的位置显示给玩家),所以我们要为他们设置不同的符号;此外,为了我们方便对棋盘的大小进行设置,使用#define 定义了四个常ROW,COL,ROWS,COL;ROW与COL为9,是我们为玩家显示的棋盘:9*9,ROWS与COLS比显示的棋盘长度多2即上下左右各多一行,是为了便于后面在排查坐标时不会超出范围(对某个坐标周边的全部坐标进行排查,在靠边的坐标处可能会超出范围),函数调用时只需要将symbol改为自己选择的符号 (注符号要打单引号)。
3.屏幕显示
将棋盘中的元素显示给用户,只需在显示棋盘进行调用,相应细节以写在注释中
代码和运行结果如下:
void Game()//调用
{
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
InitMine(mine,ROWS,COLS,'0');
InitMine(show, ROWS, COLS, '*');
DisplayMine(show, ROW, COL);//显示对玩家的那块棋盘
}
//显示
void DisplayMine(char mine[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
printf("--扫雷游戏--\n");//美观性设计
for (j = 0; j <= col; j++)//没有其他条件,默认在第一行打印
{
printf("%d",j);//打印列号
}
printf("\n");//换行符,将其他打印的元素的起始位置变为下一行
for (i = 1; i <= row; i++)//i=0时打印列号
{
printf("%d", i);//打印行号
for (j = 1; j <= col; j++)
{
printf("%c", mine[i][j]);//打印棋盘中的元素
}
printf("\n");//每行进行换行处理
}
printf("--扫雷游戏--\n");
}
3.雷的布置
使用srand与time函数生成一个随机值,再用rand函数接受,并对齐进行处理,使获得的值符合棋盘范围;srand()和rand()的头文件是<stdlib.h>,time()的头文件是<time.h>;因为sand()只需要调用一次即可,所以将其放入主函数中。其余细节在注释中。
代码及运行结果:
srand((unsigned int)time(NULL));//生成随机值
void Game()//调用功能函数
{
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
InitMine(mine,ROWS,COLS,'0');
InitMine(show, ROWS, COLS, '*');
DisplayMine(show, ROW, COL);
SetMine(mine, ROW, COL);
DisplayMine(mine, ROW, COL);//显示布置了雷后的棋盘
}
//雷的设置
void SetMine(char mine[ROWS][COLS], int row, int col)
{
int count = mineamount;//最大布置雷的数量
while (count)//雷的数量不为0
{
int m = rand() % row + 1;//对随机值的范围进行改变,使其符合棋盘范围
int n = rand() % col + 1;
if (mine[m][n] == '0')//棋盘初始化时的值,避免两个值布置在同一位置
{
mine[m][n] = '1';//将雷的位置的符号改变
count--;//每布置一颗雷,剩余可布置雷数减少1
}
}
}
4.玩家操作与页面反馈
本功能是整个扫雷游戏中最重要的功能,其中应用到的知识也较难,包括主要包括循环与递归。
代码和结果如下图:
//统计周围有几颗雷
int Statistics(char mine[ROWS][COLS], int x, int y)
{
return mine[x - 1][y - 1] +
mine[x - 1][y]+
mine[x - 1][y + 1] +
mine[x][y - 1] +
mine[x][y + 1] +
mine[x + 1][y - 1] +
mine[x + 1][y] +
mine[x + 1][y + 1] - 8 * '0';//计算周围有几颗雷;'1'-'0'=1;1的ASCII值为049,0的为048;
}
//查找周围值附近的雷
void Find(char mine[ROWS][COLS], char show[ROWS][ROWS], int x, int y)
{
int i = 0;
int j = 0;
for (i = x - 1; i >= x - 1 && i <= x + 1; i++)//排查范围
{
for (j = y - 1; j >= y - 1 && j <= y + 1; j++)
{
if (show[i][j] == '*')//未排查过的数
{
int count = Statistics(mine, i, j);//与排雷函数一致
if (count == 0 )
{
show[i][j] = ' ';
Find(mine, show, i, j);//递归,再次对函数进行调用
}
else
{
show[i][j] = count + '0';
}
}
}
}
}
//判断还剩余几个位置
int Full(char show[ROWS][COLS], int row, int 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] == '*')
{
count++;
}
}
}
return count;
}
//排雷函数(主体)
void FindMine(char mine[ROWS][COLS],char show[ROWS][ROWS], int row,int col)
{
int x = 0;
int y = 0;
while (1)
{
int count = Full(show,row,col);
if (count == mineamount)//剩余数与雷数相等则胜利
{
printf("你赢了\n");
break;
}
printf("请输入你想要查看的坐标:");
scanf_s("%d %d", &x, &y);
if (x>0 && x<=row && y>0 && y<=col)
{
if (mine[x][y] == '0')
{
int count = Statistics(mine, x, y);//周围有几颗雷
if (count == 0)//周围没有雷,开始扩大范围查找
{
show[x][y] = ' ';//将排查过的位置显示为空
Find(mine,show,x,y);
DisplayMine(show,row,col);
}
else
{
show[x][y] = count + '0';//显示此坐标周围有几颗雷,count为int类型+'0'转换为char类型,符合棋盘类型
DisplayMine(show, row, col);
}
}
if (show[x][y] != '*')
{
printf("此处已排查,请重新输入\n");
}
if (mine[x][y] == '1')
{
printf("你被炸死了\n");
DisplayMine(mine, ROW, COL);
break;
}
}
else
{
printf("输入错误请重新输入:\n");
}
}
}
代码详细讲解:
(1)主体是最下面的排雷函数,while()循环中,判断值一直是1,所以循环会一直执行,只能用break函数跳出,而跳出的条件我设置了两个:赢或者被炸死
赢的这一部分与其对应的函数如下:
int count = Full(show,row,col);
if (count == mineamount)//剩余数与雷数相等则胜利
{
printf("你赢了\n");
break;
}
//判断还剩余几个位置
int Full(char show[ROWS][COLS], int row, int 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] == '*')
{
count++;
}
}
}
return count;
}
赢的条件是将所有不是雷的位置排除,在我所设置的条件中,一旦某个点被排查,对玩家的显示部分即show[][] 就会发生改变,所以我们可以统计其未被改变的值,如果没有被改变,统计值count就+1,当未被改变的值刚好与我们设置的雷的值相等时,就会break跳出循环,我们就赢了。
(2)输的条件是我们被炸死了,代码如下:
if (mine[x][y] == '1')
{
printf("你被炸死了\n");
DisplayMine(mine, ROW, COL);
break;
}
如果我们查找的恰好是我们设置的雷值:‘1’,那么就会显示“你被炸死了”,然后将整个雷的位置分布图打印给我们,然后跳出循环。
(3)判定输入是否正确,就是最外围的if与else,代码如下:
if (x>0 && x<=row && y>0 && y<=col)
{
}
else
{
printf("输入错误请重新输入:\n");
}
正常的数组下标是由0开始的,小于我们计算出的数组值(因为我们是从1开始的)但是这里不同,由于我们在第0行与第0列的位置打印了行号和l列号,且我们初始化的时候范围比9*9打,不必担心超范围的事,所以我们的范围是>0即从1开始,一直到行列的最大值,输入的值不满足就会被判定重新输入
(4)主排查区
符合全部要求的就会进入我们的主排查区进行排查,代码与涉及到的函数如下:
if (mine[x][y] == '0')
{
int count = Statistics(mine, x, y);//周围有几颗雷
if (count == 0)//周围没有雷,开始扩大范围查找(爆炸性查找,节省玩家时间)
{
show[x][y] = ' ';//将排查过的位置显示为空,便与判段是否被排查过
Find(mine,show,x,y);
DisplayMine(show,row,col);//当此次查找完成时,打印出显示图,为玩家下次查找提供参考
}
else
{
show[x][y] = count + '0';//显示此坐标周围有几颗雷,count为int类型+'0'转换为char类型,符合棋盘类型
DisplayMine(show, row, col);//当此次查找完成时,打印出显示图,为玩家下次查找提供参考
}
}
//统计周围有几颗雷
int Statistics(char mine[ROWS][COLS], int x, int y)
{
return mine[x - 1][y - 1] +
mine[x - 1][y]+
mine[x - 1][y + 1] +
mine[x][y - 1] +
mine[x][y + 1] +
mine[x + 1][y - 1] +
mine[x + 1][y] +
mine[x + 1][y + 1] - 8 * '0';//计算周围有几颗雷;'1'-'0'=1;1的ASCII值为049,0的为048;
}
//查找周围值附近的雷
void Find(char mine[ROWS][COLS], char show[ROWS][ROWS], int x, int y)
{
int i = 0;
int j = 0;
for (i = x - 1; i >= x - 1 && i <= x + 1; i++)//排查范围
{
for (j = y - 1; j >= y - 1 && j <= y + 1; j++)
{
if (show[i][j] == '*')//未排查过的数
{
int count = Statistics(mine, i, j);//与排雷函数一致
if (count == 0 )
{
show[i][j] = ' ';
Find(mine, show, i, j);//函数递归,对满足周围没有雷这一条件的数,再次调用此函数
}
else
{
show[i][j] = count + '0';
}
}
}
}
}
三、 完整代码及实现结果
1.mine.h
游戏包含的函数声明,函数的头文件的包含以及宏定义
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 9
#define COL 9
#define mineamount 10
#define ROWS ROW+2//初始化时在上下各多一行
#define COLS COL+2//初始化时在左右各多一列
//为了以后在排查时不会超限
void InitMine(char mine[ROWS][COLS],int row,int col,char symbol);
void DisplayMine(char mine[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][ROWS], int row, int col);
2.mine.c
游戏中使用到的函数
#include "mine.h"
//初始化
void InitMine(char mine[ROWS][COLS], int row, int col,char symbol)
{
int i = 0;
int j = 0;
for (i=0;i<row;i++)
{
for (j=0;j<col;j++)
{
mine[i][j] = symbol;//根据不同的符号需求设定不同符号
}
}
}
//显示
void DisplayMine(char mine[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
printf("--扫雷游戏--\n");
for (j = 0; j <= col; j++)//没有其他条件,默认在第一行打印
{
printf("%d",j);//打印列号
}
printf("\n");//换行符,将其他打印的元素的起始位置变为下一行
for (i = 1; i <= row; i++)//i=0时打印列号
{
printf("%d", i);//打印行号
for (j = 1; j <= col; j++)
{
printf("%c", mine[i][j]);//打印棋盘中的元素
}
printf("\n");//每行进行换行处理
}
printf("--扫雷游戏--\n");
}
//雷的设置
void SetMine(char mine[ROWS][COLS], int row, int col)
{
int count = mineamount;//最大布置雷的数量
while (count)//雷的数量不为0
{
int m = rand() % row + 1;//对随机值的范围进行改变,使其符合棋盘范围
int n = rand() % col + 1;
if (mine[m][n] == '0')//棋盘初始化时的值,避免两个值布置在同一位置
{
mine[m][n] = '1';//将雷的位置的符号改变
count--;//每布置一颗雷,剩余可布置雷数减少1
}
}
}
//统计周围有几颗雷
int Statistics(char mine[ROWS][COLS], int x, int y)
{
return mine[x - 1][y - 1] +
mine[x - 1][y]+
mine[x - 1][y + 1] +
mine[x][y - 1] +
mine[x][y + 1] +
mine[x + 1][y - 1] +
mine[x + 1][y] +
mine[x + 1][y + 1] - 8 * '0';//计算周围有几颗雷;'1'-'0'=1;1的ASCII值为049,0的为048;
}
//查找周围值附近的雷
void Find(char mine[ROWS][COLS], char show[ROWS][ROWS], int x, int y)
{
int i = 0;
int j = 0;
for (i = x - 1; i >= x - 1 && i <= x + 1; i++)//排查范围
{
for (j = y - 1; j >= y - 1 && j <= y + 1; j++)
{
if (show[i][j] == '*')//未排查过的数
{
int count = Statistics(mine, i, j);//与排雷函数一致
if (count == 0 )
{
show[i][j] = ' ';
Find(mine, show, i, j);
}
else
{
show[i][j] = count + '0';
}
}
}
}
}
//判断还剩余几个位置
int Full(char show[ROWS][COLS], int row, int 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] == '*')
{
count++;
}
}
}
return count;
}
//排雷
void FindMine(char mine[ROWS][COLS],char show[ROWS][ROWS], int row,int col)
{
int x = 0;
int y = 0;
while (1)
{
int count = Full(show,row,col);
if (count == mineamount)//剩余数与雷数相等则胜利
{
printf("你赢了\n");
break;
}
printf("请输入你想要查看的坐标:");
scanf_s("%d %d", &x, &y);
if (x>0 && x<=row && y>0 && y<=col)
{
if (mine[x][y] == '0')
{
int count = Statistics(mine, x, y);//周围有几颗雷
if (count == 0)//周围没有雷,开始扩大范围查找
{
show[x][y] = ' ';//将排查过的位置显示为空
Find(mine,show,x,y);
DisplayMine(show,row,col);
}
else
{
show[x][y] = count + '0';//显示此坐标周围有几颗雷,count为int类型+'0'转换为char类型,符合棋盘类型
DisplayMine(show, row, col);
}
}
if (show[x][y] != '*')
{
printf("此处已排查,请重新输入\n");
}
if (mine[x][y] == '1')
{
printf("你被炸死了\n");
DisplayMine(mine, ROW, COL);
break;
}
}
else
{
printf("输入错误请重新输入:\n");
}
}
}
3.test.c
游戏主体,用于测试
#include"mine.h"
void Menu()
{
printf("**************\n");
printf("****0.退出 ***\n");
printf("****1.Play ***\n");
printf("**************\n");
}
void Game()//调用功能函数
{
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
InitMine(mine,ROWS,COLS,'0');
InitMine(show, ROWS, COLS, '*');
DisplayMine(show, ROW, COL);
SetMine(mine, ROW, COL);
FindMine(mine, show, ROW, COL);
}
int main()
{
srand((unsigned int)time(NULL));//生成随机值
int input = 0;
do
{
Menu();
printf("请输入你想要执行的操作:");
scanf_s("%d", &input);
switch (input)
{
case 1:
Game();
printf("本次游戏已结束\n");
break;
case 0:
break;
default:
printf("输入错误,请重试\n");
}
} while (input);
}
//
SetMine(mine,ROW,COL);
FindMine(mine, show,ROW,COL);
4.运行结果
总结
扫雷程序的编写,我认为对于C语言小白来说是一个很大的挑战,但同时,在编写完成后我们对于如何编写一个较大的程序,也会有自己的思考,会成为以后编写的一块垫脚石同时也会提高自己的编程能力。希望我此次的教程能对各位C语言小白的成长起到一定的助力,谢谢大家!