C语言实现扫雷(包含递归展开)

目录

一:扫雷游戏的基础逻辑

二:关于扫雷相关的信息存储

三:游戏大体实现逻辑

四:具体实现

(1)初始化

(2)打印

(3)布置雷

(4)查雷

五:全部代码

(1)game.h

(2)game.c

(3)test.c

六:实际效果演示


一:扫雷游戏的基础逻辑

扫雷游戏逻辑如下:

1. 游戏开始时,玩家将看到一个方格棋盘,每个方格上有一个数字或着一个地雷。

2. 玩家需要透过数字来判断周围哪些格子有雷。一个数字所在的方格周围八个格子中,有几个格子有雷就显示几。例如,如果一个格子的数字是2,则周围有两个雷。

3. 如果一个方格上是一个地雷,玩家就输了游戏。如果玩家成功地标记出所有地雷,就赢了游戏。

4. 玩家需要使用自己的推理和猜测技能来确定哪些方格上有地雷而不需要点开它,如果玩家点开了一个没有地雷的方格,那么它会显示一个数字或者显示空白区域,这些数字和空白区域恰好对应周围方格中的地雷数量。

整个游戏的逻辑就是通过不断的推理来确定地雷的位置,在避免踩到地雷的情况下,将整个棋盘的状态揭示出来。


二:关于扫雷相关的信息存储

两个信息,一个是雷的信息,是雷用字符’1’表示,不是用字符’0’表示。

一个是非雷的坐标的雷数信息,我们统计以这个点为中心3x3的区域雷数
 

因此我们可以设计两个数组来保存这些信息,其中展示数组(show)负责给予玩家雷数提示,雷数组(mine)负责保存雷的位置信息。


用字符’1’来表示雷是因为数字字符的ASCLL码是连续的,我们用'1'-'0'得到的就是1,我们可以把3x3区域的数字字符加起来减去7个'0',就可以得到雷数(数字字符)。

但是如果我们就设计9x9的区域,边界区域的3x3范围会越界,我们可以设计成11x11的区域并把外围初始化为’0’,计算的时候外围不会影响,为了尽量统一,展示数组也设计成11x11。

但如果这个位置周围没有雷,我们依然要玩家排查周围,游戏效率很低,我们可以在这个位置周围无雷的时候向四周进行展开(后面会讲),周围无雷的位置展示数组对应修改为空格。

除此之外我们实现标记雷的功能,被标记的位置对应展示数组修改为'!'。


汇总:

雷数组——'0'表示不是雷,'1'表示是雷

展示数组——'*'表示未排查,'!'表示标记但未排查,' '(空格)表示这个位置周围没有雷,数字字符代表这个位置周围雷数。

图解:

 


三:游戏大体实现逻辑

 

 


四:具体实现

 

(1)初始化

初始化,传对应的数组首地址和字符来初始化

代码:

//初始化
void InitBoard(char Board[ROWS][COLS], int row, int col, char set)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			Board[i][j] = set;
		}
	}
}

(2)打印

打印,利用双重循环一行一行的打印,在打印内容前打印对应列数(方便看),打印一行前打印对应行数(方便看)。

代码:

//打印
void DisplayBoard(char Board[ROWS][COLS], int row, int col)
{
	int i = 0;
	//对齐就行
	printf("   ");
	for (i = 1; i <= col; i++)
	{
		printf("%d ", i);
	}
	printf("n");

	for (i = 1; i <= row; i++)
	{
		printf("%d| ", i);
		for (int j = 1; j <= col; j++)
		{
			printf("%c ",Board[i][j]);
		}
		printf("n");
	}
}

(3)布置雷

布置雷,只给内层9x9布置,我们随机生成坐标,为'0'布置,为'1'(已经布置)就不布置,布置了10个就结束(计数器控制)。

代码:

//布雷
void SetMine(char mine[ROWS][COLS], int row, int col)
{
	//雷数
	int count = EasyCount;
	//随机坐标
	int x = 0;
	int y = 0;
	while (count)
	{
		x = rand() % row + 1;
		y = rand() % row + 1;
		//这个位置已经是雷就不修改
		if (mine[x][y] == '0')
		{
			mine[x][y] = '1';
			//计数器减1
			count--;
		}
	}
}

(4)查雷

查雷,把两个数组地址都传过去,输入坐标后通过雷数组看是否为雷,是雷跳出结束游戏,不是雷统计周围的雷数并修改展示数组。

游戏胜利的条件是非雷位置全排查完(计数器控制)非雷位置数为(内层行数x内存列数-雷数)。

其中有三个比较重要的点

【1】计算周围的雷数

图解:

代码:

 

//计算周围雷数
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] - 7 * '0');
}

【2】展开周围无雷的位置

如果排查位置周围雷数为0,就把周围的信息显示出来,如果周围还有雷数0的位置,就继续展开,依此类推。其中被展开的位置也算被排查了,需要让计数器加1,我们可以传计数器地址来进行修改。

代码:

void unfold(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y,int* win)
{
	//合法坐标才展开
	if (x >= 1 && x <= row && y >= 1 && y <= col)
	{
		//计算这个位置周围雷数
		int count = GetMineCount(mine, x, y);
		//排查计数加1
		*win += 1;

		//如果这个位置周围无雷,向四周展开
		if ((count-'0') == 0)
		{
			//修改周围无雷位置为空格
			show[x][y] = ' ';
			int i = 0;

			for (i = x - 1; i <= x + 1; i++)
			{
				for (int j = y - 1; j <= y + 1; j++)
				{
					//每一个位置只能展开一次
					if (show[i][j] == '*' || show[x][y] == '!')
					{
						unfold(mine, show, row, col, i, j,win);
					}
				}
			}
		}
		//如果周围有雷,修改展示数组为雷数
		else
		{
			show[x][y] = count;
		}
	}
}

【3】玩家标记雷

标记雷,每次排查完都提示一下是否需要记录雷的位置,需要的话修改展示数组为’!’,只是标记不是排查 ,后面排查和展开的时候这个位置依然是合法的。

代码:

//标记雷位置
void SignMine(char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	while (1)
	{
		printf("请输入要标记的坐标:");
		scanf("%d %d", &x, &y);

		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (show[x][y] == '*')
			{
				show[x][y] = '!';
				break;
			}
			else
			{
				printf("该位置不能被标记,请重新输入:n");
			}
		}
		else
		{
			printf("输入坐标非法,请重新输入:n");
		}
	}
	//清空屏幕再打印
	system("cls");
	DisplayBoard(show, row, col);
}

查雷的全部代码:

//查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS],int row,int col)
{
	//保存排除了多少个不是雷的位置
	int win = 0;
	char ch = 0;
	while (win < (row*col- EasyCount))
	{
		int x = 0;
		int y = 0;
		printf("请输入要排查的坐标,空格隔开:>");
		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;
			}
			//无雷
			if(show[x][y] == '*' || show[x][y] == '!')
			{
				//展开
				unfold(mine, show, row, col, x, y,&win);
				system("cls");
				//打印展示数组
				DisplayBoard(show, ROW, COL);
				printf("需要标注地雷就输入:Y,不需要标注地雷则输入:Nn");
				//清空一下缓冲区
				while ((ch = getchar()) != 'n');
				scanf("%c", &ch);
				switch (ch)
				{
				case 'Y':
					//标记雷的位置
					SignMine(show, row, col);
					break;
				default:
					break;
				}
			}
			else
			{
				printf("你已经排查过这个位置了,请重新输入n");
			}
		}
		else
		{
			printf("坐标非法,重新输入n");
		}
	}

	//判断是否是排雷结束跳出
	if (win == (row * col - EasyCount))
	{
		printf("恭喜你,你赢了n");
	}
}

五:全部代码

(1)game.h

​
#pragma once
//可以自行修改行列数和雷的数量
//内层行数和列数
#define ROW 9
#define COL 9
//数组加上外层的实际行列
#define ROWS ROW+2
#define COLS COL+2
//雷数
#define EasyCount 10

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

//初始化
void InitBoard(char Board[ROWS][COLS], int row, int col, 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);


​

(2)game.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"

//标记雷位置
void SignMine(char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	while (1)
	{
		printf("请输入要标记的坐标:");
		scanf("%d %d", &x, &y);

		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (show[x][y] == '*')
			{
				show[x][y] = '!';
				break;
			}
			else
			{
				printf("该位置不能被标记,请重新输入:n");
			}
		}
		else
		{
			printf("输入坐标非法,请重新输入:n");
		}
	}
	//清空屏幕再打印
	system("cls");
	DisplayBoard(show, row, col);
}

//初始化
void InitBoard(char Board[ROWS][COLS], int row, int col, char set)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			Board[i][j] = set;
		}
	}
}

//打印
void DisplayBoard(char Board[ROWS][COLS], int row, int col)
{
	int i = 0;
	//对齐就行
	printf("   ");
	for (i = 1; i <= col; i++)
	{
		printf("%d ", i);
	}
	printf("n");

	for (i = 1; i <= row; i++)
	{
		printf("%d| ", i);
		for (int j = 1; j <= col; j++)
		{
			printf("%c ",Board[i][j]);
		}
		printf("n");
	}
}

//布雷
void SetMine(char mine[ROWS][COLS], int row, int col)
{
	//雷数
	int count = EasyCount;
	//随机坐标
	int x = 0;
	int y = 0;
	while (count)
	{
		x = rand() % row + 1;
		y = rand() % row + 1;
		//这个位置已经是雷就不修改
		if (mine[x][y] == '0')
		{
			mine[x][y] = '1';
			//计数器减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] - 7 * '0');
}

//展开
void unfold(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y,int* win)
{
	//合法坐标才展开
	if (x >= 1 && x <= row && y >= 1 && y <= col)
	{
		//计算这个位置周围雷数
		int count = GetMineCount(mine, x, y);
		//排查计数加1
		*win += 1;
		//如果这个位置周围无雷,向四周展开
		if ((count-'0') == 0)
		{
			//修改周围无雷位置为空格
			show[x][y] = ' ';
			int i = 0;
			for (i = x - 1; i <= x + 1; i++)
			{
				for (int j = y - 1; j <= y + 1; j++)
				{
					//每一个位置只能展开一次
					if (show[i][j] == '*' || show[x][y] == '!')
					{
						unfold(mine, show, row, col, i, j,win);
					}
				}
			}
		}
		//如果周围有雷,修改展示数组为雷数
		else
		{
			show[x][y] = count;
		}
	}
}

//查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS],int row,int col)
{
	//保存排除了多少个不是雷的位置
	int win = 0;
	char ch = 0;
	while (win < (row*col- EasyCount))
	{
		int x = 0;
		int y = 0;
		printf("请输入要排查的坐标,空格隔开:>");
		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;
			}
			//无雷
			if(show[x][y] == '*' || show[x][y] == '!')
			{
				//展开
				unfold(mine, show, row, col, x, y,&win);
				system("cls");
				//打印展示数组
				DisplayBoard(show, ROW, COL);
				printf("需要标注地雷就输入:Y,不需要标注地雷则输入:Nn");
				//清空一下缓冲区
				while ((ch = getchar()) != 'n');
				scanf("%c", &ch);
				switch (ch)
				{
				case 'Y':
					//标记雷的位置
					SignMine(show, row, col);
					break;
				default:
					break;
				}
			}
			else
			{
				printf("你已经排查过这个位置了,请重新输入n");
			}
		}
		else
		{
			printf("坐标非法,重新输入n");
		}
	}

	//判断是否是排雷结束跳出
	if (win == (row * col - EasyCount))
	{
		printf("恭喜你,你赢了n");
	}
}

(3)test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
//菜单
void menu()
{
	printf("****************n");
	printf("**** 1.play ****n");
	printf("**** 0.exit ****n");
	printf("****************n");
}

//游戏主逻辑
void game()
{
	//每一轮新游戏开始都清空屏幕
	system("cls");
	//雷数组
	char mine[ROWS][COLS] = { 0 };
	//展示数组
	char show[ROWS][COLS] = { 0 };

	//初始化
	InitBoard(mine, ROWS, COLS, '0');
	InitBoard(show, ROWS, COLS, '*');

	//开始打印
	printf("扫雷游戏--------n");
	DisplayBoard(show, ROW, COL);

	//布雷
	SetMine(mine, ROW, COL);
	
	//查雷
	//这里打印雷数组是为了方便查看
	//DisplayBoard(mine, ROW, COL);
	FindMine(mine, show,ROW,COL);
}

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

六:实际效果演示

 

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>