我们前面已经了解了数组和函数的使用方法以及效果,现在我们来运用数组和函数来实践一下如何实现扫雷游戏吧。
扫雷游戏的玩法
当我们想要写出一个游戏的代码时,我们首先要理清楚一个游戏的玩法,再去考虑实现这个玩法的代码。经典的扫雷游戏的玩法如下:
1)扫雷游戏中有一块块方格的
2)扫雷游戏中放置了一定数量的雷。
3)当我们点击方格后,方格当中如果不是雷,就是显示附近的雷的数量,如果点到雷,那么游戏失败
4)如果方格附近没有雷,那么会在附近展开格子。
一、游戏菜单
这个大家根据自己想法进行实现吧,用简单的printf函数就能完成
二、生成游戏界面并放置雷
首先是生成一片扫雷的区域,我们可以发现扫雷中的区域是一个mxn。那么我们可以考虑用一个列为n,行为m的二维数组来实现这片区域。
扫雷游戏中有些方格是没有雷的,而有些方格是有雷的,那么我们就需要在数组中存放这些信息,我的设置方法是没雷的格子为0,有雷的格子为1.
我的实现方法如下:
将二维数组的元素全部初始化为‘0’随后再放置雷区
void InitChess(char chess[ROWS][COLS], int y, int x, char set)//初始化雷区的函数
{//set是一个字符型的形式参数
for (y = 0; y < ROWS; y++)
{
for (x = 0; x < COLS; x++)
{
chess[y][x] = set;
}
}
return;
}
1)当我们点击的格子为0时,排查周围的雷的个数
2)当我们点击格子为1时,游戏失败
3)如果排查的格子周围的个数为0,那么就会自动扩展排查雷区
二(1)、放置雷区
我们需要在这个雷区中随机放置一定数量的雷,那么如何放置呢?我们首先要注意随机这两个字,所以我们需要使用到随机数(随机数的使用在C语言入门指南中提到了)。
我的实现方法如下:
1)用x,和y接收随机数,然后在y行x列置上雷。
2)判断一下y行x列是否已经存在雷,如果存在,就重新随机一个新的位置放置雷
void SetMine(char mine[ROWS][COLS], int y, int x, char set)
{
srand((unsigned int)time(NULL));
int n = EASE;//EASE是一个宏定义的常量,值是雷的数量(具体几个雷由玩家决定)。
while (n)
{
y = rand()%8+1, x = rand()%8+1;
if (mine[y][x] == '0')
{
mine[y][x] = '1';
n--;
}
}
return;
}
三、显示雷区
当我们完成初始化后,我们就需要将雷区展示给玩家了。
void DisplayChess(char chess[ROWS][COLS], int y, int x)
{
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%-2d", i);
}
printf("\n");
for (y = 1; y <= ROW; y++)
{
printf("%d ", y);
for (x = 1; x <= COL; x++)
{
printf("%c ", chess[y][x]);
}
printf("\n");
}
return;
}
不仅仅初始化后展示雷区,当玩家进行操作后也需要展示一下雷区。
四、排查雷区
当程序随机放置好雷后,就来到玩家的游戏回合了。这里我们要实现的就是游玩的规则。首先我们已经制作好了雷区,但是这部分雷区是玩家看不到,也就是说在玩家的游玩页面中,我们不能用上面的那个数组作为游戏页面来打印,所以当我们需要实现游戏界面时,我们需要一个与上面数组元素个数相同的二维数组来作为游戏界面。这时候我们就不仅仅要初始化上面的数组了,我们还需要将游玩界面的数组初始化
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
InitChess(mine, ROWS, COLS, '0');//初始化雷区数组,元素为‘0’
InitChess(show, ROWS, COLS, '*');//初始化界面数组,元素为‘*’
当玩家输入一个区域时,我们需要扫描一下周围的元素,那么我们就需要考虑一个问题,如果玩家输入的区域在边缘呢?
如果玩家的输入区域在边缘,那么我们就需要扫描周围区域的雷,这就难免扫描到边缘以外的区域,但是我们创建的数组是不包含边缘以外的地区,这就会导致出现数组越界的问题。我想到的解决方法有两个:
1)判断扫描的合法性,也就是不让扫描的区域越过边缘
2)把数组扩大两行两列,让边缘区域的扫描部分不再是越界区域。
右边是扩大后的数组,其中1~9是玩家可操作区,其余部分作为缓冲区不提供给玩家使用。
这时候我们就可以放心的编写一个扫描雷区的函数了。排查的方法如下,将这个区域的周围8个格子都检查一遍,存在几个雷,就给玩家显示几个雷。
我们还需要创建一个全局变量来表示非雷区的数量,当非雷区的数量为0是,玩家胜利。
void CheatMine(char mine[ROWS][COLS],char show[ROWS][COLS], int y, int x)
{//检查雷的函数,而且博主还顺带实现了递归--+
if (y > 0 && y<10&&x < 10&&x>0)
{
int ret = ScanMine(mine, y, x);
if (ret==0&&show[y][x]=='*')
{
show[y][x] =' ';
win--;
CheatMine(mine, show, y - 1, x - 1);
CheatMine(mine, show, y - 1, x);
CheatMine(mine, show, y - 1, x + 1);
CheatMine(mine, show, y, x - 1);
CheatMine(mine, show, y, x + 1);
CheatMine(mine, show, y + 1, x - 1);
CheatMine(mine, show, y + 1, x);
CheatMine(mine, show, y + 1, x + 1);
}
else if(show[y][x]=='*') {
show[y][x] = '0' + ret;
win --;
}
}
}
int ScanMine(char mine[ROWS][COLS], int y, int x)
{
return mine[y - 1][x - 1] + mine[y - 1][x]
+ mine[y - 1][x + 1] + mine[y][x - 1]
+ mine[y][x + 1] + mine[y + 1][x - 1]
+ mine[y + 1][x] + mine[y + 1][x + 1] - 8 * '0';
}
博主的实现方法计算他们的ASCII码值来决定显示的数(在ASCII码值中,我们字符‘0’+整型1的结果是字符‘1’,以此类推)。
五、展开雷区
试玩一下正版扫雷,我们知道如果我们排查的区域没有雷,那么游戏中还会展开周围的地方。那么我们该如何展开呢?我们细想一下,展开的区域是不是也要扫描周围区域?如果展开区域没有雷,是不是又要展开一次?
我们可以发现这个效果是不是在重复实现一个效果,那么我们就可以用递归的形式来实现展开。如何实现呢?当我们进行扫描时,如果扫描的结果为0个雷,我们就先周围的区域都传上去扫描,这样就实现了递归。
我们要注意,这种递归是会导致栈溢出的,假设某个地方的雷为0,它周围的地方也是0,那么就会出现,我是0,递归一下你,你也是0,递归一下我。两者之间一直递归不退出函数,那么不就导致了栈溢出了吗?所以我们就要判断一下合法性再递归了,如果我已经知道你是0了,就不再递归你。(实现方法已经在上面给出了哦)(感觉描述有点怪怪的–+)。
扫雷游戏的所有代码
#define _CRT_SECURE_NO_WARNINGS 1
#define ROWS 11 //行数的宏定义
#define COLS 11 //列数的宏定义
#define ROW ROWS-2 //用户可操作的数组行数的宏定义
#define COL COLS-2 //用户可操作的数组列数的宏定义
#define EASE 10 //游戏难度的设置(其实就是雷的数量)
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
void menu(void);
void Play(void);
void DisplayChess(char chess[ROWS][COLS], int y, int x);
void InitChess(char chess[ROWS][COLS], int y, int x, char set);
void SetMine(char mine[ROWS][COLS], int y, int x, char set);
void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS], int y, int x);
void CheatMine(char mine[ROWS][COLS], char show[ROWS][COLS], int y, int x);
头文件,记得用.h文件 来存储。(博主将这个头文件叫做game.h,所以后面我们看到的代码中会出现#include"game.h",这是博主的自定义函数库,大家如果创建了头文件也可以用这种方法引用。)
#include"game.h"
int main()
{
int input = 0;
menu();
do
{
printf("请选择:>");
scanf("%d", &input);
switch (input){
case 1:Play();
break;
default:
break;
case 0:
printf("游戏结束");
}
} while (input);
}
主函数,也是实现用户菜单界面的主要地方
#include"game.h"
int win = ROW * COL - EASE;
void menu(void)
{
printf("*********************************************************************\n");
printf("******************** 1.play **************************\n");
printf("******************** 0.exit **************************\n");
printf("*********************************************************************\n");
}
int ScanMine(char mine[ROWS][COLS], int y, int x)
{
return mine[y - 1][x - 1] + mine[y - 1][x]
+ mine[y - 1][x + 1] + mine[y][x - 1]
+ mine[y][x + 1] + mine[y + 1][x - 1]
+ mine[y + 1][x] + mine[y + 1][x + 1] - 8 * '0';
}
void CheatMine(char mine[ROWS][COLS],char show[ROWS][COLS], int y, int x)
{
if (y > 0 && y<10&&x < 10&&x>0)
{
int ret = ScanMine(mine, y, x);
if (ret==0&&show[y][x]=='*')
{
show[y][x] =' ';
win--;
CheatMine(mine, show, y - 1, x - 1);
CheatMine(mine, show, y - 1, x);
CheatMine(mine, show, y - 1, x + 1);
CheatMine(mine, show, y, x - 1);
CheatMine(mine, show, y, x + 1);
CheatMine(mine, show, y + 1, x - 1);
CheatMine(mine, show, y + 1, x);
CheatMine(mine, show, y + 1, x + 1);
}
else if(show[y][x]=='*') {
show[y][x] = '0' + ret;
win --;
}
}
}
void InitChess(char chess[ROWS][COLS], int y, int x, char set)
{
for (y = 0; y < ROWS; y++)
{
for (x = 0; x < COLS; x++)
{
chess[y][x] = set;
}
}
return;
}
void DisplayChess(char chess[ROWS][COLS], int y, int x)
{
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%-2d", i);
}
printf("\n");
for (y = 1; y <= ROW; y++)
{
printf("%d ", y);
for (x = 1; x <= COL; x++)
{
printf("%c ", chess[y][x]);
}
printf("\n");
}
return;
}
void SetMine(char mine[ROWS][COLS], int y, int x, char set)
{
srand((unsigned int)time(NULL));
int n = EASE;
while (n)
{
y = rand()%8+1, x = rand()%8+1;
if (mine[y][x] == '0')
{
mine[y][x] = '1';
n--;
}
}
return;
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int y, int x)
{
while (win) {
printf("请输入排查的行列,用空格隔开\n");
scanf("%d%d", &y, &x);
if (y > 0 && y < ROWS - 1 && x>0 && x < COLS - 1)
{
if (mine[y][x] == '1')
{
printf("你输了\n");
printf("是否重新开始:1.是,0否\n");
break;
}
else if (show[y][x] == '*')
{
CheatMine(mine, show, y, x);
DisplayChess(show, y, x);
}
else printf("输入错误\n");
}
else printf("非法输入\n");
}
if (!win)
{
printf("你赢了\n");
}
return;
}
void Play(void)
{
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
InitChess(mine, ROWS, COLS, '0');
InitChess(show, ROWS, COLS, '*');
DisplayChess(show, ROW, COL);
SetMine(mine, ROW, COL,'1');
DisplayChess(mine, ROW, COL);
FindMine(mine, show, ROW, COL);
}
实现游戏玩法的函数定义文件,游戏中的内容实现都是这个文件形成的哦。
代码中出现的宏定义常量
#define ROWS 11 //行数的宏定义
#define COLS 11 //列数的宏定义
#define ROW ROWS-2 //用户可操作的数组行数的宏定义
#define COL COLS-2 //用户可操作的数组列数的宏定义
#define EASE 10 //游戏难度的设置(其实就是雷的数量)
这里就完成了一个扫雷游戏的创作了,我们知道扫雷游戏还有标记雷这个东东的。如果大家有更好的实现方法或者在实现的过程中发现了难以解决的问题,欢迎在评论区留言,或者私信博主哦~