C语言_扫雷游戏

这篇博客是为了记录扫雷游戏的全部C语言代码。与前面记录三子棋游戏的博客一样,我会尽最大努力将扫雷游戏的所有细节都梳理出来(在代码中详细注释)。

游戏介绍

总体来说: 扫雷游戏一开始会初始化一个棋盘,在棋盘中任意的位置埋好雷,然后玩家开始排雷,如果玩家将所有的雷的位置都找到,那么玩家胜利。

具体细节:

  1. 游戏开始,初始化一个棋盘,将雷埋好
  2. 游戏第一步,玩家随机选个位置,开始排雷
  3. 玩家排雷时有两个选择:直接排雷或者标记该位置为雷
  4. 直接排雷时:如果该位置是雷,那么游戏结束,玩家失败;如果该位置不是雷,那么将这个位置周围雷的个数显示在该位置,如果周围雷的个数为零,那么遍历该位置周围其他位置,将其他位置周围雷的个数为零的自动排掉
  5. 标记雷:将某个位置标记为雷
  6. 重复执行3-5,直到游戏结束

代码逻辑

为了方便代码编写,也为了更清晰的逻辑,扫雷游戏也是用多文件的方式来实现。总共有三个文件:

  1. game.h 包括所有的头文件引用、宏定义和功能函数的声明
  2. game.c 包括所有的功能函数的实现
  3. saolei.c 游戏的主体框架以及非功能函数

实现思路

首先需要定义两个二维数组mine[ROWS][COLS]show[ROWS][COLS]来存储棋盘中的内容,show[ROWS][COLS]存放要展示给玩家的信息,初始时里面全放成'*'mine[ROWS][COLS]用来存储雷的信息,有雷的位置为'1',没雷的位置为'0'
注意:数组的行和列要比棋盘大2,举个例子来看,如下图所示:
在这里插入图片描述
红色框中是我们要展示给玩家的 9*9 的棋盘,总共的格子大小是11*11的。玩家选择某个位置排雷时,需要检查以这个位置周围的位置有没有雷,当玩家选择边界位置时(如上图中绿色方格),方便起见,我们还是要统计周围位置雷的个数(蓝色方格),这时候如果数组的大小跟棋盘大小一样,那么就会造成数组的越界访问,为避免这个问题,我们就设置数组比棋盘大一圈。
然后在排雷过程中,把玩家标记为雷的位置显示'#',把玩家选择排雷的位置显示周围雷的个数。这两个操作修改的都是show数组中的内容。
最后如何判断玩家是否胜利,定义一个变量win,每确定一个不是雷的位置,win就增加1,如果win=棋盘格子个数-雷的个数,那说明玩家已经找到所有雷了,所以胜利。

全部代码

game.h

// game.h

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <time.h>
#include <stdlib.h>

#define EASY_COUNT 10 // 雷的个数,可修改

#define ROW 9  // 棋盘的行数
#define COL 9  // 棋盘的列数

#define ROWS ROW+2 // 数组的行
#define COLS COL+2 // 数组的列


// AutoFind()和GetMineCount()写在game.c里面了
// AutoFind是自动展开函数,GetMineCount是计算周围雷函数

// 初始化
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);

// 打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col);

// 布置雷
void SetMine(char board[ROWS][COLS], int row, int col);

// 扫雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

// 标记雷
void MarkShow(char show[ROWS][COLS], int x, int y, int row, int col);

game.c

// game.c

#include "game.h"

// 初始化函数 - 初始化指定的数组,为数组每个位置赋值
// 函数第一个参数是传入指定的数组,第二、三个参数是指定行数、列数,最后一个参数是要赋给数组的值
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			board[i][j] = set;
		}
	}
}

// 展示函数 - 将棋盘和棋盘上的情况展示出来 
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	printf("-------------------------------------\n");
	
	// 列号的打印
	for (i = 0; i <= col; i++)
	{
		printf("%d ", i);
	}
	printf("\n");

	for (i = 1; i <= row; i++)
	{
		// 行号的打印
		printf("%d ", i);
		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
	printf("-------------------------------------\n");
}

// 布置雷的函数 - 生成随机坐标,将该坐标位置设置成雷
// 注意这个函数名称,Mine代表着mine数组,是存储雷的信息的数组,不能给玩家展示的数组,只能自己看
void SetMine(char board[ROWS][COLS], int row, int col)
{
	// 1.随机找坐标布置雷
	int count = EASY_COUNT; // EASY_COUNT是前面宏定义的数,代表雷的个数
	while (count)
	{
		// 成功布置一个雷,count--
		// 1. 生成随机坐标
		int x = rand() % row + 1; // 生成的随机数+1之后的范围是(1,row)
		int y = rand() % col + 1;
		// 2. 布置雷
		if (board[x][y] == '0') // 如果当前位置是空的,那么可以设置为雷
		{
			board[x][y] = '1'; // 用字符'1'来代表雷
			count--;
		}
	}
}

// 统计周围雷个数的函数
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
	// '0'字符和'1'字符的ASCII码值分别为48、49,
	// 把所有位置字符的值加起来,减去8*'0',就能得到周围雷的个数
	return mine[x - 1][y] +
		mine[x - 1][y - 1] +
		mine[x][y - 1] +
		mine[x + 1][y - 1] +
		mine[x + 1][y] +
		mine[x + 1][y + 1] +
		mine[x][y + 1] +
		mine[x - 1][y + 1] - 8 * '0';
}

// 扫雷游戏是怎么结束的?
// 1. 炸死了
// 2. 排查出来所有雷

// 自动展开函数 - 递归判断周围雷的个数为0的位置,将该位置置为'0',再遍历该位置的周围位置
// 该函数涉及到mine和show两个数组
int AutoFind(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int row, int col)
{
	int win = 0; 
	if (x >= 1 && x <= row && y >= 1 && y <= col && show[x][y]=='*') // 如果坐标正确才可继续
	{
		int count = GetMineCount(mine, x, y); // 统计(x,y)周围雷的个数

		if (0 == count) // 如果(x,y)周围雷的个数为0,那么遍历周围位置
		{
			win++; // 每找到一个周围雷个数为0的位置,那么说明排掉了一个位置
			show[x][y] = '0';  // 将这个位置显示为雷的个数
			// 下面就是遍历周围位置
			win += AutoFind(mine, show, x, y - 1, ROW, COL);
			win += AutoFind(mine, show, x, y + 1, ROW, COL);
			win += AutoFind(mine, show, x - 1, y, ROW, COL);
			win += AutoFind(mine, show, x + 1, y, ROW, COL);
			win += AutoFind(mine, show, x - 1, y - 1, ROW, COL);
			win += AutoFind(mine, show, x + 1, y - 1, ROW, COL);
			win += AutoFind(mine, show, x - 1, y + 1, ROW, COL);
			win += AutoFind(mine, show, x + 1, y + 1, ROW, COL);
		}
	}
	return win; // win非常重要,涉及到后面判断是否胜利
}

// 标记函数 — 将玩家选中的位置用'#'表示
// 注意这个函数修改的也是show数组
void MarkShow(char show[ROWS][COLS], int x, int y, int row, int col)
{
	if (x >= 1 && x <= row && y >= 1 && y <= col && show[x][y] == '*')
	{
		show[x][y] = '#';
	}
}


// 检查是否踩到雷、是否胜利
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0; 
	int y = 0;
	int win = 0;
	/*
		win代表着棋盘上除了雷以外的所有位置的个数,如果玩家每找到一个除雷以外的位置,那么win就 + 1,直到找到所有
		除雷以外的位置,那么玩家就胜利了
	*/
	while (win < ROW * COL - EASY_COUNT) 
	{
		int input = 0;
		printf("请选择你的操作:1.排雷 2.标记雷\n");
		scanf("%d", &input);

		switch (input)
		{
		case 1:
		{
			printf("请输入要排查的坐标:>");
			scanf("%d%d", &x, &y);

			if (x >= 1 && x <= row && y >= 1 && y <= col)
			{
				// 判断x,y坐标处是否是雷
				if (mine[x][y] == '1')
				{
					printf("很遗憾,你被炸死了\n");
					DisplayBoard(mine, row, col);
					exit(0);
					break;
				}
				else
				{
					// 如果x,y坐标不是雷,就统计周围有几个雷
					int count = GetMineCount(mine, x, y);
					if (0 == count)
					{
						win += AutoFind(mine, show, x, y, ROW, COL); 
						// win加上自动展开的位置的个数

						DisplayBoard(show, ROW, COL); // 给玩家展示一下
					}
					else
					{
						show[x][y] = count + '0';
						DisplayBoard(show, ROW, COL);
						win++;
					}

				}
			}
			else
			{
				printf("坐标非法\n");
			}

			if (win == ROW * COL - EASY_COUNT)
			{
				printf("恭喜你,排雷成功\n");
				DisplayBoard(mine, ROW, COL);
			}
		}
		break;
		case 2:
		{
			int p = 0;
			int q = 0;
			printf("请输入要标记的坐标:>");
			scanf("%d%d", &p, &q);
			MarkShow(show, p, q, ROW, COL);
			DisplayBoard(show, ROW, COL);
		}
		break;

		default:
			printf("输入有误,请重新选择:>");
		}
	}
}

saolei.c

// saolei.c

#include "game.h"

void menu()
{
	printf("********************************\n");
	printf("********    1.paly   ***********\n");
	printf("********    0.exit   ***********\n");
	printf("********************************\n");
}

void game()
{
	// 真正的扫雷过程
	// 创建两个数组
	
	// 存放布置好的雷
	char mine[ROWS][COLS] = {0}; // '0'
	// 存放排查出来的雷
	char show[ROWS][COLS] = {0}; // '*'

	// 初始化数组
	InitBoard(mine, ROWS, COLS, '0');
	InitBoard(show, ROWS, COLS, '*');
	
	DisplayBoard(show, ROW, COL);

	// 1.布置雷
	SetMine(mine, ROW, COL);
	//DisplayBoard(mine, ROW, COL); // 布置好的雷的信息

	// 2.扫雷
	FindMine(mine, show, ROW, COL);
}

int main()
{
	int input = 0;

	srand((unsigned int)time(NULL)); // 设置随机数起点, 时间戳time(NULL)返回值是time_t整型

	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			exit(0);
			break;
		default:
			printf("输入错误!\n");
			break;
		}
	} while (input);

	return 0;
}

运行效果

为了方便测试,我将埋雷的个数设置成了1
初始界面
在这里插入图片描述
初始棋盘
在这里插入图片描述
开始排雷
在这里插入图片描述
因为只有1个雷,所以周围位置雷个数为0的位置全部自动展开了

排雷成功
在这里插入图片描述
标记雷
在这里插入图片描述
将(5,5)这个位置标记为雷,显示'#'.

总结

总体来说上述代码实现了扫雷游戏的主要功能,后续考虑可以做个简单的UI界面,将输入坐标的方式改掉,一个个位置输坐标太麻烦了,假期再说吧。上面的代码我简单测试过很多次,应该没有大的问题,如果有的话,麻烦看到的人给我反馈一下,感激不尽!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值