C语言实现简易“扫雷”小游戏(含递归)

游戏思路概要

扫雷游戏是一款基于逻辑推理的益智游戏。玩家需要使用推理和猜测的能力,在未知区域的地雷位置周围进行标记,并避免触发地雷。

  1. 游戏初始化:

    • 创建一个游戏区域,被分成网格状的方块。
    • 在方块中随机布置地雷。
  2. 点击方块:

    • 玩家根据自己的判断和推理,选择一个未翻开的方块进行点击。
    • 如果点击的方块是地雷,游戏结束,玩家失败。
    • 如果点击的方块周围有地雷,显示该方块周围地雷的数量。
    • 如果点击的方块周围没有地雷,递归地翻开相邻的空白方块。
  3. 游戏结束条件:

    • 翻开所有不带地雷的方块,游戏获胜。
    • 点击到地雷,游戏失败。

代码思路

  • 建立一个头文件“game.h”用来封装所需要使用的头文件以及函数的声明。
  • 建立一个源文件“game.c”用来对头文件中需要使用的函数进行定义。
  • 建立另一个源文件“PlayGame.c”实现扫雷游戏。
图片展示:

函数介绍:


srand() 和 rand() 是C语言中用于生成随机数的标准库函数,它们声明在 stdlib.h 头文件中。

1、srand()
srand() 函数用于设置随机数生成器的种子。使用 srand() 函数可以改变随机数生成器的起始点,从而影响后续调用 rand() 生成的随机数序列。

函数原型如下:

void srand(unsigned int seed);
参数 seed 是一个无符号整数值,用作随机数生成器的种子。常用的种子值有 time(NULL),它使用当前时间作为种子,因此每次程序运行都会得到不同的随机数序列。

2、time()
time() 是C语言中的一个标准库函数,声明在 time.h 头文件中。它用于获取当前的系统时间并返回一个 time_t 类型的值。

函数原型如下:

time_t time(time_t* timer);
参数 timer 是一个可选的指向 time_t 对象的指针,用于接收时间值。如果传入 NULL,则 time() 函数会仅返回时间值,而不会将其存储到指定的位置。

返回值是一个表示当前时间的 time_t 类型的值,通常是一个整数,表示自某个特定时间(通常是系统特定的起始时间点)到调用 time() 时经过的秒数。

使用 time() 函数可以实现获取当前时间的功能。

3、rand()
rand() 函数用于生成一个随机数。

函数原型如下:

int rand(void);
rand() 函数返回一个在范围 [0, RAND_MAX] 内的伪随机整数。RAND_MAX 是一个常量,表示随机数的最大可能值,通常是一个较大的整数。

若要生成一个指定范围内的随机数,可以使用取余运算符 % 对 rand() 的返回值进行处理,例如:rand() % n 表示生成一个范围在 [0, n-1] 的随机整数。

由于rand()函数生成的随机值具有局限性,且只能生成一次随机数,所以我们在主函数中调用srand()函数,以及使用time(NULL)时刻改变其参数,使其程序调用rand()函数都可以生成不同的伪随机数,极大的确保了电脑下棋的随机性。
我们列举一个简单的生成随机数的例子:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
 
int main(void) {
    int i;
    srand((unsighed int)time(NULL)); // 设置种子为当前时间
    for (i = 0; i < 5; i++) {
        printf("%d\n", rand()); // 生成随机数并打印
    }
    return 0;
}


**** 由于time(NULL)的返回值为time_t类型,通常是一个整数。由于游戏中所需的随机数类型为正整数,所以我们将time(NULL)的返回值用(unsigned int)强制转换为“无符号整型”,以确保电脑下棋的可行性。

注意:

在这个程序中,我们需要使用到两个数组mine[rows][cols]和show[rows][cols]。本次我们实现9x9的扫雷棋盘。见下图:

所以我们在程序中进行如下定义。
#define row 9
#define col 9
#define rows row+2
#define cols col+2
既然是实现9x9的棋盘,我们为什么要建立11x11大小的数组呢?我们知道,“如果点击的方块周围有地雷,显示该方块周围地雷的数量”。这就要求我们要对输入坐标周围的8个位置进行遍历,判断一共有几个地雷。但当需要判断的坐标位于边界时,我们下面这段判断地雷个数的函数会越界。
void get_mine_count(char mine[rows][cols],char show[rows][cols], int a, int b)
{
	int i, j, sum = 0;
	if (a >= 1 && a <= row && b >= 1 && b <= col)
	{
		for (i = a - 1; i <= a + 1; i++)
		{
			for (j = b - 1; j <= b + 1; j++)
			{
				sum += mine[i][j] - '0';
			}
		}
	}
}
所以我们建立一个11x11大小的棋盘并且将棋盘最外圈不放地雷,并且遍历的范围只在棋盘内部9x9大小的棋盘上。这样既能保证数组不会越界,也能准确的判断出坐标周围地雷的个数。

 那么我们为什么要使用两个数组呢?

在扫雷游戏的实现中,可使用两个数组来表示游戏区域,一个数组用于存储地雷的位置信息,另一个数组用于存储每个方块的状态和周围地雷的数量。

以下是使用两个数组的原因:

  1. 地雷数组【mine[rows][cols]】:
    • 地雷数组用于记录地雷的位置。
    • 在游戏开始时,通过随机生成的方式将地雷的位置分布在游戏区域中。
    • 该数组的元素用来表示方块是否为地雷,例如 1 表示地雷,0 表示非地雷。
  2. 状态数组【show[rows][cols]】:此数组用于将棋盘状态信息打印于屏幕上
    • 状态数组用于记录每个方块的状态,如方块是否翻开、是否为地雷、周围地雷的数量等。
    • 这个数组的元素可以是枚举类型,表示不同的状态和信息。

通过使用两个数组,程序能够有效地将地雷的位置和方块的状态进行分离存储。这样,在玩家进行点击或标记操作时,程序可以根据状态数组进行判断和更新,而无需直接读取和操作地雷数组。这样的设计可以简化游戏逻辑实现,并提高运行效率。

棋盘展示:

show[rows][cols]实现:

 mine[rows][cols]数组实现: 1 表示地雷,0 表示非地雷

整体流程

1、头文件:game.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define row 9
#define col 9
#define rows row+2
#define cols col+2
#define count 10
void meau();//菜单
void game();
void init(char board[rows][cols],char s);//初始化扫雷棋盘
void display(char board[rows][cols]);//打印棋盘
void setmine(char mine[rows][cols]);//设置“地雷”
void findmine(char mine[rows][cols], char show[rows][cols]);//寻找地雷
void get_mine_count(char mine[rows][cols], int a, int b);//获取环绕(a,b)坐标周围8个坐标内“地雷”的个数。
int is_win(char show[rows][cols]);//判断棋盘上剩余地雷数量

2、源文件:game.c

#include"game.h"//game.h 此头文件为个人创建,引用时需加双引号

//菜单,为玩家提供是否进入游戏的选择
void meau()
{
	printf("****************************\n");
	printf("******    1. play    *******\n");
	printf("******    0. exit    *******\n");
	printf("****************************\n");
}

//判断棋盘上剩余地雷数量,并返回地雷的数量
int is_win(char show[rows][cols])
{
	int i, j, key = 0;
	for (i = 1; i <= row; i++)
	{
		for (j = 1; j <= col; j++)
		{
			if (show[i][j] == '*')
			{
				key++;
			}
		}
	}
	return key;
}

//初始化地雷棋盘
void init(char board[rows][cols],char s)
{
	int i, j;
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			board[i][j] = s;
		}
	}
}

//打印棋盘
void display(char board[rows][cols])
{
	int i, j;
	for (i = 0; i <= row; i++)
	{
		if (i == 0)
			printf("    ");
		else
		printf("%-2d",i);
	}
	printf("\n");
	for (i = 0; i <= col; i++)
	{
		if (i == 0)
			printf("   ");
		else
		printf("__");
	}
	printf("\n");
	for (i = 1; i <= row; i++)
	{
		printf("%-3d|",i);
		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
}

//在mine数组中设置count个地雷
void setmine(char mine[rows][cols])
{
	int a, b, c = count;
	while (c>0)
	{
		a = rand() % row + 1;
		b = rand() % col + 1;
		if (mine[a][b] == '0')
		{
			mine[a][b] = '1';
			c--;
		}
	}
}

//获取环绕(a,b)坐标周围8个坐标内“地雷”的个数。
void get_mine_count(char mine[rows][cols],char show[rows][cols], int a, int b)
{
	int i, j, sum = 0;
//限制变量不越界
	if (a >= 1 && a <= row && b >= 1 && b <= col)
	{
		for (i = a - 1; i <= a + 1; i++)
		{
			for (j = b - 1; j <= b + 1; j++)
			{
				sum += mine[i][j] - '0';
			}
		}
	}
	show[a][b] = '0' + sum;
//当环绕(a,b)坐标周围8个坐标内“地雷”的个数为0时,(a,b)对应的位置为‘0’.
//将满足上述条件的坐标位置置空
	if (show[a][b] == '0')
	{
		show[a][b] = ' ';
	}

/*
当(a,b)坐标为'0'时,进行递归,判断其周围坐标是否满足【环绕(a,b)坐标周围8个坐标内“地雷”的个数为0】。
*/
	if (sum == 0 && a >= 1 && a <= row && b >= 1 && b <= col)
	{
		for (i = a - 1; i <= a + 1; i++)
		{
			for (j = b - 1; j <= b + 1; j++)
			{
//if()如果不添加此限制条件,将进入死递归。因为已经置空的坐标无需再进行判断
				if(show[i][j]=='*')
				get_mine_count(mine, show, i, j);
			}
		}
	}
}

//寻找地雷
void findmine(char mine[rows][cols], char show[rows][cols])
{
	int a, b, sum ;
	int i, j;
	while (1)
	{
		printf("请输入坐标,eg: nun1 num2 >:");
		scanf("%d %d", &a, &b);
//限制变量不越界
		if (a >= 1 && a <= 9 && b >= 1 && b <= 9)
		{
			if (mine[a][b] != '1')
			{
				get_mine_count(mine,show, a, b);
				display(show);
			}
			else
			{
				printf("踩到雷了!!!\n");
				display(show);
				break;
			}
		}
		else
		{
			printf("输入坐标错误,请重新输入>:");
		}

//如果is_win()函数的返回值与设置的地雷数量相等,则扫雷成功。
		if (is_win(show) == count)
		{
			printf("恭喜获胜!!!\n");
			display(show);
			printf("\n");
			break;
		}
	}
}

3、源文件:playgame.c

#include"game.h"
void game()
{
	char mine[rows][cols];
	char show[rows][cols];
	init(mine, '0');
	init(show, '*');
	display(show);
	setmine(mine);
	display(mine);
	findmine(mine, show);
}
int main()
{
	srand((unsigned int)time(NULL));
	int input;
	do
	{
		meau();
		printf("请选择>:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("退出游戏!");
			break;
		default:
			printf("输入错误,请重新输入>:");
			break;
		}
	}while (input);
	return 0;
}

 总结:

        由于博主自身水平原因,此处的 模块化设计 以及 递归 的应用不是特别好。本节主要为大家实现简易扫雷游戏。小伙伴们如果有更好的递归或判断方法欢迎在评论区交流意见。

  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

这题怎么做?!?

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值