C语言实现扫雷(含标记、展开)(含源码)

目录

一、基本功能

二、附加功能

1、标记

2、通过递归实现展开

三、源码


扫雷作为一款经典游戏,其主要玩法就是在一片固定的区域(n*n)中,通过所获得的一个格子周围的8个格子中的雷的数量,来避开地雷,最终使棋盘上只剩下地雷。

利用C语言实现扫雷,用到的主要知识有:二维数组、循环语句、选择语句、常量的定义、头文件的使用、随机数生成

环境:Visual Studio 2022

一、基本功能

首先,扫雷游戏需要一个大小为n*n的棋盘,这里以9*9大小的棋盘为例

创建名为game.h的头文件,用于引入头文件、常量的定义、函数的声明。在头文件中分别定义棋盘大小行(ROW)和列(COL),在头文件中定义常量方便后期修改

#define ROW 9
#define COL 9

因为游戏时,方格中显示的数字为该方格周围8个方格所有的雷的数量,若数组的大小为9*9,在计算边缘方格时存在越界访问的问题,因此,我们在定义数组时应该在棋盘的上下左右各加一行(列),此时再定义数组的行和列

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

此时,我们就可以再创建一个名为test.c的源文件,并开始游戏的主函数

作为游戏的开头,一个开始菜单必不可少。开始菜单应该同时满足开始和退出游戏、在完成一次游戏后再次开始的功能。那么可以通过switch语句对玩家的输入做出选择。

void menu()
{
	printf("**************************\n");
	printf("*****    1.start     *****\n");
	printf("*****    0.exit      *****\n");
	printf("**************************\n");
	printf("请选择=>");
}

为防止玩家输入的选项超出我们给的范围,我们通过循环提醒玩家。以下为开始菜单的写法

	do//程序先执行一次
	{
		menu();
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			//printf("game\n");
			game();
			break;
		case 0:
			printf("exit\n");
			break;
		default:
			printf("error\n");
			break;
		}
	} while (input);//当输入非法时,再次输入

此时便可以开始编写游戏的主要函数game()

首先需要创建游戏所用的数组,若只有一个数组,将难以同时完成布置雷区和显示游玩情况等目标,因此,两个或以上的数组会是更好的选择,此处我们创建两个数组,分别用于游戏进程的显示(show)和雷区的布置(mine)

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

在创建二维数组后,需要对其进行初始化,在show数组中,用‘ * ’表示未排查的方块,在mine中,用‘ 0 ’表示非雷区,用‘ 1 ’表示雷区。我们可以写一个通用函数,利用循环对两个数组进行初始化。为方便管理,我们将调用的函数放置于名为game.c的源文件中

void InitBoard(char arr[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++)
		{
			arr[i][j] = set;
		}
	}
}

因为两个数组所需初始化的内容不同,此处通过向函数传递内容来确定初始化内容(char set)。

//初始化函数的调用
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');

此处再写一个打印棋盘的函数,并在棋盘左边和上边设置坐标,方便查看,用于在测试时确认棋盘被初始化,和在游玩时显示进度

void DisplayBoard(char arr[ROWS][COLS], int row, int col)//此时打印的棋盘大小为9*9
{
	int i = 0;
	int j = 0;
	printf("---mine clearance---\n");
	
	for (j = 0; j <= col; j++)//打印列的编号,方便观察
	{
		printf("%d ", j);
	}
	printf("\n");
	for (i = 1; i <= row; i++)
	{
		printf("%d ", i);//打印行的编号
		for (j = 1; j <= col; j++)
		{
			printf("%c ", arr[i][j]);
		}
		printf("\n"); 
	}
	
	printf("---mine clearance---\n");
}

在创建并初始化数组后,就可以开始写布雷和扫雷的程序了

FindMine(show, mine, ROW, COL)

由于使用随机数生成雷区,因此我们需要在玩家第一次选择之后再生成雷区,此处为第一次输入

//第一次输入坐标
printf("请输入坐标=>");
while (1)//判断输入
{
	scanf("%d %d", &x, &y);
	if (x >= 1 && x <= row && y >= 1 && y <= col)
	{
		break;
	}
	else
	{
		printf("输入非法,请重新输入=>");
	}
}

这是就可以避开玩家的选择来生成雷区:在mine数组中用1代表雷区,通过随机数写入

srand((unsigned int)time(NULL));//利用时间戳设置随机数生成的种子
//放在main函数中
//需要头文件time.h
//和stdlib.h

在头文件中定义雷的数量

#define EAZY_COUNT 10
//写一个函数用于生成雷区
void SetMine(char arr[ROWS][COLS], int row, int col, int a, int b)
{
	int i = 0;
	int j = 0;

	int count = EAZY_COUNT;
	while (count)
	{
		i = rand() % row + 1;
		j = rand() % col + 1;
		if (i != a && j != b)
		{
			if (arr[i][j] != '1')
			{
				arr[i][j] = '1';
				count--;
			}
		}
	}
}

此时就可以开始写玩家的输入了,此处需要先检验玩家的输入是否合法,再判断是否踩雷,因此我们,需要一个循环来实现,此处我选择用死循环,在输入合法坐标时跳出循环。此处同样需要打印棋盘来显示游戏进度。

在玩家输入后,程序需要校验玩家是否踩雷,若没踩雷,则需要校验周围的8格内存在几个雷,并打印出来。

while (1)//判断输入
{
				scanf("%d %d", &x, &y);
				if (x >= 1 && x <= row && y >= 1 && y <= col)
				{
					break;
				}
				else
				{
					printf("输入非法,请重新输入=>");
				}
}
if (mine[x][y] == '1')//判断有没有踩雷
{
				printf("爆了!!!!!!\n");
				DisplayBoard(mine, ROW, COL);
				goto END;
}
else
{
				if (show[x][y] != '*')
				{
					printf("该坐标已被排查,请重新输入\n");
				}
				else
				{
					show[x][y] = MineCount(mine, x, y) + '0';//校验雷的数量
					DisplayBoard(show, ROW, COL);
				}
				break;
}
//通过循环检验雷的数量
//因为数组类型为cahr,所以其中的1和0都是以ASCII值储存
//字符数字在转换为整形数字时需要-48,也就是0对应的值
//或者写成-‘0’
int MineCount(char mine[ROWS][COLS], int x, int y)//计算输入坐标周围的雷数
{
	int i = -1;
	int j = -1;
	int count = 0;
	for (; i <= 1; i++)
	{
		for (j = -1; j <= 1; j++)
		{
			count += mine[x + i][y + j];
		}
	}
	count -= ('0' * 9);//将字符转换为整形
	return count;
}

最后就是输赢的判断,输的判断很简单,就是判断是否踩雷,也就是玩家的选择坐标对应的mine数组的值是否为1。赢的判断有两种,这里先讲第一种,下文会讲第二种。

我们可以通过计算成功操作,也就是输入合法的次数来计算。设置一个变量count,每成功输入一次,count++,当count的值为棋盘格数减去雷的个数时,玩家胜利。

count == ROW * COL - EAZY_COUNT;

这就是扫雷基本功能的实现。

二、附加功能

1、标记

我们都知道,在Windows中的扫雷中,玩家可以通过右键进行标记,我们通过判断也可以在自己写的简易扫雷中实现这个功能。我们可以通过直接修改show数组中对应的ASCII值对显示字符进行修改。

scanf("%d %d", &x, &y);
show[x][y] = 4;//标记
DisplayBoard(show, ROW, COL);

再添加一个判断,让玩家选择进行什么操作,并将其与排查功能结合。

printf("请选择模式(1、排查  2、标记)\n");//实现标记功能
while (1)
{
	scanf("%d", &chose);
	if(chose == 1)//选择排查
	{
		printf("请输入坐标=>");
		while (1)//判断输入
		{
			scanf("%d %d", &x, &y);
			if (x >= 1 && x <= row && y >= 1 && y <= col)
			{
				break;
			}
			else
			{
				printf("输入非法,请重新输入=>");
			}
		}
		if (mine[x][y] == '1')//判断有没有踩雷
		{
			printf("爆了!!!!!!\n");
			DisplayBoard(mine, ROW, COL);
			goto END;
		}
		else
		{
			if (show[x][y] != '*'&& show[x][y] != 4)
			{
				printf("该坐标已被排查,请重新输入\n");
			}
			else
			{
				show[x][y] = MineCount(mine, x, y) + '0';
				DisplayBoard(show, ROW, COL);
				count = Iswin(show, ROW, COL);
			}
			break;
		}
	}
	else if (chose == 2)//选择标记
	{
		printf("请输入坐标=>");
		scanf("%d %d", &x, &y);
		show[x][y] = 4;//标记
		DisplayBoard(show, ROW, COL);
		break;
	}
	else
	{
		printf("选择错误,请重新选择=>");
	}
}

2、通过递归实现展开

在扫雷中,玩家第一个选择的方格一定不是雷,并且程序会把玩家选择的格子周围不存在雷的区域全部显示,在c语言中,我们可以通过递归来实现这个功能。

通过调用计算雷数的函数,从玩家第一次选择的格子开始,逐渐向四周判断:当一个格子应该显示0时,则在show中将其改为空格,即不显示,再以这个格子为起始点向周围判断,当一个格子的显示不为0时,终止该格向四周扩展的过程,并显示该格对应数字。

在运用递归时,应注意递归的条件,防止出现死递归。

void AutoExc(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col, int x, int y)
{
	int i = -1;
	int j = -1;
	show[x][y] = ' ';//将当前格子替换为空格
	for (; i <= 1; i++)
	{
		for (j = -1; j <= 1; j++)
		{
			if (mine[x + i][y + j] != '1')
			{
				int ret = 0;
				ret = MineCount(mine, x + i, y + j);//判断下一格是否为0
				if (ret == 0)
				{
					if (show[x + i][y + j] == '*')//防止判断过的格子被再次判断,造成死递归
					{
						AutoExc(show, mine, ROW, COL, x + i, y + j);
					}
					show[x + i][y + j] = ' ';
				}
				else
				{
					show[x + i][y + j] = ret + '0';
				}
			}
		}
	}
}

此时我们需要运用第二种判断胜利的方法:计算没有被玩家排查或者标记格格子数

int Iswin(char show[ROWS][COLS],int row,int col)
{
	int i = 1;
	int j = 1;
	int count = 0;
	for (; i <= row; i++)
	{
		for (j = 1; j <= col; j++)
		{
			if (show[i][j] == '*'|| show[i][j] == 4)
			{
				count++;
			}
		}
	}
	return count;
}

三、源码

game.h

#pragma once

#include
#include
#include

#define ROW 9
#define COL 9

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

#define EAZY_COUNT 10

void InitBoard(char arr[ROWS][COLS], int rows, int cols, char set);//初始化棋盘
void DisplayBoard(char arr[ROWS][COLS], int rows, int cols);//打印棋盘

void SetMine(char arr[ROWS][COLS], int row, int col, int a, int b);//设置雷区

void FindMine(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int cow);//扫雷
int MineCount(char mine[ROWS][COLS], int x, int y);//计算雷数
void AutoExc(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col, int x, int y);//自动排除

test.c

#define _CRT_SECURE_NO_WARNINGS

#include"game.h"

void menu()
{
	printf("**************************\n");
	printf("*****    1.start     *****\n");
	printf("*****    0.exit      *****\n");
	printf("**************************\n");
	printf("请选择=>");
}

void game()
{
	char mine[ROWS][COLS] = { 0 };
	char show[ROWS][COLS] = { 0 };
	InitBoard(mine, ROWS, COLS, '0');
	InitBoard(show, ROWS, COLS, '*');//创建并初始化两个数组
	//DisplayBoard(mine, ROW, COL);
	DisplayBoard(show, ROW, COL);

	//SetMine(mine, ROW, COL);//设置雷区
	//DisplayBoard(mine, ROW, COL);

	FindMine(show, mine, ROW, COL);//开始扫雷
}

int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));//设置随机数生成的种子

	do
	{
		menu();
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			//printf("game\n");
			game();
			break;
		case 0:
			printf("exit\n");
			break;
		default:
			printf("error\n");
			break;
		}
	} while (input);
	return 0;
}

game.c

#define _CRT_SECURE_NO_WARNINGS

#include"game.h"

void InitBoard(char arr[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++)
		{
			arr[i][j] = set;
		}
	}
}

void DisplayBoard(char arr[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	printf("---mine clearance---\n");
	
	for (j = 0; j <= col; j++)//打印列的编号,方便观察
	{
		printf("%d ", j);
	}
	printf("\n");
	for (i = 1; i <= row; i++)
	{
		printf("%d ", i);//打印行的编号
		for (j = 1; j <= col; j++)
		{
			printf("%c ", arr[i][j]);
		}
		printf("\n"); 
	}
	
	printf("---mine clearance---\n");
}

void SetMine(char arr[ROWS][COLS], int row, int col, int a, int b)
{
	int i = 0;
	int j = 0;

	int count = EAZY_COUNT;
	while (count)
	{
		i = rand() % row + 1;
		j = rand() % col + 1;
		if (i != a && j != b)
		{
			if (arr[i][j] != '1')
			{
				arr[i][j] = '1';
				count--;
			}
		}
	}
}

void FindMine(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int count = 0;
	int chose = 0;
	
	//第一次输入坐标
	printf("请输入坐标=>");
	while (1)//判断输入
	{
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			break;
		}
		else
		{
			printf("输入非法,请重新输入=>");
		}
	}
	
	SetMine(mine, ROW, COL, x, y);//在第一次输入后设置雷区,防止上来就失败了
	show[x][y] = MineCount(mine, x, y) + '0';
	
	DisplayBoard(show, ROW, COL);
	//DisplayBoard(mine, ROW, COL);
	AutoExc(show, mine, ROW, COL, x, y);//将第一个坐标周围没有雷的地方自动排除
	DisplayBoard(show, ROW, COL);

	while (count < ROW * COL - EAZY_COUNT)
	{
		printf("请选择模式(1、排查  2、标记)\n");//实现标记功能
		while (1)
		{
			scanf("%d", &chose);
			if(chose == 1)//选择排查
			{
				printf("请输入坐标=>");
				while (1)//判断输入
				{
					scanf("%d %d", &x, &y);
					if (x >= 1 && x <= row && y >= 1 && y <= col)
					{
						break;
					}
					else
					{
						printf("输入非法,请重新输入=>");
					}
				}
				if (mine[x][y] == '1')//判断有没有踩雷
				{
					printf("爆了!!!!!!\n");
					DisplayBoard(mine, ROW, COL);
					goto END;
				}
				else
				{
					if (show[x][y] != '*'&& show[x][y] != 4)
					{
						printf("该坐标已被排查,请重新输入\n");
					}
					else
					{
						show[x][y] = MineCount(mine, x, y) + '0';
						DisplayBoard(show, ROW, COL);
						count = Iswin(show, ROW, COL);
					}
					break;
				}
			}
			else if (chose == 2)//选择标记
			{
				printf("请输入坐标=>");
				scanf("%d %d", &x, &y);
				show[x][y] = 4;//标记
				count = Iswin(show, ROW, COL);
				DisplayBoard(show, ROW, COL);
				break;
			}
			else
			{
				printf("选择错误,请重新选择=>");
			}
		}
		if (count == EAZY_COUNT)
		{
			printf("YOU WIN!\n");
			DisplayBoard(mine, ROW, COL);
			break;
		}
	}
END:
	;
}


int MineCount(char mine[ROWS][COLS], int x, int y)//计算输入坐标周围的雷数
{
	int i = -1;
	int j = -1;
	int count = 0;
	for (; i <= 1; i++)
	{
		for (j = -1; j <= 1; j++)
		{
			count += mine[x + i][y + j];
		}
	}
	count -= ('0' * 9);//将字符转换为整形
	return count;
}


void AutoExc(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col, int x, int y)
{
	int i = -1;
	int j = -1;
	show[x][y] = ' ';
	for (; i <= 1; i++)
	{
		for (j = -1; j <= 1; j++)
		{
			if (mine[x + i][y + j] != '1')
			{
				int ret = 0;
				ret = MineCount(mine, x + i, y + j);
				if (ret == 0)
				{
					if (show[x + i][y + j] == '*')
					{
						AutoExc(show, mine, ROW, COL, x + i, y + j);
					}
					show[x + i][y + j] = ' ';
				}
				else
				{
					show[x + i][y + j] = ret + '0';
				}
			}
		}
	}
}


int Iswin(char show[ROWS][COLS],int row,int col)
{
	int i = 1;
	int j = 1;
	int count = 0;
	for (; i <= row; i++)
	{
		for (j = 1; j <= col; j++)
		{
			if (show[i][j] == '*'|| show[i][j] == 4)
			{
				count++;
			}
		}
	}
	return count;
}

你可能感兴趣的:(C语言学习记录,c语言,开发语言)