C语言低配版扫雷游戏

一.分析

扫雷我想大部分应该都玩过吧,游戏规则我在这里就不做解释了。

首先我们得需要两个二维数组来存放我们所需的内容,

第一个二维数组里存放的内容是让玩家看的见的内容:
在这里插入图片描述
像这样,我们把这些灰色的方格用 * 代替,为了让玩家知道要在这里输入坐标,然后输入坐标后,要返回一个信息:这个坐标周围有几个雷。我们第一个数组目前就存放这些东西。

第二个数组存放的信息就是雷的位置了,通过rand函数来随机存放我们需要的雷的个数

如果我们希望格子是7 * 7的,那我们必须让地图的大小变成9 * 9的。

这是因为,我们需要有一个检查玩家输入的这个坐标周围的信息,如果地图范围只有7 * 7的话,那是不是边缘的信息在检查的时候就越界了?这样的话我们直接往外扩大一圈,然后初始化成0是不是就解决了。
而且我们两个地图的大小必须相同,这样可以起到一个映射的效果,因为玩家在第一个地图输入的坐标,该位置也可以反映到第二个地图的位置上,然后通过检查第二个地图里该坐标周围的雷的个数,然后在显示出来。
在这里插入图片描述
只用红框里面这部分,外面不用。

放置雷的方法:

可以将第二个数组里的内容全部初始化成0,然后随机将某些位置的0变成1,这样是为了在判断一个坐标周围位置的雷的时候,可以把他们的坐标(每个坐标要减去‘0’)加起来返回。

二.游戏代码结构

2.1主程序

#include "game.h"

void menu()
{
	printf("************************\n");
	printf("******   1.play   ******\n");
	printf("******   0.exit   ******\n");
	printf("************************\n");
}

void game()
{
	char uvis[ROWS][COLS];//让玩家看不到的地图
	char vis[ROWS][COLS];//能让玩家看到的地图
	//初始化地图
	Init_Map(uvis, vis);

	//打印可看见的地图
	Print_map(vis);

	//打印不可看见的地图
	//Print_map(uvis);

	//布置雷
	Set_Beng(uvis);
	Print_map(uvis);

	while (1)
	{
		//输入坐标并判断是否为雷
		int flag = 0;
		flag = BengBeng(vis, uvis);
		if (flag == -1)
		{
			printf("很遗憾,你被炸死了\n");
			break;
		}
		else if (flag == 1)
		{
			system("cls");
			Print_map(vis);
			Print_map(uvis);
		}
		else
			break;
	}
}

int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));
	do
	{
		menu();
		printf("请输入->");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			system("cls");
			game();
			break;
		case 0:
			break;
		default:
			printf("输入错误,请重新输入:\n");
			break;
		}
	} while (input);
	return 0;
}

为了更好的维护写的代码,我把和实现游戏有关的代码全部写到了game.c这个文件里。现在我们先分析这个文件里的代码。

2.1.1main()函数

int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));
	do
	{
		menu();//菜单
		printf("请输入->");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			system("cls");
			game();
			break;
		case 0:
			break;
		default:
			printf("输入错误,请重新输入:\n");
			break;
		}
	} while (input);
	return 0;
}

main函数最外面的框架是do…while循环。循环里面是一个switch的选择语句。
我们通过玩家输入0,1来操作,如果输入1,则开始游戏,如果输入0就是退出游戏,如果输入其它就提示输入错误,重新输入。

srand((unsigned int)time(NULL));这行代码是为了和后面的rand函数相关联,到时候在解释,现在可以先跳过不用看。

system(“cls”);这是一个清屏的代码,主要是为了清理掉上次玩的游戏显示的界面,让画面变得更简洁。

2.1.2menu()函数

void menu()
{
	printf("************************\n");
	printf("******   1.play   ******\n");
	printf("******   0.exit   ******\n");
	printf("************************\n");
}

就是一个菜单,打印出来是这个效果:
在这里插入图片描述

2.1.3game()函数

void game()
{
    //ROWS,COLS我会在game.h头文件处解释
	char uvis[ROWS][COLS];//让玩家看不到的地图
	char vis[ROWS][COLS];//能让玩家看到的地图
	//初始化地图
	Init_Map(uvis, vis);

	//打印可看见的地图
	Print_map(vis);

	//打印不可看见的地图
	//Print_map(uvis);

	//布置雷
	Set_Beng(uvis);
	Print_map(uvis);

	while (1)
	{
		//输入坐标并判断是否为雷
		int flag = 0;
		flag = BengBeng(vis, uvis);
		if (flag == -1)
		{
			printf("很遗憾,你被炸死了\n");
			break;
		}
		else if (flag == 1)
		{
			system("cls");
			Print_map(vis);
			Print_map(uvis);
		}
		else
			break;
	}
}

游戏实现的步骤都在这里面,但是每个函数如何实现的放在了game.c文件里面。

在这里插入图片描述

2.2game.h

在这里我先把.h头文件里的内容给大家展示一下,以防一会在介绍.c文件里函数时,有些东西大家不知道。

//这是一会需要用到的库函数
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

//对一些参数做的宏定义

//ROW,COL分别是行和列,是你希望地图的大小
#define ROW    9
#define COL    9

//这两个是实际需要的行和列,刚在说过了,需要大一圈
#define ROWS   ROW + 2
#define COLS   COL + 2

//这是你希望设置的炸弹的个数
#define BENG   10

//下面就是一会需要用到的函数

//初始化地图
void Init_Map(char uvis[ROW][COLS], char vis[ROWS][COLS]);

//打印地图
void Print_map(char map[ROWS][COLS]);

//布置雷
void Set_Beng(char uvis[ROWS][COLS]);

//判断是否为雷
int BengBeng(char vis[ROWS][COLS], char uvis[ROWS][COLS]);

2.3game.c

#include "game.h"


//初始化地图
void Init_Map(char uvis[ROW][COLS], char vis[ROWS][COLS])
{
	//玩家看到的地图数组内容全部初始化成*
	//看不到的数组里初始化为空格
	int i = 0;
	int j = 0;
	for (i = 0; i < ROWS; i++)
	{
		for (j = 0; j < COLS; j++)
		{
			vis[i][j] = '*';
			uvis[i][j] = '0';
		}
	}
}

//打印地图
void Print_map(char map[ROWS][COLS])
{
	int i = 0;
	int j = 0;
	//打印列号
	for (j = 0; j <= COL; j++)
		printf("%d ", j);
	printf("\n");
	for (i = 1; i <= ROW; i++)
	{
		//打印行号
		printf("%d ", i);
		for (j = 1; j <= ROW; j++)
		{
			printf("%c ", map[i][j]);
			//map对应的那两个数组大小都是11*11,这里只是把一个数组中间9*9的拿出来
			//用来存放玩家输入的信息,所以存放的信息都是从1开始。
		}
		printf("\n");
	}
}

//布置雷
void Set_Beng(char uvis[ROWS][COLS])
{
	int amount = BENG;
	while (amount)
	{
		char x = rand() % ROW + 1;
		char y = rand() % COL + 1;
		if (uvis[x][y] == '0')
		{
			uvis[x][y] = '1';
			amount--;
		}
	}
}

int Count_Beng(int x, int y, char uvis[ROWS][COLS])
{
	//因为外边两圈全初始化成0了,行列数只是打印出来
	//并没有改变数组里的内容
	return uvis[x + 1][y] +
		   uvis[x - 1][y] +
		   uvis[x][y + 1] +
		   uvis[x][y - 1] +
		   uvis[x - 1][y - 1] +
		   uvis[x - 1][y + 1] +
		   uvis[x + 1][y + 1] +
		   uvis[x + 1][y - 1] - 8 * '0';
		   
}

//判断是否为雷
int BengBeng(char vis[ROWS][COLS], char uvis[ROWS][COLS])
{
	while(1)
	{
		//玩家输入坐标
		printf("请输入坐标->");
		int x = 0;
		int y = 0;
		scanf("%d %d", &x, &y);
		int count = 0;
		int count1 = 0;

		if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
		{
			if(uvis[x][y] != '1')
			{
				vis[x][y] = Count_Beng(x, y, uvis) + '0';
				system("cls");
				Print_map(vis);
				Print_map(uvis);
				count1++;
			}
			else
			{
				return -1;
			}
		}
		else
		{
			printf("输入不合法\n");
		}
		if (count1 == ROW * COL - BENG)
		{
			printf("很遗憾,你没被炸死\n");
			return 0;
		}
		return 1;
	}
}

2.3.1初始化地图Init_Map()

void Init_Map(char uvis[ROW][COLS], char vis[ROWS][COLS])
{
	//玩家看到的地图数组内容全部初始化成*
	//看不到的数组里初始化为空格
	int i = 0;
	int j = 0;
	for (i = 0; i < ROWS; i++)
	{
		for (j = 0; j < COLS; j++)
		{
			vis[i][j] = '*';
			uvis[i][j] = '0';
		}
	}
}

用两个for循环把二维数组都遍历一遍。

2.3.2打印地图Print_map()

先给你们看下地图的样子:
在这里插入图片描述

void Print_map(char map[ROWS][COLS])
{
	int i = 0;
	int j = 0;
	//打印列号
	for (j = 0; j <= COL; j++)
		printf("%d ", j);
	printf("\n");
	for (i = 1; i <= ROW; i++)
	{
		//打印行号
		printf("%d ", i);
		for (j = 1; j <= ROW; j++)
		{
			printf("%c ", map[i][j]);
			//map对应的那两个数组大小都是11*11,这里只是把一个数组中间9*9的拿出来
			//用来存放玩家输入的信息,所以存放的信息都是从1开始。
		}
		printf("\n");
	}
}

先要在第一行把列号打印出来,也就是i=0的位置上。
后面每一行在打印之前也就是每一行的j=0的位置上把每一行的行号打印出来。
剩下的位置就是数组里的元素的位置,但是记住我们是从坐标[1,1]开始打印的。就是把11 * 11其中9 * 9位置上的信息打印出来。玩家在输入坐标的时候也是从[1,1]-[ROW,COL]。
行,列坐标是直接打印出来的,不会影响我们外面那一圈的内容。

2.3.3布置雷Set_Beng()

void Set_Beng(char uvis[ROWS][COLS])
{
	int amount = BENG;
	while (amount)
	{
		char x = rand() % ROW + 1;
		char y = rand() % COL + 1;
		if (uvis[x][y] == '0')
		{
			uvis[x][y] = '1';
			amount--;
		}
	}
}

这里的rand函数是和刚在main函数里的srand函数一起用的。
srand()括号里面的内容里可以随便写一个值,可以把它当成种子,如果没有就默认为1,rand()每次用的时候都会检查是否调用过srand()函数,如果有就会产生一个随机值。但是这个种子如果不变的话,rand函数产生的随机值就固定了,换句话说,他就第一次随机,以后的值和第一次的一样。
这样的话我们就必须让srand函数里面的种子也是个随机值/不同值。这样就麻烦了,这不是套娃吗?所以为了满足我们的需求,我们通过time()函数来当作种子。
time():获取当前日历时间作为time_t类型的值.
我们直接把时间当成种子带进去,时间可不是固定的每分每秒都在运行。这样就会让我们每秒产生的随机值都不一样

因为time函数的返回值是time_t类型的,而srand需要的类型是unsigned int类型的,所以我们在使用时要强制类型转换。
time函数的参数填NULL空指针就行。

但是这个随机值的取值返回太大,所有我们必须把它限制在我们想要的范围来作为炸弹的坐标,我们发现炸弹的坐标应该在1 ~ ROW,和1 ~ COL之间。而一个任意数%x,这个取值范围是0 ~ x-1.所以为了让炸弹的坐标在我们希望的范围中就给%之后的值+1.

再布置炸弹的前提下是必须这个位置上的内容是‘0’,因为我们初始化的时候就全设为’0’,这样防止重复布置雷。我们将布置成功后那个坐标的内容置成‘1’。然后没布置成功一个amount–。布置的个数是我们需要的数量就停下来。

2.3.4判断是否为雷BengBeng()

int BengBeng(char vis[ROWS][COLS], char uvis[ROWS][COLS])
{
	while(1)
	{
		//玩家输入坐标
		printf("请输入坐标->");
		int x = 0;
		int y = 0;
		scanf("%d %d", &x, &y);
		int count = 0;
		int count1 = 0;

		if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
		{
			if(uvis[x][y] != '1')
			{
				vis[x][y] = Count_Beng(x, y, uvis) + '0';
				system("cls");
				Print_map(vis);
				//Print_map(uvis);
				count1++;
			}
			else
			{
				return -1;
			}
		}
		else
		{
			printf("输入不合法\n");
		}
		if (count1 == ROW * COL - BENG)
		{
			printf("很遗憾,你没被炸死\n");
			return 0;
		}
		return 1;
	}
}

玩家每走一步,我们都判断一下结果。

首先判断的是玩家输入的坐标是否合法,只有合法了,才能真正的判断这个位置是否为雷,或者这个位置周围有多少雷。

如果这一个位置的坐标不是雷,我们就通过函数Count_Beng来计算这个位置周围有多少雷,然后记录并打印下来,让玩家知道。
如果这一个位置的坐标就是雷的话,之间返回-1

玩家每成功下好一步,变量count1就++,这样的话在最后判断count1是否等于ROW * COL - BENG。
ROW * COL - BENG是棋盘的所有坐标之和-炸弹的个数,也就是地图上玩家所有不是炸弹的坐标的个数,如果满了就返回0说明,游戏结束,玩家赢了。

如果既没有返回0,也没有返回-1,说明游戏还要继续,还没结束,这样我们就返回1,以供game()函数里判断。

用函数Count_Beng计算出来的结果要+‘0’。因为我们返回的是一个整型,而数组里存放的是一个字符,所以+‘0’,让数字变成字符,这样才能打印出来让玩家看到。

2.3.5计算坐标周围雷的个数Count_Beng

int Count_Beng(int x, int y, char uvis[ROWS][COLS])
{
	//因为外边两圈全初始化成0了,行列数只是打印出来
	//并没有改变数组里的内容
	return uvis[x + 1][y] +
		   uvis[x - 1][y] +
		   uvis[x][y + 1] +
		   uvis[x][y - 1] +
		   uvis[x - 1][y - 1] +
		   uvis[x - 1][y + 1] +
		   uvis[x + 1][y + 1] +
		   uvis[x + 1][y - 1] - 8 * '0';		   
}

我们把该坐标周围8个位置的坐标- 8 * ‘0’加起来之间返回即可,因为我们当初布置雷的时候把雷的位置里的内容记成了’1’,如果减去’0’也就是数字1,周围有几个雷,就有几个1,全加起来就是雷的个数。
在这里插入图片描述

三.结尾

以上就是扫雷的全部内容了。源码在上面已经列出来了,需要的可以自取。但是为了看到自己布置雷的位置,所以把玩家不该看到的地图也打印出来了,你们如果不想要注释掉就行。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值