C语言小游戏第二弹——扫雷

前言

  • 当我们学习完C语言中二维数组和函数这一块的知识以后,我们就可以编写一些小游戏了,比如说五子棋,扫雷,本文将讲述如何通过C语言实现扫雷,想学习如何实现五子棋的伙伴可以看看我的上一篇文章
  • 链接:在15*15的棋盘中下五子棋

以下是本篇文章正文内容,下面案例可供参考

一:游戏框架的书写

  • 我们要把这个游戏的框架分为三个文件,分别是game.c,game.h,test.c
  • 在game.c中我们实现我们需要的函数的定义
  • 在game.h中实现常量的定义和函数的声明和需要的头文件的引用
  • 在test.c中实现游戏的主体框架
  • 首先我们先看看test.c的完成品,才来说如何实现
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void game()
{
	//数组的创立
	char mine[ROWS][COLS];
	char show[ROWS][COLS];
	//初始化棋盘
	Initboard(mine, ROWS, COLS, '0');
	Initboard(show, ROWS, COLS, '*');
	//展示棋盘
	Displayboard(show, ROW, COL);
	//埋雷
	Setmine(mine, ROW, COL);
	//Displayboard(mine, ROW, COL);
	//排雷
	Findmine(mine, show, ROW, COL);
	
}
void menu()
{
	printf("************************\n");
	printf("******   1.play   ******\n");
	printf("******   0.exit   ******\n");
	printf("************************\n");
}

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

1.main函数

  • 我们首先看到main函数,抛开srand那一行先不谈,剩下的do while语句实现的是接收玩家输入的值,然后根据值进入不同的接口
  • 如果是1则进入game函数开始游戏,如果是0则退出游戏
  • 为了让玩家知道应该输入什么进行和退出游戏,我们首先可以打印一个菜单,就是menu函数
  • 我们还需要考虑到玩家可能输入除0和1以外的其它值,对此做出判断,让玩家重新输入
  • 所以while后的括号中填input就完美实现了这一点,如果是0为假则循环结束
  • 如果是非零则进入循环,再通过switch语句判断是1还是其它值
  • do while则保证了至少一次输入

2.game函数

  • main函数实现之后,游戏的大体框架就基本完成,剩下我们需要往里面填充血肉
  • 这个血肉其实就是一个个函数,其中最重要的函数就是game函数
  • 而game函数又包括了好几个子函数
  • 我们思考一下game函数需要哪些子函数才能完成它应有的功能
  • 首先我们需要一个棋盘,所以我们需要一个初始化棋盘函数Initboard
  • 重点来了,其实我们需要两个棋盘,因为我们需要一个用来布置雷,另一个才是玩家看到的棋盘,上面一开始是空白,随着玩家的输入,棋盘上会出现数字告诉玩家周围有多少雷
  • 想一想,如果我们只有一个棋盘,那怎么做到记住雷的位置的同时又显示周围有多少雷呢,而我们有两个棋盘的话,就会变得十分容易
  • 我们把埋雷的棋盘叫做mine,用于显示的棋盘叫做show
  • 现在讨论一下棋盘大小的问题
  • 假设我们要实现9*9的扫雷
  • 我们棋盘大小应该设置成11*11
  • 因为在后面为了告诉玩家周围8个格子有多少雷的时候,我们需要遍历玩家给出的坐标附件的8个格子,才能得到周围雷的个数,设置成11*11的棋盘可以避免当玩家给出的坐标在边界时,造成的越界访问问题。
    请添加图片描述
  • 虽然我们棋盘大小是11*11
  • 但我们实际进行游戏的棋盘还是9*9,为了后面方便修改我们棋盘的大小,我们可以使用#define定义ROW,COL为9,ROWS,COLS为11
  • 而前文说了有关常量的定义我们放在game.h中,是为了方便另外两个文件的使用
  • 创建完棋盘以后,我们就需要初始化棋盘,也就是实现Initboard函数
  • 初始化棋盘之后,我们需要展示棋盘给玩家,就需要Display函数
  • 为了给我们的mine数组中埋雷,我们需要Setmine函数
  • 最后让玩家排雷,就需要Findmine函数
  • 到此为止,game函数的框架我们也定下来了
  • 剩下的只需要实现我们所需要的函数,扫雷就大功告成了。

二、函数的声明

  • 在实现我们需要的函数之前,我们先在game.h中对其进行声明
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define COL 9
#define ROW 9
#define ROWS ROW+2
#define COLS COL+2
#define level 10
//函数的声明
//初始化棋盘函数
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);
//
  • 其中第一行的#pragma once是为了防止头文件的多次引用
  • 常量level代表我们所需要埋雷的数量

三:game.c中函数的实现

1.Initboard函数实现

  • 这个函数的实现非常简单,不过对于mine数组和show数组要有不同的初始化

  • 我们把mine数组的值全部初始化为0,代表现在所有位置都只有0个雷

  • 当后续调用Setmine函数进行埋雷时再把mine数组中随机10个位置改为1,代表这10个位置有雷

  • 对于show数组我们全部初始化为*,代表未知,玩家需要输入坐标获取某一位置的信息

  • 要是这个坐标是雷,则游戏结束,不是则把*改为数字,代表周围雷的个数

  • 所以函数的参数就有4个,一个是board数组用于接收mine或show数组

  • rows和cols分别代表所需要初始化的行列数

  • set代表所需要初始化的值,mine是0,show是*

  • 代码如下

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.Displayboard函数实现

  • 这个函数的作用就是打印棋盘的内容,不过我们需要打印行索引和列索引方便玩家查看行列

  • 同时还需要通过-字符打印个框架,其实没有这一步也可,不过打印上看起来更加舒适

  • 成品如下:
    请添加图片描述

  • 代码如下:

void Displayboard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i <= col; i++)
	{
		printf("%3d", i);
	}
	printf("\n");
	for (i = 0; i < 3 * (COL+1); i++)
	{
		printf("-");
	}
	printf("\n");
	for (i = 1; i <= row; i++)
	{
		printf("%2d|", i);
		for (j = 1; j <=col; j++)
		{
			printf("%3c", board[i][j]);
		}
		printf("\n");
	}
	for (i = 0; i < 3 * (COL + 1); i++)
	{
		printf("-");
	}
	printf("\n");
}

3.Setmine函数实现

  • 这个函数作用就是将mine数组中10个位置的0改为1,代表埋雷
  • 具体实现就可以依靠rand函数,不过这个函数需要一个种子,就是srand函数
  • 并且只需要一次,所以我们将它放在主函数中,还记得吗
  • srand((unsigned int)time(NULL));
  • 之后我们就可以通过rand函数生成随机数进行埋雷
  • 不过为了保证数字的有效性,我们需要将得到的随机数的值修改
  • 将这个数对行或列取余,然后加一,取余很好理解,加一是为什么呢
  • 首先取余保证这个数在0-8之间,加一则让这个数在1-9之间,是因为我们实际的棋盘大小为11*11
  • 而使用的只有9*9,将随机数限制在1-9之间刚好能够保证它在我们实际使用的棋盘上
  • 另外我们可能生成相同的随机数,所以每次对mine数组的值做出判断
  • 如果是0则将其改成1,并让count–,如果是1则不修改
  • 这样当count为0时我们就埋好10个雷了
  • 代码如下
void Setmine(char mine[ROWS][COLS], int row, int col)
{
	int count = level;
	while (count)
	{
		int i = rand() % row + 1;
		int j = rand() % col + 1;
		if (mine[i][j] == '0')
		{
			mine[i][j] = '1';
			count--;
		}
	}
}

4.getmine函数实现

  • 这个函数的作用并未在game函数中出现,因为这是一个辅助函数
  • 作用是找到玩家输入的坐标周围的8个格子有几个雷,并返回雷的个数,我们将在Findmine函数中使用它
  • 代码如下
int getmine(char mine[ROWS][COLS], int x, int y)
{
	int i = 0;
	int j = 0;
	int sum = 0;
	if (x <= ROW && y <= COL&&x>=1&&y>=1)
	{
		for (i = x - 1; i <= x + 1; i++)
		{
			for (j = y - 1; j <= y + 1; j++)
			{
				if (i == x && j == y)
				{
					continue;
				}
				sum += mine[i][j] - '0';
			}
		}
	}
	return sum;
	
}

5.moreinformation函数实现

  • 这个函数是扫雷游戏最重要的一个辅助函数了,作用是成片展开show数组告诉玩家这些位置有多少雷
  • 前面的getmine函数只能返回一个坐标附近雷的个数,而这里能展开一片
  • 我们为什么需要展开一片呢?玩过扫雷的都知道,当我们点中一个位置,如果这个位置一个雷都没有,它将继续展开周围的格子告诉我们雷的个数,直到某个位置周围有1个或一个以上的雷才停止
  • 具体实现时就是通过递归函数,首先玩家输入一个坐标,我们用getmine获取这个坐标周围有多少雷,如果为0个雷,我们就调用moreinformation函数,就是对这个坐标周围8个坐标继续调用getmine函数返回雷的个数,如果是0则继续,不是0则停止
  • 其中递归终止的条件我们确定了,但还有一点我们需要注意,就是判断一个格子是否已经调用过getmine函数,也就是是否已经返回了雷的个数,如果是我们需要跳过这一次递归,否则会造成死递归
    请添加图片描述
  • 举个栗子,假设我们对x,y坐标处调用getmine函数返回了0,我们需要调用moreinformation对x,y周围8个坐标再次调用getmine获取雷的个数
  • 我们首先从x-1,y-1这个位置开始调用,如果发现这个位置返回的雷的个数仍是0,我们又需要对x-1,y-1这个位置调用moreinformation函数,然后这时x,y在x-1,y-1周围8个坐标中,我们又会对x,y调用getmine函数,而这个地方前面已经确定是0,所以又会调用moreinformation函数,然后又会对x-1,y-1这个位置调用getmine函数发现是0,继续调moreinformation函数,这时候又会回来对x,y调用getmine,然后调用moreinformation
  • 这就陷入了死递归
  • 所以我们每次需要对show数组的值进行判断,如果不是*,代表已经调用过getmine,就跳过这次递归
  • 其中ret代表一个位置雷的个数,如果是0,则终止递归
  • 代码如下:
int moreinformation(char show[ROWS][COLS], char mine[ROWS][COLS],int ret,int x,int y)
{
	int i = 0;
	int j = 0;
	if(ret)
	{
		return 0;
	}
	if (!ret)
	{
		
		for (i = x - 1; i <= x + 1; i++)
		{
			
			for (j = y - 1; j <= y + 1; j++)
			{
				if (show[i][j] != '*')
					continue;
				if ((i == x && j == y) || i>ROW || j>COL||i<1||j<1)
				{
					continue;
				}
				show[i][j] = '0' + getmine(mine, i, j);
				moreinformation(show, mine, show[i][j] - '0', i, j);
			}
		}
		
	}
	
}
  • 实现后效果如下请添加图片描述

6.victory函数实现

  • 这个函数的作用是判断棋盘上标记雷的数量,并且判断玩家标记雷的位置和埋雷的位置是否一致,如果一致,则让sum++,最终返回sum的值
  • 当sum的值和我们埋下的雷的值一致时,则可以判定排雷成功,游戏结束
  • 代码如下:
int victory(char show[ROWS][COLS],char mine[ROWS][COLS])
{
	int i = 0;
	int j = 0;
	int sum = 0;
	for (i = 1; i <= ROW; i++)
	{
		for (j = 1; j <= COL; j++)
		{
			if (mine[i][j] == '1')
			{
				if (show[i][j] == '#')
					sum++;
			}
		}
	}
	return sum;
}

7.Findmine函数实现

  • 这个函数的作用就是让玩家排雷,首先我们提供给玩家两个选择
  • 一个是1-排雷,另一个是0-标记雷
  • 我们还需要对玩家输入的值进行判断,如果不是1或0,则让玩家重新输入

(1)排雷

  • 如果输入1,则进入排雷,让玩家输入他想排雷的坐标,先判断坐标合法性,
  • 如果合法,则判断玩家输入的坐标是否是雷,就是判断mine数组的这个位置是不是1,如果是1,代表这个位置是雷,则玩家排雷失败
  • 如果是0,代表这个位置不是雷,我们继续判断这个位置对应的show数组的值
  • 如果是#,则询问玩家是否需要取消标记
  • 如果是数字,则告诉玩家这个地方已经排查过
  • 如果是*,我们就调用getmine函数返回这个位置附近雷的个数
  • 如果getmine返回的是0,则调用moreinformation函数实现成片展开
  • 之后展现棋盘,再调用victory函数判断此时棋盘上有多少个标记的雷
  • 如果sum==10,则排雷成功游戏结束

(2)标记雷

  • 如果玩家输入0,则进入标记雷,让玩家输入他想标记的坐标,判断坐标合法性,再判断show数组这个位置是否是*,如果是,则将其改为#,代表标记这个位置是雷
  • 标记成功后调用victory函数,让sum的值改变
  • 如果不是*,再判断是数字还是#,如果是数字,提醒玩家这里是已经排查过的坐标,
  • 如果是#,则询问玩家是否需要取消标记,如果玩家输入1,则代表玩家需要取消标记,我们就将这个位置的#改为*
  • 最后判断sum的值是否为10,如果为10,代表玩家已经排了10个雷,则玩家胜利
  • 代码如下:
void Findmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int a = 0;
	int b = 0;
	int choose=0;
	int c = 0;
	int sum = 0;
	while (sum<10)
	{
		printf("排查雷请按1,标记雷请按0\n");
		scanf("%d", &choose);
			if (choose != 0 && choose != 1)
			{
				while (1)
				{
					printf("无效数字,请重新输入>\n");
					scanf("%d", &choose);
					if (choose == 0 || choose == 1)
					{
						break;
					}
				}
			}
			if (!choose)
			{
				printf("请输入要标记雷的坐标>示例:1 1\n");
				scanf("%d %d", &a, &b);
				if (a >= 1 && a <= row && b >= 1 && b <= col)
				{
					if (show[a][b] == '*')
					{
						show[a][b] = '#';
						printf("标记成功\n");
						sum = victory(show, mine);
						Displayboard(show, ROW, COL);
					}
					else if (show[a][b] == '#')
					{
						printf("这个地方是你标记的雷,你需要取消吗?\n");
						printf("取消标记请按1,不取消标记请按0\n");
						scanf("%d", &c);
						if (c != 0 && c != 1)
						{
							while (1)
							{
								printf("无效数字,请重新输入>\n");
								scanf("%d", &c);
								if (c == 0 || c == 1)
								{
									break;
								}
							}
							if (c == 1)
							{
								show[x][y] = '*';
								printf("取消标记成功!");
								Displayboard(show, ROW, COL);
							}
						}
					}
					else
						printf("此处是已排查雷的坐标,请勿标记!\n");
					continue;
				}
				else
				{
					printf("坐标输入错误,请重新输入\n");
				}
			}
		printf("请输入要排查雷的坐标> 示例:5 5\n");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (mine[x][y] == '1')
			{
				printf("很遗憾,你被炸死了\n");
				Displayboard(mine, ROW, COL);
				break;
			}
			else if(show[x][y]!='*')
			{
				if (show[x][y] == '#')
				{
					printf("这个地方是你标记的雷,你需要取消吗?\n");
					printf("取消标记请按1,不取消标记请按0\n");
					scanf("%d", &c);
					if (c != 0 && c != 1)
					{
						while (1)
						{
							printf("无效数字,请重新输入>\n");
							scanf("%d", &c);
							if (c == 0 || c == 1)
							{
								break;
							}
						}
						if (c == 1)
						{
							show[x][y] = '*';
							printf("取消标记成功!");
							Displayboard(show, ROW, COL);
						}
					}
				}
				else 
				{
					printf("此处已排查过,请勿重复输入\n");
				}
			}
			else
			{
				int ret = getmine(mine,x,y);
				show[x][y] = '0' + ret;
				int t=moreinformation(show, mine, show[x][y]-'0', x, y);
				Displayboard(show, ROW, COL);
				sum = victory(show,mine);
			}
		}
		else
		{
			printf("坐标输入错误,请重新输入\n");
		}
	}
	if (sum == level)
	{
		printf("恭喜你,排雷成功\n");
		Displayboard(mine, ROW, COL);
	}

}
  • 10
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dhdw

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值