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

news2024/10/7 0:11:12

目录

一:扫雷游戏的基础逻辑

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

三:游戏大体实现逻辑

四:具体实现

(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,不需要标注地雷则输入:N\n");
				//清空一下缓冲区
				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,不需要标注地雷则输入:N\n");
				//清空一下缓冲区
				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);
}

六:实际效果演示

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/512662.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【操作系统复习】第6章 虚拟存储器 2

请求分页中的内存分配 在为进程分配物理块时&#xff0c;要解决下列的三个问题&#xff1a; 1. 保证进程可正常运行所需要的最少物理块数 2. 每个进程的物理块数&#xff0c;是固定值还是可变值&#xff08;分配策略&#xff09; 3. 不同进程所分配的物理块数&#xff…

Python基础入门编程代码练习(三)

一、猜数游戏 有一个数列&#xff1a;8&#xff0c;4&#xff0c;2&#xff0c;1&#xff0c;23&#xff0c;344&#xff0c;12循环输出数列的值求数列中所有数值的和猜数游戏&#xff1a;从键盘中任意输入一个数据&#xff0c;判断数列中是否包含此数 实现代码如下&#xf…

【云原生】Kubrenetes二进制--单节点Master集群

单节点Master集群 一、Kubernetes概述1、Master组件2、Node组件 二、Kubernetes核心概念1、Pod概述2、Pod控制器3、Label标签4、Label选择器5、Service6、Ingress 三、部署kubernetes1、所有节点关闭防火墙 核心防护 Swap交换2、将三台服务器的地址hosts中3、调整内核参数4、时…

十分钟教你搭建ChatGPT 图片生成的安卓应用

十分钟教你搭建ChatGPT 图片生成的安卓应用 大家好&#xff0c;我是易安&#xff01; 今天&#xff0c;我们将集成 OpenAI API (ChatGPT)来构建一个简单的类似 ChatGPT 的 android 应用程序&#xff0c;让它返回我们想要的图片&#xff0c;本文是上一篇的姊妹篇。 详细步骤 第…

防止表单重复提交的几种方式,演示一个自定义注解方式的实现

防止表单重复提交的几种方式&#xff0c;演示一个自定义注解方式的实现 一、防止表单重复提交的几种方式方式一&#xff1a;Token 机制方式二&#xff1a;去重表&#xff08;主要是利用 MySQL 的唯一索引机制来实现的&#xff09;方式三&#xff1a;Redis 的 setnx方式四&#…

数组(C语言版)

&#x1f929;本文作者&#xff1a;大家好&#xff0c;我是paperjie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 &#x1f970;内容专栏&#xff1a;这里是《C知识系统分享》专栏&#xff0c;笔者用重金(时间和精力)打造&#xff0c;基础知识一网打尽&#xff0c;…

3 ES快速入门

3 ES快速入门 ES作为一个索引及搜索服务&#xff0c;对外提供丰富的REST接口&#xff0c;快速入门部分的实例使用head插件来测试&#xff0c;目的是对ES 的使用方法及流程有个初步的认识。 3.1 创建索引库 ES的索引库是一个逻辑概念&#xff0c;它包括了分词列表及文档列表…

电力系统负荷与电价预测优化模型(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【C++】vector的模拟实现及深度剖析

目录 一、模拟实现二、使用memcpy拷贝问题三、动态二维数组理解 一、模拟实现 namespace hxj {template<class T>class vector{public:// Vector的迭代器是一个原生指针typedef T* iterator;typedef const T* const_iterator;//构造和销毁vector():_start(nullptr), _fi…

Spring更简单的存储和读取Bean对象

目录 1.第一个Spring项目 2.存储Bean对象 2.1 准备工作 2.2 五大类注解 2.3 方法注解Bean 2.4 Bean对象的默认命名规则 3. 读取Bean对象 3.1 属性注入 3.2 setter注入 3.3 构造方法注入 3.4 注入异常问题 3.5 注入方式优缺点 3.6 Autowired和Resource的区别 1.第一…

python web开发(二):HTML标签语言

文章目录 简介标签语法标题div和span超链接插入图片列表表格Input系列提交表单 参考 简介 如下展示了一段简单的HTML模板&#xff0c; <head></head>标签中主要包含一些基本配置&#xff0c;如编码方式&#xff0c;标题等&#xff0c;注意标题的作用如下图所示 …

【java BUG收集-持续更~】

JAVA BUG JAVA BUGliquibase.lockservice锁异常1、启动参数增加jvm参数 -Dliquibase.lockservicefalse2、修改或清空 包含有 DATABASECHANGELOGLOCK的表 JAVA BUG 该章收集工作中遇到的java bug,作为工作日志&#xff0c;方便回顾。 liquibase.lockservice锁异常 报错信息&a…

Spring Boot实用技巧之单元测试

文章目录 一、单元测试的概念二、单元测试的优势三、Spring Boot实现单元测试&#xff08;一&#xff09;添加依赖&#xff08;二&#xff09;生成单元测试的类&#xff08;三&#xff09; 添加注解和业务代码1. 添加 SpringBootTest 注解2. 添加单元测试的业务代码3. 执行测试…

MySQL部分常用函数总结

数值计算函数 使用方法&#xff1a; ABS&#xff08;x&#xff09; date函数 获取date中对应部分&#xff1a; YEAR(date) 字符串函数 用法举例&#xff1a; 左侧截取字符 SELECT LEFT(‘MySQL’,2); 按符号拆分字符&#xff0c;返回拆分后的部分 SUBSTRING_INDEX(profile,“…

创新洞察 |与众不同的DTC模式:2023年发展趋势将如何影响零售业增长策略?

DTC零售不再局限于数字原生品牌。传统零售商也在采用相同的策略&#xff0c;特别是在后疫情世界中竞争时。预计DTC销售将继续增长&#xff0c;因为更多品牌转向电子商务领域&#xff0c;而已建立的DTC参与者也在扩大其现有市场。预计仅在美国&#xff0c;数字原生品牌将在2023年…

多分类的ROC曲线绘制思路

目录 一、什么是ROC曲线 二、AUC面积 三、代码示例 1、二分类问题 2、多分类问题 一、什么是ROC曲线 我们通常说的ROC曲线的中文全称叫做接收者操作特征曲线&#xff08;receiver operating characteristic curve&#xff09;&#xff0c;也被称为感受性曲线。 该曲线有两…

第四节 Linux 特殊权限SUID、SGID、SBIT

目录 1.Set UID 简称 SUID 2.Set GID 简称 SGID 3.Sticky Bit 简称 SBIT 1.Set UID 简称 SUID 简称 SUID 限制与功能&#xff1a; SUID权限仅对二进制程序有效&#xff1b; 执行者对于该程序需要具有x的执行权限&#xff1b; 本权限仅在执行该程序的过程中有效&#xff1…

Softmax简介

Softmax是一种数学函数&#xff0c;通常用于将一组任意实数转换为表示概率分布的实数。其本质上是一种归一化函数&#xff0c;可以将一组任意的实数值转化为在[0, 1]之间的概率值&#xff0c;因为softmax将它们转换为0到1之间的值&#xff0c;所以它们可以被解释为概率。如果其…

VSCode +gdb+gdbserver远程调试arm开发板

一、下载编译器 从ARM官网下载gcc-arm编译器&#xff0c;编译器中自带gdb和gdbserver&#xff0c;可以省去自己编译。 注&#xff1a;gdb是电脑端程序&#xff0c;gdbserver是arm开发板程序 arm官网链接&#xff1a;https://developer.arm.com/downloads/-/arm-gnu-toolchain-d…

速卖通、Lazada、美客多、亚马逊新品流量如何利用测评快速提升?

熟悉亚马逊的卖家应该清楚&#xff0c;亚马逊对于新发布的产品会有一定的流量倾向&#xff0c;特别是产品刚上架的2-4周&#xff0c;你的产品将在搜索结果中显示更多&#xff0c;排名比通常情况下更快。 第一步&#xff1a;优化好自己的产品listing1.新品上架标题要点标题权重…