C语言实现扫雷游戏

2024-10-09 14:49:10 浏览数 (1)

引言

在这个数字化的时代,游戏已经成为我们生活中不可或缺的一部分。无论是复杂的3D大作,还是简单的桌面小游戏,它们都能带给我们无尽的乐趣和挑战。今天,我们要一起回到那个经典的桌面游戏时代,探索如何用C语言编写一个充满怀旧气息的扫雷小游戏。

一、游戏规则

游戏目标: 盘面上随机分布着一定数量的地雷。你的目标是避开这些地雷,打开其他所有格子。 地雷数量: 选择相应的难度后,电脑会在雷区随机布置一定数量的地雷。 排雷:输入需要排查的坐标。如果点击的是地雷,则游戏失败;如果点击的是非雷方格,会显示周围八个方格内地雷的数。 标记:在怀疑的方格上放置旗帜(本游戏中用$符号代替),标记为地雷。 游戏结束: 当所有非雷方格都被揭开,且所有地雷都被正确标记时,游戏胜利。 如果揭开了一个地雷,游戏失败。

二、设计思路

1. 游戏概述

首先,明确游戏的基本框架和玩法。扫雷游戏主要包括一个雷区、地雷的随机分布、玩家的点击操作以及游戏胜负的判定。

2. 数据结构设计

雷区表示:使用一个二维数组来表示雷区,每个元素对应一个方格。棋盘可操作的区域是9*9的二维字符数组,实际的棋盘要多出两行两列(防止越界,简化设计操作)。 棋盘有两个,一个用来埋雷,一个用来显示排查雷的情况 埋雷数组(逻辑层):这个数组存储了游戏的真实状态,即哪些位置埋有地雷,哪些位置是安全的。这个数组对玩家是不可见的,它用于游戏的内部逻辑处理。其中用‘1’表示有雷,‘0’表示无雷。 展示数组(视图层):这个数组用于向玩家展示游戏当前的状态。它包含了玩家已经点击的方格、标记的地雷以及显示的数字提示。其中‘*’表示待排查的地理,可进行标记操作和排雷操作,‘$’表示已标记的地理,盘面上的数字表示该位置周围一圈格子雷的数量。

3. 游戏流程设计

难度选择:开始游戏前可以选择难度,简单,一般,困难三个级别难度 初始化:初始化两个二维数组 生成雷区:随机布置地雷 显示盘面:打印展示数组供玩家操作 玩家操作:玩家可以选择排雷,标记或是删除标记 逻辑判断:根据玩家的点击,更新显示数组,并进行游戏胜负的判断。 游戏结束:当玩家触发地雷或成功标记所有地雷时,游戏结束。

4. 功能模块划分

难度模块:供玩家选择对应难度。 初始化模块:负责初始化雷区和显示数组。 布雷模块:随机在雷区布置地雷。 显示模块:根据玩家的操作更新显示数组,并打印当前雷区的状态。 标记模块:玩家可以在怀疑的地方做说标记。 胜负判定模块:判断游戏是否结束,并给出相应的提示。

5. 主要算法设计

布雷算法:使用随机数生成器来确定地雷的位置。 计算周围地雷数量:对于每个非雷方格,计算其周围八个方格内地雷的数量。 递归扫雷:当一个格子显示‘0’即周围没有雷时,进行递归扫雷,展开一片区域

三、游戏设计

1.菜单函数

首先,我们需要制作一个简易的游戏菜单,代码如下:

代码语言:javascript复制
void Menu()
{
	printf("****************************n");
	printf("*******    1.play    *******n");
	printf("*******    0.exit    *******n");
	printf("****************************n");
	//玩家按1开始游戏,按0则结束游戏
}
2.主函数

主函数实现代码框架,用来控制按1开始游戏/按0退出游戏,并且多次进行直到玩家退出。

这里我们用switch来实现玩家的选择,用do...while循环语句保证游戏的多次进行。代码如下:

其中的srand函数功能以及解释请看上一篇博客中的介绍。代码如下:

代码语言:javascript复制
int main()
{
	srand((unsigned int)time(NULL));//随机种子
	int option;
	do
	{
		system("cls");//用于清除缓冲区,后一次玩的时候清除前面记录
		Menu();
		printf("请做出你的选择:");
		scanf("%d", &option);
		switch (option)
		{
		case 1:
			system("cls");//清除缓冲区
			game();
			break;
		case 0:
			printf("游戏结束n");
			break;
		default:
			printf("输入有误,请重新输入n");
			system("pause");//用来暂停程序,按下后继续运行,即运行下面的清除缓冲区
			break;
		}
	} while (option);
}

现在完成了框架,接下来是各个模块代码的实现

3.选择难度函数

返回值为地理的数量,简单模式8个地雷,正常模式12个地雷,困难模式16个地雷。代码如下:

代码语言:javascript复制
int SelectDiff() {//难度选择
	int difficulty;
		printf("请选择难度:n");
		printf("1. 简单         8个雷n");
		printf("2. 正常(默认)   12个雷n");
		printf("3. 困难         16个雷n");
		scanf("%d", &difficulty);
		switch (difficulty) {
		case 1:
			return MINE - 4;//简单模式8个雷
			break;
		case 2:
			return MINE;//正常模式12个雷
			break;
		case 3:
			return MINE   4;//困难模式16个雷
			break;
		case 0:
			break;
		default:
			printf("输入有误,请重新选择n");
			break;
		}
	return MINE;
}
4.初始化函数

该函数用来初始化mine数组和show数组。代码如下:

代码语言:javascript复制
void InitBoard(char arr[ROWS][COLS], char set)//初始化棋盘
{
	int i, j;
	for (i = 0; i < ROWS; i  )
	{
		for (j = 0; j < ROWS; j  )
		{
			arr[i][j] = set;//初始化棋盘,mine数组中全部初始化为'0',show数组中全部初始化为'*'
		}
	}
}
5.布置地雷函数

利用生成的随机数,在棋盘上随机位置布置地雷。代码如下:

代码语言:javascript复制
void SetMine(char arr[ROWS][COLS], int count)//布置地雷
{
	while (count)
	{
		int x = rand() % ROW   1;//产生1~9的随机数
		int y = rand() % COL   1;//产生1~9的随机数

		if (arr[x][y] == '0')
		{
			arr[x][y] = '1';
			count--;
		}
	}
}
6.打印函数

用来打印show数组供玩家在盘面上进行操作以及游戏结束时打印mine数组供玩家查看雷区位。代码如下:

代码语言:javascript复制
void PrintBoard(char arr[ROWS][COLS])//打印棋盘
{
	int i, j;
	printf("n=====Minesweeper=====n");
	for (j = 0; j <= ROW; j  )
	{
		printf("-", j);//打印列标
	}
	printf("n");
	for (i = 1; i <= ROW; i  )
	{
		printf("-", i);//打印行标
		for (j = 1; j <= COL; j  )
		{
			printf(",", arr[i][j]);
		}
		printf("n");
	}
	printf("=====================n");
}
7.计算雷数函数

玩家在该位置排雷后,若该位置没有雷,则计算周围地雷个数,展示在show数组的该位置上。代码如下:

代码语言:javascript复制
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]
		- 8 * '0');
}
8.递归排雷函数

当排查的地方周围无雷时,进行递归,自动深度排查周围区域。代码如下:

代码语言:javascript复制
void DeepSweep(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)//递归连续排雷
{
	if (x == 0 || y == 0 || x == ROW   1 || y == ROW   1)
		return;
	if (show[x][y] != '*' && show[x][y] != '$')
		return;
	int count = GetMineCount(mine, x, y);
	if (count == 0)
	{
		show[x][y] = '0';
		DeepSweep(mine, show, x - 1, y - 1);
		DeepSweep(mine, show, x - 1, y);
		DeepSweep(mine, show, x - 1, y   1);
		DeepSweep(mine, show, x, y - 1);
		DeepSweep(mine, show, x, y   1);
		DeepSweep(mine, show, x   1, y - 1);
		DeepSweep(mine, show, x   1, y);
		DeepSweep(mine, show, x   1, y   1);
	}
	else
	{
		show[x][y] = count   '0';
	}
}
9.标记(删除标记)函数

供玩家在怀疑的地方标记为地雷,或者删除某一位置的标记,其中标记符号为$。代码如下:

代码语言:javascript复制
void MarkMine(char show[ROWS][COLS], int x, int y) {
	if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
		if (show[x][y] == '*')
		{
			show[x][y] = '$';
		}
	}
}

void UnmarkMine(char show[ROWS][COLS], int x, int y) {
	if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
		if (show[x][y] == '$')
		{
			show[x][y] = '*';
		}
	}
}
10.操作函数

游戏中可供玩家进行排雷、标记、删除标记的选择,在玩家操作后更新展示show数组,并且根据玩家的一系列操作判断来玩家是胜利还是失败。代码如下:

代码语言:javascript复制
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS],int count)
{
	int x, y;
	int win = 0;
	int flag = count;
	while (win < ROW * COL - MINE) {//排查的安全地方个数
		// 显示菜单
		printf("1. 排雷n2. 标记雷(当前还可标记%d处)n3. 删除标记n0. 退出n请选择操作:",flag); 
		int choice;
		scanf("%d", &choice);

		switch (choice) {
		case 1: // 排雷
			printf("输入你要排查的位置(输入坐标:行 列):");
			scanf("%d %d", &x, &y);
			if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
			{
				if (show[x][y] == '*' || show[x][y] == '$')
				{
					if (mine[x][y] == '1')
					{
						printf("很遗憾,你踩到雷了!n");
						PrintBoard(mine);
						system("pause");//用来暂停程序,按下后继续运行
						break;
					}
					else
					{
						DeepSweep(mine, show, x, y);//递归排雷
						int count = GetMineCount(mine, x, y);
						show[x][y] = count   '0';
						system("cls");//清除缓冲区
						PrintBoard(show);
						win  ;
					}

				}
				else
				{
					printf("该位置已被排除过,请重新输入!n");
				}
			}
			break;
		case 2: // 标记雷
			if(flag>0)
			{
				printf("输入你要标记的位置(输入坐标:行 列):");
				scanf("%d %d", &x, &y);
				if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
					if (show[x][y] == '*')
					{
						system("cls");//清除缓冲区
						MarkMine(show, x, y);
						PrintBoard(show);
						flag--;
					}
					else
					{
						printf("该位置已被排查,无法标记!n");
					}
				}
				else {
					printf("输入位置不合法,请重新输入n");
				}
			}
			else
			{
				printf("标记达上限,无法再标记!");
			}
			break;
		case 3: // 删除标记
			printf("输入你要删除标记的位置(输入坐标:行 列):");
			scanf("%d %d", &x, &y);
			if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
				if (show[x][y] == '$')
				{
					system("cls");//清除缓冲区
					UnmarkMine(show, x, y);
					PrintBoard(show);
				}
				else {
					printf("该位置未被标记,无法删除!n");
				}

			}
			else {
				printf("输入位置不合法,请重新输入n");
			}
			break;
		case 0: // 退出
			return;
		default:
			printf("输入有误,请重新输入n");
			break;
		}
	}

	if (win == ROW * COL - MINE) {//排查完所有非雷区游戏胜利
		printf("我嘞个雷!n");
		printf("恭喜你已经排完了所有的雷!n");
		PrintBoard(mine);
	}
}
11.游戏函数

即整合实现游戏运行的分模块。代码如下:

代码语言:javascript复制
void game()
{
	int minecount = SelectDiff();//难度选择
	char mine[ROWS][COLS] = { 0 };//mine数组全部初始化为'0'
	char show[ROWS][COLS] = { 0 };//show数组中全部初始化为'*'
	InitBoard(mine, '0');
	InitBoard(show, '*');
	SetMine(mine, minecount);
	//PrintBoard(mine);雷区,取消注释可以作弊查看
	PrintBoard(show);
	FindMine(mine, show,minecount);
}

四、完整代码及运行效果图

完整源代码:
代码语言:javascript复制
#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 9//9*9的棋盘
#define COL 9
#define ROWS ROW 2
#define COLS COL 2
#define MINE 12//正常难度雷的数量
void Menu()
{
	printf("****************************n");
	printf("*******    1.play    *******n");
	printf("*******    0.exit    *******n");
	printf("****************************n");
	//玩家按1开始游戏,按0则结束游戏
}
int SelectDiff() {//难度选择
	int difficulty;
		printf("请选择难度:n");
		printf("1. 简单         8个雷n");
		printf("2. 正常(默认)   12个雷n");
		printf("3. 困难         16个雷n");
		scanf("%d", &difficulty);
		switch (difficulty) {
		case 1:
			return MINE - 4;//简单模式8个雷
			break;
		case 2:
			return MINE;//正常模式12个雷
			break;
		case 3:
			return MINE   4;//困难模式16个雷
			break;
		case 0:
			break;
		default:
			printf("输入有误,请重新选择n");
			break;
		}
	return MINE;
}
void InitBoard(char arr[ROWS][COLS], char set)//初始化棋盘
{
	int i, j;
	for (i = 0; i < ROWS; i  )
	{
		for (j = 0; j < ROWS; j  )
		{
			arr[i][j] = set;//初始化棋盘,mine数组中全部初始化为'0',show数组中全部初始化为'*'
		}
	}
}
void PrintBoard(char arr[ROWS][COLS])//打印棋盘
{
	int i, j;
	printf("n=====Minesweeper=====n");
	for (j = 0; j <= ROW; j  )
	{
		printf("-", j);//打印列标
	}
	printf("n");
	for (i = 1; i <= ROW; i  )
	{
		printf("-", i);//打印行标
		for (j = 1; j <= COL; j  )
		{
			printf(",", arr[i][j]);
		}
		printf("n");
	}
	printf("=====================n");
}
void SetMine(char arr[ROWS][COLS], int count)//布置地雷
{
	while (count)
	{
		int x = rand() % ROW   1;//产生1~9的随机数
		int y = rand() % COL   1;//产生1~9的随机数

		if (arr[x][y] == '0')
		{
			arr[x][y] = '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]
		- 8 * '0');
}
void DeepSweep(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)//递归连续排雷
{
	if (x == 0 || y == 0 || x == ROW   1 || y == ROW   1)
		return;
	if (show[x][y] != '*' && show[x][y] != '$')
		return;
	int count = GetMineCount(mine, x, y);
	if (count == 0)
	{
		show[x][y] = '0';
		DeepSweep(mine, show, x - 1, y - 1);
		DeepSweep(mine, show, x - 1, y);
		DeepSweep(mine, show, x - 1, y   1);
		DeepSweep(mine, show, x, y - 1);
		DeepSweep(mine, show, x, y   1);
		DeepSweep(mine, show, x   1, y - 1);
		DeepSweep(mine, show, x   1, y);
		DeepSweep(mine, show, x   1, y   1);
	}
	else
	{
		show[x][y] = count   '0';
	}
}
void MarkMine(char show[ROWS][COLS], int x, int y) {
	if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
		if (show[x][y] == '*')
		{
			show[x][y] = '$';
		}
	}
}

void UnmarkMine(char show[ROWS][COLS], int x, int y) {
	if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
		if (show[x][y] == '$')
		{
			show[x][y] = '*';
		}
	}
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS],int count)
{
	int x, y;
	int win = 0;
	int flag = count;
	while (win < ROW * COL - MINE) {//排查的安全地方个数
		// 显示菜单
		printf("1. 排雷n2. 标记雷(当前还可标记%d处)n3. 删除标记n0. 退出n请选择操作:",flag); 
		int choice;
		scanf("%d", &choice);

		switch (choice) {
		case 1: // 排雷
			printf("输入你要排查的位置(输入坐标:行 列):");
			scanf("%d %d", &x, &y);
			if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
			{
				if (show[x][y] == '*' || show[x][y] == '$')
				{
					if (mine[x][y] == '1')
					{
						printf("很遗憾,你踩到雷了!n");
						PrintBoard(mine);
						system("pause");//用来暂停程序,按下后继续运行
						break;
					}
					else
					{
						DeepSweep(mine, show, x, y);//递归排雷
						int count = GetMineCount(mine, x, y);
						show[x][y] = count   '0';
						system("cls");//清除缓冲区
						PrintBoard(show);
						win  ;
					}

				}
				else
				{
					printf("该位置已被排除过,请重新输入!n");
				}
			}
			break;
		case 2: // 标记雷
			if(flag>0)
			{
				printf("输入你要标记的位置(输入坐标:行 列):");
				scanf("%d %d", &x, &y);
				if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
					if (show[x][y] == '*')
					{
						system("cls");//清除缓冲区
						MarkMine(show, x, y);
						PrintBoard(show);
						flag--;
					}
					else
					{
						printf("该位置已被排查,无法标记!n");
					}
				}
				else {
					printf("输入位置不合法,请重新输入n");
				}
			}
			else
			{
				printf("标记达上限,无法再标记!");
			}
			break;
		case 3: // 删除标记
			printf("输入你要删除标记的位置(输入坐标:行 列):");
			scanf("%d %d", &x, &y);
			if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
				if (show[x][y] == '$')
				{
					system("cls");//清除缓冲区
					UnmarkMine(show, x, y);
					PrintBoard(show);
				}
				else {
					printf("该位置未被标记,无法删除!n");
				}

			}
			else {
				printf("输入位置不合法,请重新输入n");
			}
			break;
		case 0: // 退出
			return;
		default:
			printf("输入有误,请重新输入n");
			break;
		}
	}

	if (win == ROW * COL - MINE) {//排查完所有非雷区游戏胜利
		printf("我嘞个雷!n");
		printf("恭喜你已经排完了所有的雷!n");
		PrintBoard(mine);
	}
}

void game()
{
	int minecount = SelectDiff();//难度选择
	char mine[ROWS][COLS] = { 0 };//mine数组全部初始化为'0'
	char show[ROWS][COLS] = { 0 };//show数组中全部初始化为'*'
	InitBoard(mine, '0');
	InitBoard(show, '*');
	SetMine(mine, minecount);
	//PrintBoard(mine);雷区,取消注释可以作弊查看
	PrintBoard(show);
	FindMine(mine, show,minecount);
}
int main()
{
	srand((unsigned int)time(NULL));//随机种子
	int option;
	do
	{
		system("cls");//用于清除缓冲区,后一次玩的时候清除前面记录
		Menu();
		printf("请做出你的选择:");
		scanf("%d", &option);
		switch (option)
		{
		case 1:
			system("cls");//清除缓冲区
			game();
			break;
		case 0:
			printf("游戏结束n");
			break;
		default:
			printf("输入有误,请重新输入n");
			system("pause");//用来暂停程序,按下后继续运行,即运行下面的清除缓冲区
			break;
		}
	} while (option);
}
运行效果图:

总结

通过这次C语言扫雷小游戏的开发,我们不仅重温了一个经典的桌面游戏,而且在实践中加深了对C语言编程的理解。从设计思路到具体实现,每一步都是对逻辑思维和编程技能的锻炼。在这个过程中,我们学到了如何利用二维数组管理复杂的游戏状态,如何处理用户输入,以及如何在游戏中实现递归和条件判断等高级功能。这次实践不仅让我们体验了从零到一构建游戏的成就感,也为未来的编程学习奠定了坚实的基础。扫雷游戏虽小,但它背后的编程智慧无穷,让我们继续探索,创造更多有趣的作品。

0 人点赞