扫雷小游戏实现

在这里插入图片描述

欢迎来到Cefler的博客😁
🕌博客主页:那个传说中的man的主页
🏠个人专栏:题目解析
🌎推荐文章:题目大解析(更新ing)

在这里插入图片描述


🤖基本思路

一、菜单实现
我们在实现某个小游戏的时候,第一步其实都是要创建一个菜单栏,这个菜单栏的功能有进入游戏和退出游戏
而我们一般会单独创建一个menu函数作为菜单内容,do while语句执行菜单的命令
二、game函数实现
我们一旦在do while语句中选择了进入游戏,那么我们就要开始创建一个game函数来实现我们的游戏,这也是我们的主体,而接下里我们的代码大部分都在game函数中进行
三、扫雷游戏制作步骤
1.创建两个二维数组
我们知道扫雷棋盘是二维的,所以如果我们可以创建二维数组,为我们提供扫雷的区域,至于为什么要创建两个二维数组呢?
在平常的扫雷游戏中,我们是见到的棋盘一般叫“可视化”棋盘,然而,还有一个不可视化的棋盘,那就设置雷的棋盘,所以我们需要两个棋盘,一个用来设置雷(内幕棋盘),一个是提供玩家玩的棋盘。(这里我们设置的棋盘大小为9x9)
2.棋盘初始化

  • 可视化棋盘初始化(而后我们管它叫show棋盘)
    这个棋盘是给我们玩家看的,所以我们就给这个棋盘的元素全部初始化为*,作为未选中的图案(图案可以任选)
  • 设置雷棋盘初始化(而后我们管它叫mine棋盘))
    我们先规定,‘1’为雷,‘0’为无雷(后面有大用),这里我们先不布置雷,先把棋盘全部初始化无雷,即初始化为‘0’

3.布置雷
我们给棋盘布置雷,一定要保证我们每次开始一个新的游戏,雷的位置都是随机的,所以,我们这里在布置雷的时候,就可以用到rand函数对雷的二维坐标进行随机赋值(在主函数中记得用srand函数加上时间戳srand((unsigned int)time(NULL));),但为了所产生的随机值不超过棋盘大小,我们需要给它规定范围
4.展示棋盘
我们在初始化棋盘完之后,就可以展示棋盘了(注意这里只展示可视化棋盘),让玩家进行选择排雷
5.排雷环节
排雷环节是整个game函数里最复杂的一环
它的基本逻辑顺序为:
玩家下棋——>
第一步:
玩家排雷,当玩家每排一个坐标的雷,而后在show棋盘中都要更新该坐标的图案(不再是原来的‘*’)
第二步:
将玩家此次在show所排雷的坐标在mine棋盘所对应的坐标进行检验,检验是’1’还是’0’,如果是前者,恭喜踩雷,游戏结束。
如果是’0’,也有两种情况:排雷未完,游戏继续 / 排雷已完,游戏结束。
对于这一、二步我们可以将其包含在一个while循环中

  • 如果踩到雷,我们就break退出循环,即游戏结束
  • 在while的判断语句设置为:排雷数<81-雷数,意思即为,当除了雷以外的坐标都排尽之后,此时就是排雷已完,就是排雷成功了,则循环自动结束,游戏结束
  • 排雷未完,循环进行排雷

6.排雷环节细节优化
我们都知道,我们在扫雷的时候,每点击一个坐标,如果不是雷,这个坐标就会显示其周围8个格子的含雷数,如果无雷,会自动向外爆炸(扩展)直到碰到雷。而在扫雷中,也有个功能就是标记雷,
所以我们还要实现三个函数功能

  • GetMineNum:显示周围雷数
  • boom:向外扩张
  • MarkMine:标记雷

介绍完这些,让我们在具体的代码中看看效果吧😊

📜菜单实现

void menu()
{
	printf("           欢迎来到扫雷小游戏\n");
	printf("              按 1 进入游戏\n");
	printf("              按 0 退出游戏\n");
}

有了基本菜单内容之后,我们需要用do while语句执行菜单命令

int main()
{
	srand((unsigned int)time(NULL));
	int input = 0;
	do
	{
		menu();
		printf("请输入:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:game();
			break;
		case 0:printf("退出游戏\n");
			break;
		default:printf("%\a\n");
			break;
		}

	} while (input);
	return 0;
}

😺game函数实现

创建show棋盘和mine棋盘

为了方便我们以后如果要修改棋盘大小,所以我们将棋盘的行和列(ROWS和COLS)设置为全局变量

#define ROW 9
#define COL 9
#define ROWS ROW+2//加2是因为有二维数组的行列的下标从0开始,我们0行0列放坐标系,从1行1列才是正式的棋盘
#define COLS COL+2
void game()
{
	char mine[ROWS][COLS] = { 0 };
	char show[ROWS][COLS] = { 0 };
}

初始化棋盘

	//初始化棋盘,mine中初始化为'0',先全部都是无雷,show中初始化为*,这是展示出来的棋盘
	InitialBoard(mine, ROWS, COLS, '0');
	InitialBoard(show, ROWS, COLS, '*');

函数内部具体实现👇🏻

void InitialBoard(char board[ROWS][COLS],int row,int col, char set)
{
	int i = 0,j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			board[i][j] = set;//set的好处就在于这里,这里既可以传给mine'0',又可以传给show'*'
		}
	}
}

布置雷

	//布置雷
	SetMine(mine, ROW, COL);

函数内部具体实现👇🏻

void SetMine(char mine[ROWS][COLS], int row, int col)
{
	int count = EasyMode;
	while (count)//用count做判断条件是因为count为雷数,若count=0时,则判断为假,循环结束,正好排雷结束
	{
		int x = rand() % row + 1;//设置好设置雷行坐标的范围为1~9
		int y = rand() % col + 1;//设置好设置雷列坐标的范围为1~9
		if (mine[x][y] != '1')
		{
			mine[x][y] = '1';
			count--;
		}

	}
}

展示棋盘

DisplayBoard(show, ROW, COL);

函数内部具体实现👇🏻

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i, j;
	//打印坐标
	for (i = 0; i <= col; i++)
	{
		printf("%d ", i);
	}
	printf("\n");//记得换行
	for (i = 0; i <= col; i++)
	{
		printf("---");
	}
	printf("\n");
	for (i = 1; i <= row; i++)
	{
		printf("%d|", i);
		for (j = 1; j <= col; j++)
		{
			
			printf("%c ", board[i][j]);
		}
		printf("\n");//记得换行
	}
	
}

排雷函数

FindMine(mine, show, ROW, COL);

函数内部具体实现👇🏻

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x, y;
	int win = 0;
	while (win<(row*col-EasyMode))
	{
		//开始前有个标记函数
		MarkMine(show,row,col);
		//内幕参考答案
		printf("参考答案\n");
		DisplayBoard(mine, ROW, COL);
		printf("请输入你的坐标:");
		scanf("%d %d", & x, & y);
		system("cls");
		/*DisplayBoard(show, ROW, COL);*/
		if (x >= 1 && x <= 9 && y >= 1 && x <= 9)//规定输入坐标范围
		{
			if (show[x][y] == '*')//所输入坐标必须还未打开过
			{
				
				if (mine[x][y] == '1')
				{
					printf("很遗憾,你被炸死了!\n");
					printf("蟹不肉不要伤心,再来一把试试?\n");
					DisplayBoard(mine, ROW, COL);//看看死在了哪里
					break;
				}
				/*mine[x][y] = '^';*/
				//这里若没有被炸死,则要显示周围雷数
				int count = GetMineNum(mine, x, y);//创建一个函数查看雷数量
				char num = count + '0';//因为'3'-'0'==51-48==3,所以整型+'0'可以得到字符的数字
				show[x][y] = num;//在show的棋盘中显示数字
				//boom
				win++;
				if (show[x][y] == '0')
			   {
					boom(mine, show, x, y,&win);
				}
			
				DisplayBoard(show, ROW, COL);//展示一下数字
			}
			else
			{
				printf("该坐标已输入过或已展开\a\n");
				DisplayBoard(show, ROW, COL);
			}
	
		}
		else
		{
			printf("坐标越界\a\n");
		}
	}
	if (win ==(row * col - EasyMode))
	{
		printf("排雷成功!\n");
		printf("蟹不肉,不愧是你!\n");

		DisplayBoard(show, ROW, COL);
	}
}

MarkMine函数实现

void MarkMine(char show[ROWS][COLS], int row, int col)
{
	int a = 0;
		int x, y;
		while (1)
		{
			printf("按1标记雷,任意值不标记:");
			scanf("%d", &a);
			if (a == 1)
			{  //这时说明要标记雷了
				printf("请输出要标记的坐标:");
				scanf("%d %d", &x, &y);
				//这时要判断坐标是否合法if (x >= 1 && x <= 9 && y >= 1 && x <= 9)
				if (x >= 1 && x <= 9 && y >= 1 && x <= 9)
				{
					//这时要判断什么地方能标记,是*的地方可以标记
					if (show[x][y] == '*')
					{
						show[x][y] = '!';
						//标记完之后,要打印出来标记后的棋盘
						DisplayBoard(show, ROW, COL);
						break;
					}
					else
						printf("该处已不能被标记\a\n");
				}
				else
					printf("坐标不合法\a\n");

			}
			//若不标记,则退出
			else
				break;
	}
}

GetMineNum函数实现

int GetMineNum(char mine[ROWS][COLS], int x, int y)
{
	int i, j;
	char count = 0;
	for (i = -1; i <= 1; i++)
	{
		for (j = -1; j <= 1; j++)
		{
			count += mine[x + i][y + j];
		}
	}
	return (count - 9 * '0');
}

我们知道’3’-‘0’=3,所以当正数+‘0’会等于其对应的ASCII码值

int count = GetMineNum(mine, x, y);//创建一个函数查看雷数量
				char num = count + '0';//因为'3'-'0'==51-48==3,所以整型+'0'可以得到字符的数字
				show[x][y] = num;//在show的棋盘中显示数字

boom函数实现

void boom(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y,int* win)
{
	//x,y为传进来的坐标
	int i = 0, j = 0;
	//现在要开始排查周围的坐标
	for (i = x - 1; i <= x + 1; i++)
	{
		for (j = y - 1; j <= y + 1; j++)
		{
			if (i >= 1 && i <= ROW && j >= 1 && j <= COL && show[i][j] == '*')//若坐标合法,且此时该位置还未爆炸过
			{
				char count = GetMineNum(mine, i, j) + '0';
				show[i][j] = count;//此时爆炸出来并显示周围有几个雷
				(*win)++;//这是已经排完雷的位置个数
				if (show[i][j] == '0')//如果都没有雷的话,继续向外面扩散
				{
					boom(mine, show, i, j,win);
				}
			}
		}
	}
}

boom函数实现思路:
检查所点开格子周围8个格子中是否有雷,若有雷,则只显示雷数,不扩张。
若无雷,分别以周围8个格子为中心继续向外扩张,继续检查周围8个格子是否有雷,这里我们可以利用递归函数实现。
但是在检查周围格子的时候,如何防止出现”复查“的情况呢?
我们知道,在我们检查完周围格子之后,如果满足无雷的条件,它就会”爆炸“开了,此时在show棋盘中也就不再显示’*‘图案了。
所以我们就规定,如果该坐标的图案为’ * '时,即还未检查过,这时就会避免复查的情况。

if (i >= 1 && i <= ROW && j >= 1 && j <= COL && show[i][j] == '*')//若坐标合法,且此时该位置还未爆炸过

😽 原码展示

主函数game_main.c

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include "game 2.h"
void menu()
{
	printf("           欢迎来到扫雷小游戏\n");
	printf("              按 1 进入游戏\n");
	printf("              按 0 退出游戏\n");
}
void game()
{
	char mine[ROWS][COLS] = { 0 };
	char show[ROWS][COLS] = { 0 };
	//初始化棋盘,mine中初始化为'0',先全部都是无雷,show中初始化为*,这是展示出来的棋盘
	InitialBoard(mine, ROWS, COLS, '0');
	InitialBoard(show, ROWS, COLS, '*');
	
	//打印棋盘
	DisplayBoard(show, ROW, COL);
	//布置雷
	SetMine(mine, ROW, COL);
	//内幕
	DisplayBoard(mine, ROW, COL);
	//开始排查和雷
	FindMine(mine, show, ROW, COL);


}
int main()
{
	srand((unsigned int)time(NULL));
	int input = 0;
	do
	{
		menu();
		printf("请输入:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:game();
			break;
		case 0:printf("退出游戏\n");
			break;
		default:printf("%\a\n");
			break;
		}

	} while (input);
	return 0;
}

头文件game.h

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <windows.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define EasyMode 10
void InitialBoard(char board[ROWS][COLS],int row,int col, char set);
void DisplayBoard(char boar[ROWS][COLS], int row, int col);
void SetMine(char mine[ROWS][COLS], int row, int col);
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS],  int row, int col);

game函数内的具体函数功能文件game.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "game 2.h"
void InitialBoard(char board[ROWS][COLS],int row,int col, char set)
{
	int i = 0,j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			board[i][j] = set;//set的好处就在于这里,这里既可以传给mine'0',又可以传给show'*'
		}
	}
}
//打印棋盘

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i, j;
	//打印坐标
	for (i = 0; i <= col; i++)
	{
		printf("%d ", i);
	}
	printf("\n");//记得换行
	for (i = 0; i <= col; i++)
	{
		printf("---");
	}
	printf("\n");
	for (i = 1; i <= row; i++)
	{
		printf("%d|", i);
		for (j = 1; j <= col; j++)
		{
			
			printf("%c ", board[i][j]);
		}
		printf("\n");//记得换行
	}
	
}
//设置雷
void SetMine(char mine[ROWS][COLS], int row, int col)
{
	int count = EasyMode;
	while (count)//用count做判断条件是因为count为雷数,若count=0时,则判断为假,循环结束,正好排雷结束
	{
		int x = rand() % row + 1;//设置好设置雷的范围
		int y = rand() % col + 1;
		if (mine[x][y] != '1')
		{
			mine[x][y] = '1';
			count--;
		}

	}
}
//查看周围雷数量的函数
int GetMineNum(char mine[ROWS][COLS], int x, int y)
{
	int i, j;
	char count = 0;
	for (i = -1; i <= 1; i++)
	{
		for (j = -1; j <= 1; j++)
		{
			count += mine[x + i][y + j];
		}
	}
	return (count - 9 * '0');
}
//MarkMine
void MarkMine(char show[ROWS][COLS], int row, int col)
{
	int a = 0;
		int x, y;
		while (1)
		{
			printf("按1标记雷,任意值不标记:");
			scanf("%d", &a);
			if (a == 1)
			{  //这时说明要标记雷了
				printf("请输出要标记的坐标:");
				scanf("%d %d", &x, &y);
				//这时要判断坐标是否合法if (x >= 1 && x <= 9 && y >= 1 && x <= 9)
				if (x >= 1 && x <= 9 && y >= 1 && x <= 9)
				{
					//这时要判断什么地方能标记,是*的地方可以标记
					if (show[x][y] == '*')
					{
						show[x][y] = '!';
						//标记完之后,要打印出来标记后的棋盘
						DisplayBoard(show, ROW, COL);
						break;
					}
					else
						printf("该处已不能被标记\a\n");
				}
				else
					printf("坐标不合法\a\n");

			}
			//若不标记,则退出
			else
				break;
	}
}
void boom(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y,int* win)
{
	//x,y为传进来的坐标
	int i = 0, j = 0;
	//现在要开始排查周围的坐标
	for (i = x - 1; i <= x + 1; i++)
	{
		for (j = y - 1; j <= y + 1; j++)
		{
			if (i >= 1 && i <= ROW && j >= 1 && j <= COL && show[i][j] == '*')//若坐标合法,且此时该位置还未爆炸过
			{
				/*check_already[i][j] = '1';*/
				char count = GetMineNum(mine, i, j) + '0';
				show[i][j] = count;//此时爆炸出来并显示周围有几个雷
				(*win)++;//这是已经排完雷的位置个数
				if (show[i][j] == '0')//如果都没有雷的话,继续向外面扩散
				{
					boom(mine, show, i, j,win);
				}
			}
		}
	}
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x, y;
	int win = 0;
	while (win<(row*col-EasyMode))
	{
		
		//开始前有个标记函数
		MarkMine(show,row,col);
		//内幕参考答案
		printf("参考答案\n");
		DisplayBoard(mine, ROW, COL);
		printf("请输入你的坐标:");
		scanf("%d %d", & x, & y);
		system("cls");
		/*DisplayBoard(show, ROW, COL);*/
		if (x >= 1 && x <= 9 && y >= 1 && x <= 9)//规定输入坐标范围
		{
			if (show[x][y] == '*')//所输入坐标必须还未打开过
			{
				
				if (mine[x][y] == '1')
				{
					printf("很遗憾,你被炸死了!\n");
					printf("蟹不肉不要伤心,再来一把试试?\n");
					DisplayBoard(mine, ROW, COL);//看看死在了哪里
					break;
				}
				/*mine[x][y] = '^';*/
				//这里若没有被炸死,则要显示周围雷数
				int count = GetMineNum(mine, x, y);//创建一个函数查看雷数量
				char num = count + '0';//因为'3'-'0'==51-48==3,所以整型+'0'可以得到字符的数字
				show[x][y] = num;//在show的棋盘中显示数字
				//boom
				win++;
				if (show[x][y] == '0')
			   {
					boom(mine, show, x, y,&win);
				}
			
				DisplayBoard(show, ROW, COL);//展示一下数字

			}
			else
			{
				printf("该坐标已输入过或已展开\a\n");
				DisplayBoard(show, ROW, COL);
			}
	
		}
		else
		{
			printf("坐标越界\a\n");
		}
	
	
	}
	if (win ==(row * col - EasyMode))
	{
		printf("排雷成功!\n");
		printf("蟹不肉,不愧是你!\n");

		DisplayBoard(show, ROW, COL);
	}
}

如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注🌹🌹🌹❤️ 🧡 💛,学海无涯苦作舟,愿与君一起共勉成长
在这里插入图片描述
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值