C语言实现递归版扫雷游戏实例

 更新时间:2022年01月25日 10:09:46   作者:绅士·永  
大家好,本篇文章主要讲的是C语言实现递归版扫雷游戏实例,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下

思路

清晰的逻辑

为方便将其分为三个文件:text.c(测试) game.c(函数实现) game.h(头文件声明)

在排雷的时候为了方便,我们需要将每一行每一列对应的行数,列数打印出来。

#define LEI 10
#define ROW 10
#define LOW 10
 
#define ROWS ROW+2
#define LOWS LOW+2
//在定义棋盘的长宽时,特意加上2,便于标记行数列数。

菜单

打印的菜单只需要有开始游戏、退出游戏的选项即可

void menu()
{
	printf("*************************************\n");
	printf("************1.开始游戏***************\n");
	printf("************0.退出游戏***************\n");
	printf("*************************************\n");
 
}

棋盘

1.雷盘

2.棋盘

扫雷需要先记录雷的信息再进行排雷,如果使用一个棋盘太过于复杂,所以我们使用两个棋盘,一个用于布置雷,一个用于玩家排雷。

两个棋盘初始化

布置雷的棋盘初始化,将字符‘0’作为非雷,字符‘1’作为雷。

玩家盘将字符‘*’作为还没有扫的地方

    board(arr1, ROWS, LOWS, '0');//雷盘
	board(arr2, ROWS, LOWS, '*');//玩家盘

因为两个的初始化方式不同,所以我们采用传参ret初始化

//初始化棋盘
void board(char arr1[ROWS][LOWS], int rows, int lows, char ret)
{
	int i = 0;
	for (i = 0; i < rows; i++)
	{
		int j = 0;
		for (j = 0; j < lows; j++)
		{
			arr1[i][j] = ret;
		}
	}

布置雷

布置的雷放置需要随机,所以采用两个随机数来定位坐标。

 
//布置雷
void Get_lei(char arr1[ROWS][LOWS], int row, int low)
{
	int count = LEI;
	while (count)
	{
		int x = rand() % row + 1;
		int y = rand() % low + 1;
		if (arr1[x][y] == '0')
		{
			arr1[x][y] = '1';
			count--;
		}
	}
	//displayboard(arr1, ROW, LOW);//用于测试
}

排雷

当我们输入一个坐标时,我们需要知道这个坐标周围雷的个数,定义一个Get_num函数来获取雷个数。但此时只能获取一个坐标的信息,我们知道一般的扫雷,如果当前坐标雷的个数为0,就会展开,这个过程较为复杂,所以我们使用递归来实现

//玩家盘
static int Get_num(char arr1[ROWS][LOWS],int x, int y)//获得当前坐标周围雷的个数
{
	int count = 0;
	int i = 0;
	for (i = x - 1; i <= x + 1; i++)
	{
		int j = 0;
		for (j = y - 1; j <= y + 1; j++)
		{
			if (arr1[i][j] == '1')
			{
				count++;
			}
		}
 
	}
	return count;
}
//判断是否展开,实现函数
static void Judge(char arr2[ROWS][LOWS], char arr1[ROWS][LOWS], int x, int y)
{
	if (x > 0 && x <= ROW && y > 0 && y <= LOW)
	{
		int ret = Get_num(arr1, x, y);
		if (ret != 0)
			arr2[x][y] = ret + '0';//记录雷的个数
		//递归散开
		else if (arr1[x][y] != ' ')
		{
			arr2[x][y] = '0';
			arr1[x][y] = ' ';
			int i = 0;
			for (i = x - 1; i <= x + 1; i++)
			{
				int j = 0;
				for (j = y - 1; j <= y + 1; j++)
				{
					Judge(arr2, arr1, i, j);
				}
			}
		}
		else
		{
			return;
		}
	}
}

判断输赢

输:即每排一次雷,检查一下雷盘对应的信息,如果是雷,就被炸死,如果不是,就继续排雷。

赢:当玩家将所有的非雷的区域都排查出来时,判断为赢。(这里采用一个计数器,没排一次雷计数器就++一下,当计数器与总的非雷的区域数目相同时,判断为赢)

void Out_lei(char arr2[ROWS][LOWS], int row, int low, char arr1[ROWS][LOWS])
{
	int x = 0;
	int y = 0;
	while (1)
	{
		printf("请输入坐标:>");
		scanf("%d,%d", &x, &y);
		if (x >= 1 && x <= ROW && y >= 1 && y <= LOW)
		{
			if (arr1[x][y] == '1')
			{
				arr2[x][y] = '#';
				displayboard(arr2, ROW, LOW);//排雷
				printf("遗憾你输了\n");
				break;
			}
			else
			{
				Judge(arr2, arr1, x, y);
				displayboard(arr2, ROW, LOW);//排雷
			}
		}
		else
		{
			printf("输入错误!\n");
		}
 
		//判断扫雷是否赢
		int i = 0, flag = 0;
		for (i = 1; i <= ROW; i++)
		{
			int j = 0;
			for (j = 1; j <= LOW; j++)
			{
				if (arr2[i][j] != '*')
				{
					flag++;
				}
			}
		}
		if (flag == ROW*LOW - LEI)
		{
			printf("你赢了!\n");
			break;
		}
	}
}

text.c实现

#define  _CRT_SECURE_NO_WARNINGS 1
 
#include "game.h"
//菜单
void menu()
{
	printf("*************************************\n");
	printf("************1.开始游戏***************\n");
	printf("************0.退出游戏***************\n");
	printf("*************************************\n");
 
}
 
 
void game()
{
	//初始化棋盘
	char arr1[ROWS][LOWS] = { 0 };//雷盘
	char arr2[ROWS][LOWS] = { 0 };//玩家盘
	board(arr1, ROWS, LOWS, '0');
	board(arr2, ROWS, LOWS, '*');
	//打印棋盘
	//displayboard(arr1, ROW, LOW);//布置雷
	displayboard(arr2, ROW, LOW);//排雷
	//布置雷
	Get_lei(arr1,ROW,LOW);
	//排雷
	Out_lei(arr2,ROW,LOW, arr1);
 
}
int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d",&input);
		switch (input)
		{
		case 1:
		{
			printf("扫雷\n");
			game();
			break;
		}
		case 0:
		{
			printf("退出游戏\n");
			break;
		}
		default:
		{
			printf("选择错误\n");
			break;
		}
		}
	} while (input);
 
	return 0;
}

game.c实现

#define  _CRT_SECURE_NO_WARNINGS 1
 
#include "game.h"
 
//初始化棋盘
void board(char arr1[ROWS][LOWS], int rows, int lows, char ret)
{
	int i = 0;
	for (i = 0; i < rows; i++)
	{
		int j = 0;
		for (j = 0; j < lows; j++)
		{
			arr1[i][j] = ret;
		}
	}
}
 
//打印棋盘
void displayboard(char arr1[ROWS][LOWS], int row, int low)
{
	printf("<———扫雷游戏———>\n");
	int i = 0;
	for (i = 1; i <= row; i++)
	{
		int j = 0;
		if (i == 1)
		{
			for (j = 0; j <= low; j++)
			{
				printf("%2d ", j);
			}
			printf("\n");
		}
 
		for (j = 1; j <= low; j++)
		{
			if (j == 1)
			{
				printf("%2d ", i);
			}
			
			printf("%2c ", arr1[i][j]);
		}
		printf("\n");
	}
	printf("<———扫雷游戏———>\n");
 
}
 
//布置雷
void Get_lei(char arr1[ROWS][LOWS], int row, int low)
{
	int count = LEI;
	while (count)
	{
		int x = rand() % row + 1;
		int y = rand() % low + 1;
		if (arr1[x][y] == '0')
		{
			arr1[x][y] = '1';
			count--;
		}
	}
	//displayboard(arr1, ROW, LOW);
}
//玩家盘
static int Get_num(char arr1[ROWS][LOWS],int x, int y)
{
	int count = 0;
	int i = 0;
	for (i = x - 1; i <= x + 1; i++)
	{
		int j = 0;
		for (j = y - 1; j <= y + 1; j++)
		{
			if (arr1[i][j] == '1')
			{
				count++;
			}
		}
 
	}
	return count;
}
//判断是否展开,实现函数
static void Judge(char arr2[ROWS][LOWS], char arr1[ROWS][LOWS], int x, int y)
{
	if (x > 0 && x <= ROW && y > 0 && y <= LOW)
	{
		int ret = Get_num(arr1, x, y);
		if (ret != 0)
			arr2[x][y] = ret + '0';
		//递归散开
		else if (arr1[x][y] != ' ')
		{
			arr2[x][y] = '0';
			arr1[x][y] = ' ';
			int i = 0;
			for (i = x - 1; i <= x + 1; i++)
			{
				int j = 0;
				for (j = y - 1; j <= y + 1; j++)
				{
					Judge(arr2, arr1, i, j);
				}
			}
		}
		else
		{
			return;
		}
	}
}
void Out_lei(char arr2[ROWS][LOWS], int row, int low, char arr1[ROWS][LOWS])
{
	int x = 0;
	int y = 0;
	while (1)
	{
		printf("请输入坐标:>");
		scanf("%d,%d", &x, &y);
		if (x >= 1 && x <= ROW && y >= 1 && y <= LOW)
		{
			if (arr1[x][y] == '1')
			{
				arr2[x][y] = '#';
				displayboard(arr2, ROW, LOW);//排雷
				printf("遗憾你输了\n");
				break;
			}
			else
			{
				Judge(arr2, arr1, x, y);
				displayboard(arr2, ROW, LOW);//排雷
			}
		}
		else
		{
			printf("输入错误!\n");
		}
 
		//判断扫雷是否赢
		int i = 0, flag = 0;
		for (i = 1; i <= ROW; i++)
		{
			int j = 0;
			for (j = 1; j <= LOW; j++)
			{
				if (arr2[i][j] != '*')
				{
					flag++;
				}
			}
		}
		if (flag == ROW*LOW - LEI)
		{
			printf("你赢了!\n");
			break;
		}
	}
}
 
 

game.h实现

#pragma once
 
#include <stdio.h>
#include <stdlib.h>
 
#define LEI 10
#define ROW 10
#define LOW 10
 
#define ROWS ROW+2
#define LOWS LOW+2
 
//初始化棋盘
void board(char arr1[ROWS][LOWS],int rows,int lows,char ret);
 
//打印棋盘
void displayboard(char arr1[ROWS][LOWS], int row, int low);
 
//布置雷
void Get_lei(char arr1[ROWS][LOWS], int row, int low);
//玩家盘
void Out_lei(char arr2[ROWS][LOWS], int row, int low, char arr1[ROWS][LOWS]);

递归部分详解

递归条件:1.有停止的条件。2.每一次递归都会向这个条件靠拢。

那么这里的停止条件是什么呢?

递归:当返回雷的个数为0时,就符合继续递归的条件,我们需要将当前坐标周围的点全部排除。且需要将已经排查了的坐标做一个标记,否则就会不停的排查下去,就会形成死递归。所以

停止条件:当前这个坐标是已被排查过的,就停止递归。

因为每一次排查都会的一个标记,所以这就是那个不断向停止条件靠拢的过程。

//判断是否展开,实现函数
static void Judge(char arr2[ROWS][LOWS], char arr1[ROWS][LOWS], int x, int y)
{
	if (x > 0 && x <= ROW && y > 0 && y <= LOW)
	{
		int ret = Get_num(arr1, x, y);
		if (ret != 0)
			arr2[x][y] = ret + '0';
		//递归散开
		else if (arr1[x][y] != ' ')
		{
			arr2[x][y] = '0';//玩家盘
			arr1[x][y] = ' ';//雷盘
			int i = 0;
			for (i = x - 1; i <= x + 1; i++)
			{
				int j = 0;
				for (j = y - 1; j <= y + 1; j++)
				{
					Judge(arr2, arr1, i, j);
				}
			}
		}
		else
		{
			return;
		}
	}
}

总结

到此这篇关于C语言实现递归版扫雷游戏实例的文章就介绍到这了,更多相关C语言递归版扫雷内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 解析C++中的虚拟函数及其静态类型和动态类型

    解析C++中的虚拟函数及其静态类型和动态类型

    虚拟函数(Visual Function)亦常被成为虚函数,是C++中的一个重要特性,本文我们就来解析C++中的虚拟函数及其静态类型和动态类型
    2016-06-06
  • opengl实现直线扫描算法和区域填充算法

    opengl实现直线扫描算法和区域填充算法

    这篇文章主要为大家详细介绍了opengl实现直线扫描算法和区域填充算法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-04-04
  • C语言 详解如何删除有序数组中的重复项

    C语言 详解如何删除有序数组中的重复项

    数组不擅长插入(添加)和删除元素。数组的优点在于它是连续的,所以查找数据速度很快。但这也是它的一个缺点。正因为它是连续的,所以当插入一个元素时,插入点后所有的元素全部都要向后移;而删除一个元素时,删除点后所有的元素全部都要向前移
    2022-03-03
  • 电脑开机时间的计算代码

    电脑开机时间的计算代码

    这几天我琢磨着一件事,那就是怎么计算我的PC从开机到现在的总时间。终于,看看这个函数:GetTickCount();
    2013-05-05
  • Qt通过图片组绘制动态图片

    Qt通过图片组绘制动态图片

    这篇文章主要为大家详细介绍了Qt通过图片组绘制动态图片,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-07-07
  • C语言细致讲解线程同步的集中方式

    C语言细致讲解线程同步的集中方式

    多线程中的线程同步可以使用,CreateThread,CreateMutex 互斥锁实现线程同步,通过临界区实现线程同步,Semaphore 基于信号实现线程同步,CreateEvent 事件对象的同步,以及线程函数传递单一参数与多个参数的实现方式
    2022-05-05
  • C++回溯算法中的全排列问题分析探讨

    C++回溯算法中的全排列问题分析探讨

    递归中遇到一个问题全排列的问题,我看见回溯特别神奇,特此记录一下。对比一下深度优先搜索与广度优先搜索,个人感觉这里的回溯像是一种递归树中的深度优先搜索的算法,他不断构造往下延伸的深度,使其达到完全编列
    2023-03-03
  • C++数据精度问题(对浮点数保存指定位小数)

    C++数据精度问题(对浮点数保存指定位小数)

    这篇文章主要介绍了对浮点数保存指定位小数。比如, 1.123456. 要保存1位小数,,调用方法后, 保存的结果为: 1.1。 再比如,1.98765, 保存2位小数的结果为: 2.00,需要的朋友可以参考下
    2017-08-08
  • 浅析VSCode launch.json中的各种替换变量的意思 ${workspaceFolder} ${file} ${fileBasename} ${fileDirname}等

    浅析VSCode launch.json中的各种替换变量的意思 ${workspaceFolder} ${file} $

    这篇文章主要介绍了VSCode launch.json中的各种替换变量的意思 ${workspaceFolder} ${file} ${fileBasename} ${fileDirname}等,非常不错具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-03-03
  • Qt在vs2019中使用及设置方法

    Qt在vs2019中使用及设置方法

    这篇文章主要介绍了Qt在vs2019中使用及设置方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08

最新评论