C语言实现扫雷小游戏

实现思路

回忆一下,我们曾经在windows上玩的小游戏,可以知道,我们需要一个数组布置雷。另外当我们进行扫雷时,每进行一次,我们需要更新一次扫雷时的雷阵信息。因此我们需要两个二维数组。mine[ ][ ]数组用于存放布置雷时的信息。show[ ][ ]用来进行扫雷时,更新雷阵的信息。这里我们需要注意的是: 当进行扫雷时,如果没有雷,需要统计周围八个位置雷的数量,并在show[ ][ ]数组中显示。这里就需要注意一个问题,怎样统计周围边缘部分的雷呢?我们不妨将数组周围扩大一圈。布置雷时周围一圈不放置雷。这样以来,在排查雷,打印雷的信息时,我们不打印外围的一圈。(例如:我们定义二维数组元素个数是11x11,而实际打印时,仅打印9x9的数组)下面我们梳理一下扫雷的流程。开始时,第一次扫雷,如果踩雷,为了增加游戏的可玩性,我们将雷重新布置位置,将利用一个NewSetMine()函数,重新布置第一个雷。如果不是第一次踩雷,那么很遗憾游戏结束。在扫雷的过程中,如果没有雷需要统计周围八个位置雷的数量,并显示。如果周围也没有雷,我们就用递归函数将雷阵展开。以此类推,直到排查出所有的雷游戏结束。
难点1 在扫雷过程中,第一次踩雷,要重新布置雷。
难点2 当周围没有雷时,要递归展开。

具体实现

我们采取分文件编写。分别新建 test.c源文件,game.h头文件,game.c源文件。
test.c中包含程序的整体框架。game.h中存放函数的定义。game.c中是对于自定义函数的具体实现。
在这里插入图片描述

主体框架

#define _CRT_SECURE_NO_WARNINGS 1

#include"game.h"
void memu()
{
  printf("**********************\n");
  printf("****    1.play    ****\n");
  printf("****    0.exit    ****\n");
  printf("**********************\n");

}

int main()
{
  int input = 0;

  do
  {
  	memu();
  	printf("请选择:>\n");
  	scanf("%d", &input);
  	switch (input)
  	{
  	case 1:
  		printf("扫雷游戏\n");
  		break;
  	case 0:
  		printf("退出游戏\n");
  		break;
  	default:
  		printf("输入有误重新输入\n");
  		break;
  	}


  } while (input);

  return 0;
}

效果

在这里插入图片描述

函数细节实现

接下来定义数组

//定义mine[][]用于存放布置雷的信息
char mine[ROWS][COLS] = {0};
//定义show[][]用于存放排查雷后每一次更新的信息
char show[ROWS][COLS] = {0};

值得注意的是ROWS COLS 是宏定义方便更改数组的大小

创建InitBoard()函数对数组进行初始化

InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');

在game.h文件中写入对初始化函数的定义

void InitBoard(char* board[ROWS][COLS],int row,int col,char ch);

在game.c文件中对其进行实现

//初始化函数,分别初始化mine、show 数组
void InitBoard(char board[ROWS][COLS], int row, int col, char ch)
{
	int i = 0;

	for (i = 0; i < row; ++i)
	{
		int j = 0;
		for (j = 0; j < col; ++j)
		{
			board[i][j] = ch;
		}
	}

}

创建打印函数PrintBoard()并测试打印后的效果

PrintBoard(mine, ROW, COL);
PrintBoard(show, ROW, COL);

同样的道理,在game.h中定义,在game.c中具体实现,在以下的每个函数就不在一一强调

void PrintBoard(char board[ROWS][COLS], int row, int col);
//打印函数
void PrintBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;

	printf("-------------------------\n");

	for (i = 0; i <= row; ++i)
	{
		printf("%d ", i);
	}
	printf("\n");

	for (i = 1; i <= row; ++i)
	{
		int j = 0;

		printf("%d ", i);

		for (j = 1; j <= col; ++j)
		{
			printf("%c ", board[i][j]);
		}

		printf("\n");

	}

	printf("-----------------------------\n");
}

我们来看一下效果
在这里插入图片描述
mine 数组初始化时全部放了字符’0’,而 show 数组全部放了字符’*’。

创建SetMine()函数布置雷

这里我们仅布置10个雷。在game.h文件中采取宏定义的方式

#define EASY_COUNT 10

定义SetMine()函数

void SetMine(char board[ROWS][COLS], int row, int col);

实现SetMine()函数

void SetMine(char board[ROWS][COLS], int row, int col)
{
	int count = EASY_COUNT;

	int x = 0;
	int y = 0;

	while (count)
	{
		x = rand() % row + 1;
		y = rand() % col + 1;

		if (board[x][y] != '1')
		{
			board[x][y] = '1';
			count--;
		}

	}
	

}

我们注意到以上的函数是采用随机的方式布置10个雷。这就离不开随机数生成器。不要忘记在主函数中加入下面一行代码:

srand((unsigned int)time(NULL));

创建函数FindMine()实现排雷的过程

接下来就是最重要的。我们要开始排雷了。这才是游戏的关键所在。

void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS],int row, int col);

FindMine()函数实现

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int flag = 0;//flag = 0表示第一次就踩到雷

	while (1)
	{
		printf("请输入要排查的坐标:>\n");

		scanf("%d%d", &x, &y);

		//判断坐标是否合法

		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (mine[x][y] == '1' && flag == 0)
			{
				//此时表示第一次踩到雷,为了增加游戏的可玩性,我们让游戏第一次踩不到雷。
				//将雷移到别的地方。
				flag = 1;

				mine[x][y] = '0';

				//用NewSetMine()函数重新布置第一个雷

				NewSetMine(mine,row,col, x, y);

				//OpenMine()递归函数统计x,y 坐标外8个位置是否有雷如果有雷就展开。

				OpenMine(mine, show, x, y);

				//打印排查后雷阵的信息

				PrintBoard(show, row, col);

			}
			else if (mine[x][y] == '1' && flag == 1)
			{
				//此时表示不是第一次踩到雷,那么游戏结束 break 退出游戏

				printf("很遗憾你被炸死了!\n");

				printf("雷的分布如下:\n");

				PrintBoard(mine, row, col);
				break;

			}
			else
			{
				//此时没有踩到雷,游戏继续
				flag = 1;
				OpenMine(mine, show, x, y);

				PrintBoard(show, row, col);

			}


		}
		else
		{
			printf("坐标不合法!请重新输入:>\n");
		}



	}


	if (IsWin(show, row, col) == EASY_COUNT)
	{
		printf("恭喜你!你赢了\n");
		printf("雷的分布如下:\n");
		PrintBoard(mine, row, col);

		
	}
	

}

我们注意到,上述的函数中,包含了四个函数没有实现。分别是,第一踩雷时,我们要从新布置雷,用到了 NewSetMine()函数,没有排查到雷时,用到的递归展开函数 OpenMine(),统计雷的个数GetMineCount()函数,以及判断输赢函数 IsWin()。这里需要说明: 我在实现在这些函数时,直接就在调用函数之前就实现了,因此没有在game.h中定义函数。如果想看起来规整,可以在.h文件中声明。下面来具体实现一下以上四个函数。

重新设置第一个雷用到的函数NewSetMine()
void NewSetMine(char mine[ROWS][COLS],int row,int col, int x, int y)
{
   int count = 1;

   

   while (count)
   {
   	int a = rand() % row + 1;
   	int b = rand() % col + 1;

   	if (mine[a][b] != '1' && x != a && y != b)
   	{
   		mine[a][b] = '1';

   		count--;
   	}


   }

}
递归展开函数OpenMine()
void OpenMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x,  int y)
{
	//统计周围8个位置雷的个数如果为0则递归展开,否则 不展开仅统计个数

	if (GetMineCount(mine, x, y) == 0)
	{
		//递归展开
		//现将x,y 中心位置置为空格

		show[x][y] = ' ';

		if (x - 1 > 0 && y > 0 && show[x - 1][y] == '*')
		{
			OpenMine(mine, show, x-1, y);
		}
		if (x - 1 > 0 && y - 1  > 0 && show[x - 1][y - 1] == '*')
		{
			OpenMine(mine, show, x-1, y-1);
		}
		if (x - 1 > 0 && y + 1  > 0 && show[x - 1][y + 1] == '*')
		{
			OpenMine(mine, show, x-1, y+1);
		}
		if (x  > 0 && y-1 > 0 && show[x][y-1] == '*')
		{
			OpenMine(mine, show, x, y-1);
		}
		if (x > 0 && y+1 > 0 && show[x][y+1] == '*')
		{
			OpenMine(mine, show, x, y+1);
		}
		if (x + 1 > 0 && y > 0 && show[x + 1][y] == '*')
		{
			OpenMine(mine, show, x+1, y);
		}
		if (x + 1 > 0 && y-1 > 0 && show[x + 1][y-1] == '*')
		{
			OpenMine(mine, show, x+1, y-1);
		}
		if (x + 1 > 0 && y+1 > 0 && show[x + 1][y+1] == '*')
		{
			OpenMine(mine, show, x+1, y+1);
		}



	}
	else
	{
		//不展开仅统计个数

		show[x][y] = GetMineCount(mine, x, y) + '0';

	}


}

另外附一张展开时的图片看一下展开效果。
在这里插入图片描述

统计雷的个数函数GetMineCount()
int GetMineCount(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');
}

或许这里对于像我一样刚学习C语言的小伙伴不知道为什么反回的位置有 - 8*‘0’。
这里要说一下,在c语言中数字字符减去字符零会得到整形的数字。例如字符’5’-‘0’=5,这个5就是整形 我们定义的数组是char 形数组,存放的是字符,在统计雷的个数时,存放字符 ‘1’ 表示有雷,字符 ‘0’ b表示没有雷,这样周围的8个字符加起来减去 8*‘0’ 就的到了整形数字,return 返回,返回值类型为 int 。

判断输赢函数IsWin()
int IsWin(char show[ROWS][COLS], int row, int col)
{
	int i = 0;

	int count = 0;
	for (i = 1; i <= row; ++i)
	{
		int j = 0;
		for (j = 1; j <= col; ++j)
		{
			if (show[i][j] == ' * ')
				count++;

		}
	}

	return count;

}

以上我们实现了所有的函数模块。说点题外话,本人也是刚刚学习C语言,对于扫雷的认知,仅限于此。其中或许有不当的地方,希望大家谅解,仅供大家参考。另外欢迎大家批判指正。你的建议会是我坚持下去的动力。

接下来附上 game.h game.c test.c的C语言源码供大家整体把握实现的思路。

game.h文件

#pragma once

#include<stdio.h>

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

#define ROW 9
#define COL 9

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

#define EASY_COUNT 10

void InitBoard(char board[ROWS][COLS], int row, int col, char ch);

void PrintBoard(char board[ROWS][COLS], int row, int col);

void SetMine(char board[ROWS][COLS], int row, int col);

void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS],int row, int col);

game.c文件

#define _CRT_SECURE_NO_WARNINGS 1

#include"game.h"


//初始化函数,分别初始化mine、show 数组
void InitBoard(char board[ROWS][COLS], int row, int col, char ch)
{
	int i = 0;

	for (i = 0; i < row; ++i)
	{
		int j = 0;
		for (j = 0; j < col; ++j)
		{
			board[i][j] = ch;
		}
	}

}

//打印函数
void PrintBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;

	printf("-------------------------\n");

	for (i = 0; i <= row; ++i)
	{
		printf("%d ", i);
	}
	printf("\n");

	for (i = 1; i <= row; ++i)
	{
		int j = 0;

		printf("%d ", i);

		for (j = 1; j <= col; ++j)
		{
			printf("%c ", board[i][j]);
		}

		printf("\n");

	}

	printf("-----------------------------\n");


}

void SetMine(char board[ROWS][COLS], int row, int col)
{
	int count = EASY_COUNT;

	int x = 0;
	int y = 0;

	while (count)
	{
		x = rand() % row + 1;
		y = rand() % col + 1;

		if (board[x][y] != '1')
		{
			board[x][y] = '1';
			count--;
		}

	}
	

}

void NewSetMine(char mine[ROWS][COLS],int row,int col, int x, int y)
{
	int count = 1;

	

	while (count)
	{
		int a = rand() % row + 1;
		int b = rand() % col + 1;

		if (mine[a][b] != '1' && x != a && y != b)
		{
			mine[a][b] = '1';

			count--;
		}


	}

}

//统计周围雷的个数

int GetMineCount(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');
}




//递归函数展开雷阵信息

void OpenMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x,  int y)
{
	//统计周围8个位置雷的个数如果为0则递归展开,否则 不展开仅统计个数

	if (GetMineCount(mine, x, y) == 0)
	{
		//递归展开
		//现将x,y 中心位置置为空格

		show[x][y] = ' ';

		if (x - 1 > 0 && y > 0 && show[x - 1][y] == '*')
		{
			OpenMine(mine, show, x-1, y);
		}
		if (x - 1 > 0 && y - 1  > 0 && show[x - 1][y - 1] == '*')
		{
			OpenMine(mine, show, x-1, y-1);
		}
		if (x - 1 > 0 && y + 1  > 0 && show[x - 1][y + 1] == '*')
		{
			OpenMine(mine, show, x-1, y+1);
		}
		if (x  > 0 && y-1 > 0 && show[x][y-1] == '*')
		{
			OpenMine(mine, show, x, y-1);
		}
		if (x > 0 && y+1 > 0 && show[x][y+1] == '*')
		{
			OpenMine(mine, show, x, y+1);
		}
		if (x + 1 > 0 && y > 0 && show[x + 1][y] == '*')
		{
			OpenMine(mine, show, x+1, y);
		}
		if (x + 1 > 0 && y-1 > 0 && show[x + 1][y-1] == '*')
		{
			OpenMine(mine, show, x+1, y-1);
		}
		if (x + 1 > 0 && y+1 > 0 && show[x + 1][y+1] == '*')
		{
			OpenMine(mine, show, x+1, y+1);
		}



	}
	else
	{
		//不展开仅统计个数

		show[x][y] = GetMineCount(mine, x, y) + '0';

	}


}

int IsWin(char show[ROWS][COLS], int row, int col)
{
	int i = 0;

	int count = 0;
	for (i = 1; i <= row; ++i)
	{
		int j = 0;
		for (j = 1; j <= col; ++j)
		{
			if (show[i][j] == ' * ')
				count++;

		}
	}

	return count;

}



void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int flag = 0;//flag = 0表示第一次就踩到雷

	while (1)
	{
		printf("请输入要排查的坐标:>\n");

		scanf("%d%d", &x, &y);

		//判断坐标是否合法

		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (mine[x][y] == '1' && flag == 0)
			{
				//此时表示第一次踩到雷,为了增加游戏的可玩性,我们让游戏第一次踩不到雷。
				//将雷移到别的地方。
				flag = 1;

				mine[x][y] = '0';

				//用NewSetMine()函数重新布置第一个雷

				NewSetMine(mine,row,col, x, y);

				//OpenMine()递归函数统计x,y 坐标外8个位置是否有雷如果有雷就展开。

				OpenMine(mine, show, x, y);

				//打印排查后雷阵的信息

				PrintBoard(show, row, col);

			}
			else if (mine[x][y] == '1' && flag == 1)
			{
				//此时表示不是第一次踩到雷,那么游戏结束 break 退出游戏

				printf("很遗憾你被炸死了!\n");

				printf("雷的分布如下:\n");

				PrintBoard(mine, row, col);
				break;

			}
			else
			{
				//此时没有踩到雷,游戏继续
				flag = 1;
				OpenMine(mine, show, x, y);

				PrintBoard(show, row, col);

			}


		}
		else
		{
			printf("坐标不合法!请重新输入:>\n");
		}



	}


	if (IsWin(show, row, col) == EASY_COUNT)
	{
		printf("恭喜你!你赢了\n");
		printf("雷的分布如下:\n");
		PrintBoard(mine, row, col);

		
	}
	

}

test.c文件

#define _CRT_SECURE_NO_WARNINGS 1

#include"game.h"

//扫雷游戏


void memu()
{
	printf("*******************\n");
	printf("***** 1.game  *****\n");
	printf("***** 0.exit  *****\n");
	printf("*******************\n");

	
}

void game()
{
	//定义mine数组用于存放雷的信息

	char mine[ROWS][COLS] = { 0 };

	//定义show数组用于存放排查雷时的信息

	char show[ROWS][COLS] = { 0 };

	//分别初始化数组mine数组没有雷时全部存放字符‘0’
	//show 数组当开始扫雷前 全部显示字符‘*’

	InitBoard(mine, ROWS, COLS, '0');
	InitBoard(show, ROWS, COLS, '*');

	//PrintBoard()函数,用于打印扫雷时,每一次扫雷后对应的雷阵信息

	//PrintBoard(mine, ROW, COL);
	//PrintBoard(show, ROW, COL);


	//布置雷

	SetMine(mine, ROW, COL);

	//打印雷阵测试,游戏开始时应该屏蔽
	//PrintBoard(mine, ROW, COL);

	PrintBoard(show, ROW, COL);

	//FindMine()扫雷函数

	FindMine(mine, show, ROW, COL);

}


int main()
{
	srand((unsigned int)time(NULL));
	int input = 0;

	do
	{
		memu();
		printf("请选择:>\n");

		scanf("%d", &input);

		switch (input)
		{
		case 1:
			printf("扫雷\n");
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("输入有误!请重新输入\n");
			break;
		}

	} while (input);


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值