C语言:扫雷【基础篇】

前言:文件夹中的函数声明

在开始前我们将这个项目分为三个文件夹,分别是函数声明,游戏函数结构和主函数结构;这样做方便代码的管理,也不显的拥挤。

但在文件与文件之间就需要用如下代码来将三个文件夹联系起来

这里就将game.h这个文件与main.c串起来了,当然,在game.c中的函数声明也可以被main函数调用了

1.主函数(menu)的基本结构

进入游戏后肯定要打印菜单,无论如何需要执行一次,所以我们用do-while的结构来写代码,我们把打印菜单也设计成一个函数为menu,实现游戏逻辑的函数为game,假设当用户输入1(input==1)时我们正式进入游戏,并用switch语句来分析不同情况,就可以得到:

而菜单的函数较为简单,不多赘述:


2.游戏函数的结构

2.1基本逻辑

在我们玩扫雷的时候,开始时是不是有一个表格呢?我们可以把这个表格看成是二维数组,让用户输入坐标来排查雷。并且在雷的位置是随机排布的,在我们排查雷时,还要计算出所选坐标周围雷的个数。所以我们大致能分析出在游戏函数中我们要完成的任务:

  1. 生成一个带有随机排布雷的表格
  2. 能将表格打印出来的函数
  3. 一个能排查雷的函数

以较为基础的扫雷难度,一个9*9的表格,其中有10个雷的简单难度为例:

2.2表格的创建以及初始化

2.2.1表格数量

前面说过,我们可以把表单看成二维数组,所以创建表单也就是创建二维数组,但在创建前我们要想到,作为上帝视角的我们是要知道雷在哪里的。当用户选取一个位置时我们需要根据他选的地址与我们手上的数组来比较此位置是不是雷。也就是我们输出的是一个全不是雷的数组,只是用户选取了地址后传到另一个数组来确定是不是雷。如果不是,我们再去修改输出数组中位置的值。总之,我们需要两个表格来完成“对照”这个工作

而我们看见的表格是完整的命名为mine数组,给用户看的表格命名为show数组

明白有两个表格后我们迎来又一个问题,表格创建后每个元素均为0,也就是说我们打印出来是这样的:

这样就伴随一个问题,“当用户选择的位置周围确实没有雷的时候也是0,那是不是会出现不记得此位置是否排查过了的情况呢?”所以我们用'*'来表示未知的位置,也就是说我们需要在创建表格后在进行把所有‘0’换成‘*’的初始化的过程。因为有两个表格,所以这也需要一个函数。

注:因为扫雷的难度不同表格大小不同,这里分别把行(row)和宽(col)进行定义变量,如果我们想写一个还能更换难度的扫雷会更方便(在函数声明文件中定义)

2.2.2表格的真实大小

但在创建时我们还要想到一个问题,当用户选择了在表格边缘的地点时,我们还是要计算周围九个元素时,是不是造成的数组的越界访问呢?

为避免这样的问题,我们有想出了其他办法。把宽和高各加2,也就是在外围再加上一圈就完美解决了这个问题。我们创建11*11的表格,但仅在9*9的表格里做修改并打印给用户,这样周围就一定可以侦测到九个元素。明白这两大问题,我们就还需一个更大的宽和高为ROWS和COLS分别为需要的宽和高再加2。明白以上问题,我们终于能对表格进行以下声明与创建:

(游戏设计函数在game.c中,不在主函数中)

                    

2.2.3初始化表格函数(InitBoard)

和前面说的一样,现在我们需要进行对创建的两个数组进行初始化:

但此时我们发现,对于每一个传过去的数组,我们都会把他的每一个元素都把他替换成'*'。如果我们不想用'*'而是‘#’呢?或者不同的表格我想要初始化的内容不同呢?所以我们把要替换的内容定义为set得到以下最终代码:

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;
		}
	}
}

至此,我们表格创建的工作就彻底完成了


2.3表格的输出(DisplayBoard)

为要给用户展示排查后的结果,和检查自己的代码在书写时是否有错误,我们是不是需要一个能打印表格的函数呢?所以我们写出以下函数:

但这样还不够完美,能不能在界面周围加上坐标来提升游玩体验呢?如下:

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	printf("----------扫雷---------\n");
	//打印行号
	for (i = 0; i <= row; i++)//从0开始与表格对齐!!!
		printf("%d ", i);
	printf("\n");
	for (i = 1; i <= row; i++)
	{
		printf("%d ", i);//避免与上面输出的0冲突并且舍弃外围的一圈,i又从1开始!!!
		int j = 0;
		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
	printf("\n");
}

效果图:

2.4随机雷的布置函数(SetMine)

在已经创建了表格并且可以打印出来时,既然有坐标,我们是不是就可以用坐标(也就是数组下标)来表示一个位置?所以我们可以随机生成x,和y来表示一个位置并给他赋值上一个’1‘来表示雷。但我们还要想到,随机不代表不可能重复,我们埋雷的前提是这个位置一开始不是雷,所以我们写出以下代码:

void SetMine(char mine[ROWS][COLS], int row, int col) 
{
	int count = easy_count;//标记雷的数量(这里的easy_count即为简单难度等于9)
	while (count)
	{
		int x = rand() % row + 1;//%row也就是%9,再+1保证了随机生成的数在1~9之间
		int y = rand() % row + 1;//rand()是用来生成随机数的函数,在这里需要与另一个函数配合使用,这里不做详细描述
		if (mine[x][y] = '0')
		{
			mine[x][y] = '1';
			count--;
		}
	}
}

在应用rand(),srand()和time函数时,他们的库函数不同所以我们还需要在game.h中做出库函数的引用:

注:我们此时传输的只有mine数组,也就是我们自己的那份答案,不要把show数组也初始化了哦!!!

这里我们就完成了雷的随机布置的工作


2.5排查雷的函数(FindMine)

上文都是游戏开始的前提条件,有这些以后我们终于可以开始游戏了,我们现在要设计一个可以进行游戏的函数,让用户能够一个个排查雷把游戏进行下去

首先,每当用户输入一个坐标然后在与我们自己手上那份数组做对比,最后来判断这个这个位置是否是雷。这里有需要两个条件

  • 用户输入的坐标合法
  • 这个位置还没有被排查过

所以我们要对(x,y)规定范围,对于是否被排查过,我们只需要确认这个位置上的字符是否为‘*’即可

所以我们能初步得到以下代码

但是这里排查后的周围雷个数怎么得到呢?这里就要再引用一个函数来实现:

2.5.1计算周围雷的个数(get_my_count)

我们在初始化mine数组时知道,在未初始化之前的元素均为0,而初始化后部分变为1,所以我们只要找到周围八个元素把他们加起来算以下有几个‘1’就行了。那我们得到了(x,y)这个中心坐标后就很容易把周围的元素坐标表示出来了

就可以得出以下代码:

static int get_my_count(char mine[ROWS][COLS], int x, int y)//因为该函数是为FindMine服务,不许要跨文件,可以用static来修饰
{
	int i = 0;
	int c = 0;//个数
	for (i = -1; i <= 1; i++)
	{
		int j = 0;
		for (j = -1; j <= 1; j++)
		{
			c += (mine[x + i][y + j] - '0');//减去一个字符0的ASCII码值来转换为数字
		}
	}
	return c;
}

但其实用穷举发来写反而更加方便

static int get_my_count(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';
}

2.5.2整体代码

这样一次雷的排查就完成了,但是“排查”这个动作是不可能只进行一次的,所以这个动作是一个循环的,我们在外面还要套一个循环。这个循环结束有两种情况:

  1. 所有雷均被排除(不是雷的坐标被排查完)
  2. 排查到雷,游戏失败

所以我们得到以下代码:

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 - easy_count)
	{
		printf("请输入坐标\n");
		scanf("%d,%d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)//坐标合法
		{
			if (show[x][y] = '*')//坐标未被排查
			{
				if (mine[x][y] == '1')
				{
					printf("(%d,%d)处是炸弹,游戏结束\n", x, y);
					DisplayBoard(mine, ROW, COL);
                    break;
				}
				else
				{
					show[x][y] = get_my_count(mine, x, y) + '0';
					DisplayBoard(show, ROW, COL);
					win++;
				}
			}
			else
				printf("该坐标已被排查过了\n");
		}
		else
			printf("输入错误坐标\n");
		if (win == ROW * COL - easy_count)//所有雷被排除
		{
			printf("恭喜你,扫雷成功\n");
			DisplayBoard(mine, ROW, COL);
		}
	}
}

3.游戏的整体代码

3.1声明文件(game.h)

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define ROW 9
#define COL 9
#define easy_count 10

#define ROWS ROW+2
#define COLS COL+2

void InitBoard(char board[ROWS][COLS], int rows, int cols,char set);
void DisplayBoard(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.2游戏函数文件(game.c)

#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)
{
	int i = 0;
	printf("----------扫雷---------\n");
	//打印行号
	for (i = 0; i <= row; i++)//从0开始与表格对齐!!!
		printf("%d ", i);
	printf("\n");
	for (i = 1; i <= row; i++)
	{
		printf("%d ", i);//避免与上面输出的0冲突,i又从1开始!!!
		int j = 0;
		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) 
{
	int count = easy_count;
	while (count)
	{
		int x = rand() % row + 1;
		int y = rand() % row + 1;
		if (mine[x][y] = '0')
		{
			mine[x][y] = '1';
			count--;
		}
	}
}
static int get_my_count(char mine[ROWS][COLS], int x, int y)//因为该函数是为FindMine服务,不许要跨文件,可以用static来修饰
{
	int i = 0;
	int c = 0;//个数
	for (i = -1; i <= 1; i++)
	{
		int j = 0;
		for (j = -1; j <= 1; j++)
		{
			c += (mine[x + i][y + j] - '0');//减去一个字符0的ASCII码值来转换为数字
		}
	}
	return c;
}
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 - easy_count)
	{
		printf("请输入坐标\n");
		scanf("%d,%d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)//坐标合法
		{
			if (show[x][y] == '*')//坐标未被排查
			{
				if (mine[x][y] == '1')
				{
					printf("(%d,%d)处是炸弹,游戏结束\n", x, y);
					DisplayBoard(mine, ROW, COL);
				}
				else
				{
					show[x][y] = get_my_count(mine, x, y) + '0';
					DisplayBoard(show, ROW, COL);
					win++;
				}
			}
			else
				printf("该坐标已被排查过了\n");
		}
		else
			printf("输入错误坐标\n");
		if (win == ROW * COL - easy_count)
		{
			printf("恭喜你,扫雷成功\n");
			DisplayBoard(mine, ROW, COL);
		}
	}
}

3.3主函数文件(main.c)

#include "game.h"
void menu()
{
	printf("----------扫雷---------\n");
	printf("***********************\n");
	printf("*****1.play 0.exit*****\n");
	printf("***********************\n");
}
void game()
{	
	//创建表格
	char mine[ROWS][COLS] = { 0 };
	char show[ROWS][COLS] = { 0 };
	//表格初始化
	InitBoard(mine,ROWS,COLS,'0');
	InitBoard(show,ROWS,COLS,'*');
	//随机埋雷
	SetMine(mine, ROW, COL);
	//打印表格
	//DisplayBoard(mine, ROW, COL);
	DisplayBoard(show, ROW, COL);
	//排查雷
	FindMine(mine,show, ROW, COL);
}

int main()
{
	srand((unsigned int)time(NULL));//与rand()配合使用
	int input = 0;
	do
	{
		//打印游戏菜单
		menu();
		scanf("%d", &input);
		//游戏逻辑
		switch (input)
		{
		case 1://进入游戏
			game();//实现游戏的函数
			break;
		case 0://退出游戏
			break;
		default://输入其他数字时
			printf("错误输入 请输入0/1\n");
			break;
		}
	} while (input);
		return 0;
}

至此一个简化版的扫雷就完成了,创作不易;欢迎大家的指正和建议,顺手点个赞和关注呗:-)

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值