首页 > 其他分享 >【扫雷游戏】(递归版详解)

【扫雷游戏】(递归版详解)

时间:2024-12-04 13:03:31浏览次数:8  
标签:ROWS 递归 show int mine COLS char 详解 扫雷

• 前言

在我们学习了数组和函数等知识,我们可以自己实现一个扫雷游戏的逻辑。
不仅可以巩固知识,也可以提高自己的代码能力。

在这个过程中如果有什么问题,欢迎各位大佬指点,我会虚心听教改正!!

• 逻辑分析

首先我们来分析扫雷游戏逻辑

• 界面
  1. 首先来看扫雷游戏的界面,是一个二维的界面
    在这里插入图片描述

在这里我们很容易想到用二维数组来表示这个界面。

• 游戏核心逻辑
  1. 我们再来看游戏过程

• 系统随机生成10个雷在这个棋盘的某些位置

当我们选中一个位置的时候(如下图)
在这里插入图片描述
结果如下:
在这里插入图片描述
• 如果选中的位置是地雷,则游戏失败(游戏结束);
• 如果不是:
这里会统计周围8个位置雷的数量
如果为 0 会向四周散开直到遇到不会 0 的地方(这里就是递归的核心)

在这里我们需要思考:如果仅仅是只有一个二维数组,能满足我们的界面要求和统计雷的要求吗?

这显然是不可能的。
所以这里我们就可以用两个二维数组(A.showboard B. mineboard)

然后逐步排查,直到最后只剩下雷就胜利了
• 实现逻辑

通过上面的分析,我们需要:

  1. 创建两个二维数组,一个展示,一个存雷;

  2. 需要展示棋盘(打印函数);

  3. 对这个两个数组初始化(初始化函数);
    • 对于存雷的二维数组来说,我们还需要随机在棋盘进行埋雷(埋雷的函数);

  4. 然后就是排查雷的过程(排查雷的函数):
    (1) 输入一个点的坐标;
    (2) 判断这个点是不是雷:
    • 是雷,游戏结束
    • 不是雷:
    1. 如果周围没有雷,那么向四周展开,直到遇到不为0的位置(延展函数);
    2. 如果周围有雷,那么显示周围雷的个数(统计函数);

  5. 最后只剩下雷的位置,就获得胜利(判断函数);

但是 这里有很多潜在的问题:
例如:
当我们选择最边上的一圈的坐标的时候,统计周围的个数的时候,会发生什么问题呢?
很显然数组越界……
在这里插入图片描述
很显然这样的一个位置就有5个位置导致数组越界

• 为了解决这个问题: 我们决定在我们想要的这个棋盘下再扩大一圈
!! 在这扩大一圈的位置,我们不能将地雷埋在这里,相反也需要将这里初始化,后面逻辑需要注意。

• 实现过程

在这里我并不提倡初学者一上来就打印菜单
究其原因就是再写的过程中难免会有逻辑错误,如果一上来就写菜单的话,极其不方便我们调试

所以在这里,我们提倡最后打印菜单。

文件创建

首先看到这个过程还是较为复杂的,我们就采用多文件的形式:
在这里插入图片描述
方便我们进行合理分配。
game.h文件放函数声明(记得定义了的函数就要放在里面声明);
game.c文件放核心函数定义;
test.c 文件放主函数

创建准备

在test.c文件中创建一个game函数,实现代码核心逻辑;在test.c文化中主函数中调用game函数即可:
在这里插入图片描述
创建两个数组:
为了方便后续修改整个工程,我们这里采用定义宏变量来实现,方便我们修改棋盘大小。

就在头文件game.h中声明:
在这里插入图片描述
这里我们选择9 * 9的棋盘;

在game函数中创建(记得包含本地头文件
在这里插入图片描述

初始化

然后进行初始化

• show:
这里我们选择用 “ * ” 来代替展示的空格;

• mine:
对于是雷的位置我们采用字符 “ 1 ” ;
对于不是雷的位置,我们就使用字符“ 0 ”;
为了方便是实现我们统计雷的个数

定义初始化函数:

  1. 传的参数: 二维数组、棋盘的行号和列号、和初始化的内容;
  2. 遍历整个二维数组,给每个位置都赋值(对于埋雷的,都需要对扩充的位置置为字符“ 0 ”)

在这里插入图片描述
在这里插入图片描述

打印棋盘

!!!特别注意:
我们在这里要打印的是二维数组的1 ~ 9行和列
因为我们在这个二维数组扩了一圈

为了美观我们就添加行号和列号,方便我们输入坐标
在这里插入图片描述

运行看看赋值成功没(一定要边写边测试,方便找出问题):

在这里插入图片描述
在这里插入图片描述

埋雷逻辑

在这里我们要随机埋雷(我们这里取10,在game.h中定义宏变量,方便后面修改)
在这里插入图片描述

函数创建:

  1. 传参:mine数组,行号和列号;
  2. 随机生成一个坐标,把当前位置改为字符“ 1 ”,循环进行,直到10个埋完
    判断 – 生成坐标: x: 1 ~ 9; y: 1 ~ 9 且 坐标不能重复
    (随机数生成在猜数字游戏1中已经详细介绍过了,这里就不做详解)

在这里插入图片描述

在这里插入图片描述
运行看看埋雷成功没:(这里也可以调整Count的值来看成功没)
在这里插入图片描述

排查地雷

重点就是找雷逻辑;

函数创建:

  1. 传参我们要统计雷,也要输入过后展示,那么我们的 mine 和 show 都需要传过去;输入坐标需要在范围内 —— 行号和列号;
  2. 我们需要重复找地雷,直到胜利或踩雷
  3. 输入一个坐标,判断是否在合理范围内,然后判断是否是雷;
踩雷的逻辑

这个比较简单,我们就略过(附上图片)
在这里插入图片描述

没有踩雷的逻辑

!!这里就是重中之中我们需要递归展开
下面我做图解释:

在这里插入图片描述
接下来就是函数实现:

  1. 首先来实现统计周围雷的个数:

    1. 传参:mine、坐标

    2. 返回类型 整型(这里采用整型我们后面通过字符“ 0 ”的加法可以转化为对应字符)

    3. 采用循环遍历这9个位置,因为是字符所以我们通过减字符“ 0 ”得到数字

    实现:
    在这里插入图片描述

  2. 递归函数的实现:

    1. 传参: show、mine、坐标;

    2. 判断,给位置赋值

    实现:
    在这里插入图片描述

胜利条件

就是遍历整个show,统计是否有10个“ * ”就可以实现了。
在这里插入图片描述
至此所有逻辑已经完成:
在这里插入图片描述

打印菜单

这个也比较简单我们省略(附上代码)
在这里插入图片描述

• 结语

对于学到函数的初学者来说:
这个肯定是有一定的难度的,但自己实现成功过后又不失有一种成就感!!
希望大家多多练习,练习逻辑思维!!

源代码

game.h
#pragma once

#include<stdio.h>

#define ROWS 11
#define COLS 11
#define ROW 9
#define COL 9

void IntiBoard(char board[ROWS][COLS], int x, int y, char c);

void PrintBoard(char board[ROWS][COLS], int x, int y);

#include<stdlib.h> // -- rand/srand头文件

#include<time.h> // time 时间戳

#define Count 10 //埋雷个数

void GetMine(char board[ROWS][COLS], int x, int y);

void FindMine(char show[ROWS][COLS], char mine[ROWS][COLS], int a, int b);

int Countmine(char board[ROWS][COLS], int x, int y);

void ExpdShow(char show[ROWS][COLS], char mine[ROWS][COLS], int x, int y);

int WinBoard(char board[ROWS][COLS], int x, int y);
game.c
#include"game.h"

void IntiBoard(char board[ROWS][COLS], int x, int y, char c)
{
	int i = 0;     //行
	for (i = 0; i < x; i++)
	{
		int j = 0; //列
		for (j = 0; j < y; j++)
		{
			board[i][j] = c;
		}
	}
}

void PrintBoard(char board[ROWS][COLS], int x, int y)
{
	printf("------扫雷开始------\n");
	//美化列号:
	int i = 0;
	for (i = 0; i <= x; i++)
		printf("%d ", i);
	printf("\n");
	for (i = 1; i <= x; i++) //每一行的打印:
	{
		int j = 0;
		printf("%d ", i);
		for (j = 1; j <= y; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n"); //每行打印完换行
	}
	printf("-------------------\n");
}

void GetMine(char board[ROWS][COLS], int x, int y)
{
	int count = Count;
	while (count)
	{
		int a = rand() % 9 + 1; // 下标1~9
		int b = rand() % 9 + 1;
		if (0 < a && a <= x && 0 < b && b <= y)
		{
			if (board[a][b] != '1')
			{
				board[a][b] = '1';
				count--;
			}
		}
	}
}

int Countmine(char board[ROWS][COLS], int x, int y)
{
	int ret = 0;
	int i = 0;
	for (i = -1; i < 2; i++)
	{
		int j = 0;
		for (j = -1; j < 2; j++)
		{
			ret += board[x + i][y + j] - '0'; // 统计数量
		}
	}
	return ret;
}
//递归展开:在此之前需要一个统计周围雷数的函数
void ExpdShow(char show[ROWS][COLS], char mine[ROWS][COLS], int x, int y)
{
	int n = Countmine(mine, x, y); //得到周围的雷数
	if (n == 0 && 0 < x && x <= ROW && 0 < y && y <= COL && show[x][y] != '0')
	{
		show[x][y] = '0';
		ExpdShow(show, mine, x - 1, y);
		ExpdShow(show, mine, x + 1, y);
		ExpdShow(show, mine, x, y + 1);
		ExpdShow(show, mine, x, y - 1);
	}
	else if (n != 0)
	{
		show[x][y] = n + '0';
	}
}

int WinBoard(char board[ROWS][COLS], int x, int y) //胜利条件判断
{
	int i = 0;
	int count = 0; //记数
	for (i = 1; i <= x; i++)
	{
		int j = 0;
		for (j = 1; j <= y; j++)
		{
			if (board[i][j] == '*')
				count++;
		}
	}
	if (count == 10)
		return 1;
	else
		return 0;
}

void FindMine(char show[ROWS][COLS], char mine[ROWS][COLS], int x, int y)
{
	//一直查找,直到满足胜利条件:
	while (1)
	{
		int a = 0; //接受行号
		int b = 0; //接受列号
		printf("请输入你要排查的坐标(行 列):> ");
		scanf("%d%d", &a, &b);
		//判断坐标是否合法:
		if (0 < a && a <= x && 0 < b && b <= y)
		{
			//判断是否踩雷
			if (mine[a][b] == '1')
			{
				printf("Boom!你已经踩雷,游戏失败\n\n");
				PrintBoard(mine, ROW, COL); //死得明白
				break; //结束找雷
			}
			else
			{
				//不是雷的逻辑;
				//递归展开:
				ExpdShow(show, mine, a, b);
				PrintBoard(show, ROW, COL);
				if (WinBoard(show, ROW, COL))
				{
					printf("恭喜你获得胜利!!\n\n");
					printf("请看埋雷点:\n");
					PrintBoard(mine, ROW, COL);
					break;
				}
			}
		}
		else
		{
			printf("输入不合法\n请输入1~9\n");
		}
	}
}
test.c
#define _CRT_SECURE_NO_WARNINGS
#include"game.h"
void game()
{
	//设置两个棋盘:一个显示,一个埋雷:
	char mine[ROWS][COLS] = { 0 };
	char show[ROWS][COLS] = { 0 };
	
	//1.初始化棋盘:
	IntiBoard(mine, ROWS, COLS, '0');
	IntiBoard(show, ROWS, COLS, '*');
	
	//2.打印出棋盘:
	//  美化给棋盘标上序号,只需要打印下标 1~9
	PrintBoard(show, ROW, COL);
	//PrintBoard(mine, ROW, COL);


	srand((unsigned int)time(NULL));
	//3.埋雷:
	//随机数的生产,rand() -- 搭配时间戳
	GetMine(mine, ROW, COL);
	//PrintBoard(mine, ROW, COL);
	
	//4.查找雷!!(重点)
	FindMine(show, mine, ROW, COL);

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

void test()
{
	int input = 0;
	do 
	{
		//打印菜单:
		menu();
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("请退出\n\n");
			break;
		default:
			printf("请重新选择\n\n");
			break;
		}
	} while (input);

}

int main()
{
	test();
	return 0;
}

标签:ROWS,递归,show,int,mine,COLS,char,详解,扫雷
From: https://blog.csdn.net/2403_88316985/article/details/144181291

相关文章

  • WPF中的XAML Behaviors(行为)详解
    什么是XAMLBehaviors(行为)XAMLBehaviors提供了一种简单易用的方法,能以最少的代码为WindowsUWP/WPF应用程序添加常用和可重复使用的交互性。 可以通过一个最简单的示例来感受一下首先我们引用Microsoft.Xaml.Behaviors.Wpf包,然后添加如下的代码。在下面的代码中,我们放......
  • ZooKeeper最全详解 (万字图文总结!!!)
    目录一、什么是ZooKeeper1.1ZooKeeper的特点1.2ZooKeeper架构1.3ZooKeeper数据模型1.4数据节点类型二、Zookeeper安装2.1单机安装2.2集群安装2.3ZooKeeperACL使用2.4ZooKeeper使用场景2.5服务启动流程2.5.1单机启动2.5.2集群启动三、Zookeeper之ZAB......
  • React 组件通信全攻略:父子、兄弟、跨层级与非父子关系通信详解
    在React应用的开发过程中,组件通信是构建复杂用户界面和交互逻辑的关键环节。有效的组件通信能够确保数据在不同组件之间准确传递与共享,从而实现应用功能的完整性与流畅性。本文将深入探讨React组件通信的多种方式及其适用场景,帮助开发者更好地掌握这一核心概念。一、父子......
  • 4、背包问题(动态规划)(递归,回溯,迭代)
    一、递归,回溯,迭代 在开始回溯算法前,我想先弄清这三个的关系 递归是指一个函数在定义中直接或间接地调用自身,递归表现为调用函数本身,通过将问题分解为子问题来逐步解决。回溯算法会在搜索过程中尝试一个方案,如果发现当前方案无法满足要求,就“回退”到上一个步骤,尝试其他......
  • 递归 算法
    递归、搜索与回溯算法1.汉诺塔2.合并两个有序链表3.反转链表4.两两交换链表中的节点5.Pow(x,n)-快速幂1.汉诺塔题目链接:面试题08.06.汉诺塔问题解题思路:首先观察有一个、两个、三个盘子时的情况,手动去挪不难发现,大致都是先将上面n-1个盘子借助C的辅助,挪动到......
  • 网站修改域名麻烦吗,网站域名修改流程详解
    修改网站的域名涉及多个步骤,但只要按照正确的流程操作,通常不会太麻烦。以下是详细的步骤:备份网站:使用FTP工具(如FileZilla)下载网站的所有文件。使用数据库管理工具(如phpMyAdmin)导出数据库。购买新域名:在域名注册商处购买新的域名。确认域名解析设置正确。修改DNS......
  • 数据结构之算法复杂度(超详解)
    文章目录1.算法复杂度1.1数据结构1.2算法1.3二者的重要性2.算法效率开胃小菜:复杂度概念3.时间复杂度3.1大O表示法3.2时间复杂度示例练习例1例2例3例4例5例6例74.空间复杂度4.1空间复杂度示例练习例1例25.开胃小菜扩展5.1思路2:采用空间换时间的方法......
  • Windows 输入法详解
    一、输入法的实现原理 输入法的输入原理主要包括以下几个步骤:输入捕获:用户通过键盘输入字符,输入法截获这些按键事件。对于中文输入,通常是拼音、五笔或其他编码。编码解析:输入法将用户输入的编码(如拼音)解析为可能的汉字或词组候选项。候选项生成:根据输入的编码......
  • Windows API SendInput 详解
      一、SendInput API基本介绍SendInput函数概述SendInput是WindowsAPI中的一个函数,位于user32.dll中。它允许应用程序以一种更直接的方式模拟用户输入设备(如键盘和鼠标)的操作。与一些其他模拟输入的方法(如SendMessage和PostMessage)相比,SendInput提供了一种更高级别......
  • 【数据结构】 字典树trie详解
    定义:将多个字符串以树的方式存储即为字典树,如图,\(1,4,3,12\)表示\(cca\),我么用\(ch[i][j]\)来表示第\(i\)个节点的\(j\)字符所指向的下一个节点,\(tag[u]\)表示节点\(u\)是否代表一个字符串的结尾,如果是的话,\(tag[u]=1\)。模板CODE添加字符串//n表示即将要向字典......