扫雷【游戏拓展】【超详细】

在这里插入图片描述

前言
通过对代码的学习,一步步了解经典扫雷游戏是如何编程语言实现的,同时增加对C语言的认识和熟练掌握


一、初识扫雷

这款游戏的玩法是在一个9x9(初级),16x16(中级),16x30(高级),或自定义大小的方块矩阵中随机布置一定量的地雷(初级为10个,中级为40个,高级为99个)。由玩家逐个翻开方块,方块内的数字代表着该方块八个方位的方块(即九宫格)藏有几个雷,通过数字推断藏雷的方块,以找出所有地雷为最终游戏目标。如果玩家翻开的方块有地雷,则游戏结束。
在这里插入图片描述

二、整体框架

2.1游戏流程

首先,我们想象一下,我们进入游戏会有菜单栏,控制游戏开始和结束。然后我们开始游戏,游戏棋盘在我们面前,我们输入坐标,找出所有的雷。若踩到雷,输掉游戏,找到所有的雷,赢得游戏。接着程序会再次显示菜单栏,提醒我们继续游戏还是,退出游戏。

2.2游戏开始和结束

我们l来创建一个test.c文件,这是我们游戏空的框架,game()函数将实现我们游戏逻辑。

#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
#include<stdio.h>
#include<stdlib.h>

//菜单栏
void menu()
{
	printf("|-----扫雷游戏------|\n");
	printf("|------1.play------|\n");
	printf("|------0.exit------|\n");
}

int main()
{
	//生成随机数
	srand((unsigned int)time(NULL));
	int input = 0;   
	//进入游戏
	do
	{
		menu();
		printf("请输入:>");
		scanf("%d", &input);

		//清空缓存区
		while (getchar() != '\n');
		switch (input)
		{
		case 1:
		//游戏运行逻辑
			game();
			break;
		case 0:
			printf("结束游戏\n");
			break;
		default:
			printf("请重新输入0或1\n");
			break;
		}
	} while (input);
}

三、game函数

2.1初始化棋盘

在打造棋盘前我们,需要确认它的大小。为了后期方便更改大小,我们将行和列(ROW和COL)预处理,在接下来的代码中只要出现ROW和COL,那它们就是10和10。

在判断周围有多少个雷时,防止数组越界,所以预处理ROWS和COLS,这是我们数组大小,而实际上ROW和COL是我们的棋盘大小

#define ROW 10
#define COL 10
#define ROWS ROW + 2
#define COLS COL + 2

红圈我们需要判断应该放数字几,但当我们在排查时需要排查到棋盘外面一圈了,因此ROWS=ROW+2 COLS=COL+2

在这里插入图片描述
在这里插入图片描述

我们在game函数内部定义两个数组,试想一下,棋盘有行还有列,所以应该是二维数组。其次,为什么我们要创建两个数组,当我们输入一个坐标时,屏幕上会在该位置显示数字,告诉我们周围有几个雷。雷的个数我们需要在mine数组中确认,在mine中‘1’表示雷,‘0’表示无雷,为了和玩家屏幕上的数字区分开,因此创建两个数组,mine存放雷,由我们控制,而show给玩家使用。

void game()
{
    char mine[ROWS][COLS] = { 0 };//雷的存放,‘0’表示无雷,‘1’表示雷
	char show[ROWS][COLS] = { 0 };//玩家操作的棋盘
	InitBoard(mine, ROWS, COLS, '0');//初始化棋盘
	InitBoard(show, ROWS, COLS, '*');//初始化玩家棋盘
	SetMine(mine, ROW, COL);//开始布置雷
	system("cls");//清除屏幕
	DisplayBoard(show, ROW, COL);//打印起始的玩家棋盘 
	FineMine(mine, show, ROW, COL);//排查雷
}

接着我们要初始化棋盘,创建函数(为了方便查阅代码,我们重新创建一个game.c文件来存放函数)。

这里我们将实参字符‘0’和‘*’传给了形参set,为的是不用分别给mine和show分别创建函数

void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
	int i, j;
	for (i = 0; i < rows; i++)
	{ 
		for (j = 0; j < cols; j++)
		{   
			board[i][j] = set;
		}
	}

}

2.2放置雷

我们可以设置雷的个数,对雷的数量预处理

#define COUNT 20

放置雷在mine数组中
在main函数里我们使用了srand((unsigned int)time(NULL))函数,它是随机数发生器的初始化函数,需要提供一个种子,这里我们使用time(NULL),并强制转化为无符号整型。在自定义SetMine函数,随机生成x和y(即横坐标和纵坐标)。

srand和time对应的头文件分别是stdlib.h和time.h。

void SetMine(char mine[ROWS][COLS], int row, int col)
{
	int x, y;
	int count = COUNT;
	while (count)
	{
		x = rand() % row + 1;//生成横坐标1~10
		y = rand() % col + 1;//生成纵坐标,公式“a+rand()%col+b”
		if (mine[x][y] == '0')
		{   
			//将雷放入mine数组
			mine[x][y] = '1';
			count--;
		}
	}
}

2.3打印棋盘

在每次我们选择坐标或标记后需要打印一次棋盘,踩到地雷后,将显示所有雷的位置

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i, j;

	//第一行“----...”
	for (j = 0; j <= col; j++)
	{
		printf("----");
	}
	printf("\n");
	
	//第二行“| 0 | 1 |...”
	for (j = 0; j <= col; j++)
	{   
		if (j==0) 
		{
			printf("|");
	    }
		printf(" %-2d|", j);
	}
	printf("\n");
	
	//第三行“----...”
	for (i = 0; i <= col;i++) {
		printf("----");
	}
	printf("\n");

	//四行往下
	for (i = 1; i <= row; i++)
	{
		printf("| %-2d|", i);//“| 1 |”数字列
		
		for (j = 1; j <= col; j++)
		{
			printf(" %c |", board[i][j]);//“*”		
		}
		printf("\n");	
		
		for (j = 0; j <= col; j++)
		{
			printf("----");//行间分隔
		}
		printf("\n");
	}
}

效果图
在这里插入图片描述)

2.4炸开功能

如果只是输入一个坐标,展开一个方格,实现这样的功能很容易。但我们要实现,像这样“炸开”的功能,要使用函数递归,难度就提升了不少。

在这里插入图片描述)

为了实现这个功能,我们要明白三个点:
1.该坐标不是雷
2.该坐标周围不是雷
3.向周围排查时,排查过的坐标不用再排查

像下面这张图,首先1位置不是雷,且周围没有雷,接着以2为中心,接着排查,这个时候1已经排查过了,所以不要再排查。如果继续排查,那么就会出现死递归,一直在1和2之间来回递归。

在这里插入图片描述

int get_mine_count(char board[ROWS][COLS], int x, int y)
{
	//计算周围8个格子有多少雷,int类型
	return  board[x - 1][y - 1] +
		board[x][y - 1] +
		board[x + 1][y - 1] +
		board[x + 1][y] +
		board[x + 1][y + 1] +
		board[x][y + 1] +
		board[x - 1][y + 1] +
		board[x - 1][y] - 8 * '0';
}

//炸弹式展开
void explode_spread(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y)
{
	//限制非法坐标的展开
	if (x >= 1 && x <= row && y >= 1 && y <= col)
	{
		//计算该位置附近四周地雷的个数
		int count = get_mine_count(mine, x, y);
		//若四周没有一个地雷,则需要向该位置的四周展开,直到展开到某个位置附近存在地雷为止
		if (count == 0)
		{
			//把附近没有地雷的位置变成字符 “空格”
			show[x][y] = ' ';
			int i = 0;
			//向四周共8个位置递归调用
			for (i = x - 1; i <= x + 1; i++)
			{
				int j = 0;
				for (j = y - 1; j <= y + 1; j++)
				{
					//限制对点位置的重复展开调用,使得每一个位置只能向四周展开一次
					if (show[i][j] == '*')
					{
						explode_spread(mine, show, row, col, i, j);
					}
				}
			}
		}
		//若四周存在地雷则应该在这个位置上标注上地雷的个数
		else
		{
			show[x][y] = count + '0';
		}
	}
}

2.5标记功能

void MarkerMine(char show[ROWS][COLS], int row, int col) 
{
	int input = 0;
	int x, y;	

	do {
		printf("进行标记输入:1  ");
		printf("删除标记输入:2  ");
		printf("退出标记输入:0\n");
		printf("请选择:>");	
		scanf("%d",&input);

		//清理缓存区
		while (getchar() != '\n');
		switch (input) {
		case 1:
			printf("请输入坐标进行标记:>");
			scanf("%d %d", &x, &y);
			
			//清理缓存区
			while (getchar() != '\n');
			show[x][y] = '!';
			system("cls");
			DisplayBoard(show, ROW, COL);
			break;
		case 0:
			printf("完成标记\n");
			break;
		case 2:
			printf("请选择坐标删除标记:>");
			scanf("%d %d", &x, &y);

			//清理缓存区
			while (getchar() != '\n');
			show[x][y] = '*';

			//清除屏幕
			system("cls");
			DisplayBoard(show, ROW, COL);
			break;
		}
	} while (input);
}

2.6进行扫雷,判断输赢

判断输赢主要根据win的值,(row * col - COUNT)这是无雷的数量,只要win大于它即可判断赢

//踩雷后将mine数组中的雷‘1’换做‘#’,告诉玩家雷的位置
void DisplayAllMines(char mine[ROWS][COLS], int row, int col) 
{
	int i,j;
	for (i = 1; i <= row ; i++) 
	{
		for (j = 1; j <= col ; j++)
		{
			if (mine[i][j]=='1')
			{
				mine[i][j] = '#';
			}
		}
	}
}

void FineMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int i, j;
	int x, y;
	int win =0;
	while (win < row * col - COUNT)
	{
		win = 0;
		printf("请输入坐标:>");
		scanf("%d %d", &x, &y);
		
		//清理缓存区
		while (getchar() != '\n');
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (show[x][y] != '*')
			{
				printf("该处已被排查,请重新选择\n");
			}
			else
			{
				if (mine[x][y] == '1')
				{
					system("cls");

					//将所有存放在mine数组中的‘1’替代为‘#’
					DisplayAllMines(mine, ROW, COL);
					DisplayBoard(mine, ROW, COL);
					printf("对不起,您踩到雷\n\n");
					break;
				}
				//该位置未排查
				else if(show[x][y] == '*')
				{
                    NoMineSpace(mine,show, x, y);//展开无雷区域
					system("cls");
					DisplayBoard(show, ROW, COL);//没踩到雷打印
					
                    //计算‘*’剩余个数,用于判断输赢
					for (i = 1; i <= row; i++)
					{
						for (j = 1; j <= col; j++)
						{
							if (show[i][j] != '*'&&show[i][j]!='!')
							{
								win++;
							}
						}
					}
					if (win >= row * col - COUNT)
					{
						printf("恭喜你赢了!\n\n");
						break;
					}
					MarkerMine(show, x, y);//标记
					system("cls");
					DisplayBoard(show, ROW, COL);//标记后打印
				}
			}
		}
		else
		{
			printf("坐标非法,请重新输入");
		}				
	}	
}

四、完整代码

4.1源文件<tect.c>

#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"

void menu()
{
	printf("|-----扫雷游戏-----|\n");
	printf("|------1.play------|\n");
	printf("|------0.exit------|\n");
}

void game()
{
	char mine[ROWS][COLS] = { 0 };//雷的存放,‘0’表示无雷,‘1’表示雷
	char show[ROWS][COLS] = { 0 };//玩家操作的棋盘
	InitBoard(mine, ROWS, COLS, '0');//初始化棋盘
	InitBoard(show, ROWS, COLS, '*');//初始化玩家棋盘
	SetMine(mine, ROW, COL);//开始布置雷
	system("cls");
	DisplayBoard(show, ROW, COL);//打印起始的玩家棋盘 
	FineMine(mine, show, ROW, COL);//排查雷

}

int main()
{
	//生成随机数
	srand((unsigned int)time(NULL));
	int input = 0;   
	do
	{
		menu();
		printf("请输入:>");
		scanf("%d", &input);

		//清空缓存区
		while (getchar() != '\n');
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("结束游戏\n");
			break;
		default:
			printf("请重新输入0或1\n");
			break;
		}
	} while (input);
}

4.2源文件<game.c>

#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"


void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
	int i, j;
	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, j;

	//第一行“----”
	for (j = 0; j <= col; j++)
	{
		printf("----");
	}
	printf("\n");
	
	//第二行“| 0 | 1 |”
	for (j = 0; j <= col; j++)
	{   
		if (j==0) 
		{
			printf("|");
	    }
		printf(" %-2d|", j);
	}
	printf("\n");
	
	//第三行“----”
	for (i = 0; i <= col;i++) {
		printf("----");
	}
	printf("\n");

	//四行往下
	for (i = 1; i <= row; i++)
	{
		printf("| %-2d|", i);//“| 1 |”数字序列
		
		for (j = 1; j <= col; j++)
		{
			printf(" %c |", board[i][j]);//“*”		
		}
		printf("\n");	
		
		for (j = 0; j <= col; j++)
		{
			printf("----");//行间分隔
		}
		printf("\n");
	}
}

void DisplayAllMines(char mine[ROWS][COLS], int row, int col) 
{
	int i,j;
	for (i = 1; i <= row ; i++) 
	{
		for (j = 1; j <= col ; j++)
		{
			if (mine[i][j]=='1')
			{
				mine[i][j] = '#';
			}
		}
	}
}

void SetMine(char mine[ROWS][COLS], int row, int col)
{
	int x, y;
	int count = COUNT;
	while (count)
	{
		x = rand() % row + 1;//生成横坐标1~10
		y = rand() % col + 1;//生成纵坐标,公式“a+rand()%col+b”
		if (mine[x][y] == '0')
		{   
			//将雷放入mine数组
			mine[x][y] = '1';
			count--;
		}
	}
}

int get_mine_count(char board[ROWS][COLS], int x, int y)
{
	//计算周围8个格子有多少雷,int类型
	return  board[x - 1][y - 1] +
		board[x][y - 1] +
		board[x + 1][y - 1] +
		board[x + 1][y] +
		board[x + 1][y + 1] +
		board[x][y + 1] +
		board[x - 1][y + 1] +
		board[x - 1][y] - 8 * '0';
}

void MarkerMine(char show[ROWS][COLS], int row, int col) 
{
	int input = 0;
	int x, y;	

	do {
		printf("进行标记输入:1  ");
		printf("删除标记输入:2  ");
		printf("退出标记输入:0\n");
		printf("请选择:>");	
		scanf("%d",&input);

		//清理缓存区
		while (getchar() != '\n');
		switch (input) {
		case 1:
			printf("请输入坐标进行标记:>");
			scanf("%d %d", &x, &y);
			
			//清理缓存区
			while (getchar() != '\n');
			show[x][y] = '!';
			system("cls");
			DisplayBoard(show, ROW, COL);
			break;
		case 0:
			printf("完成标记\n");
			break;
		case 2:
			printf("请选择坐标删除标记:>");
			scanf("%d %d", &x, &y);

			//清理缓存区
			while (getchar() != '\n');
			show[x][y] = '*';

			//清除屏幕
			system("cls");
			DisplayBoard(show, ROW, COL);
			break;
		}
	} while (input);
}

void  NoMineSpace(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y) 
{
	//合法区域内展开
	if (x >= 1 && x <= ROWS-1 && y >= 1 && y <= COLS-1) 
	{
		//计算该位置周围的雷数
		int num = get_mine_count(mine, x, y);
		if (num == 0)
		{			
			//若四周无雷,该位置存放空格
			show[x][y] = ' ';
			int i = 0;
			
			//向周围8个位置递归
			for (i = x - 1; i <= x + 1;i++) 
			{   
				int j = 0;
				for (j = y - 1; j <= y + 1; j++) 
				{   
					//对未展开的位置进行递归,防止死递归
					if (show[i][j]=='*') 
					{
						//此处新的坐标(i,j)代替坐标(x,y)
						NoMineSpace(mine,show,i,j);
					}
				}
			}
		}
		//若对一个位置四周有雷,则在该位置标注雷的个数
		else 
		{
			show[x][y] = num + '0';
		}
	}
}

void FineMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int i, j;
	int x, y;
	int win =0;
	while (win < row * col - COUNT)
	{
		win = 0;
		printf("请输入坐标:>");
		scanf("%d %d", &x, &y);
		
		//清理缓存区
		while (getchar() != '\n');
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (show[x][y] != '*')
			{
				printf("该处已被排查,请重新选择\n");
			}
			else
			{
				if (mine[x][y] == '1')
				{
					system("cls");

					//将所有存放在mine数组中的‘1’替代为‘#’
					DisplayAllMines(mine, ROW, COL);
					DisplayBoard(mine, ROW, COL);
					printf("对不起,您踩到雷\n\n");
					break;
				}
				//该位置未排查
				else if(show[x][y] == '*')
				{
                    NoMineSpace(mine,show, x, y);//展开无雷区域
					system("cls");
					DisplayBoard(show, ROW, COL);//没踩到雷打印
					
                    //计算‘*’剩余个数,用于判断输赢
					for (i = 1; i <= row; i++)
					{
						for (j = 1; j <= col; j++)
						{
							if (show[i][j] != '*'&&show[i][j]!='!')
							{
								win++;
							}
						}
					}
					if (win >= row * col - COUNT)
					{
						printf("恭喜你赢了!\n\n");
						break;
					}
					MarkerMine(show, x, y);//标记
					system("cls");
					DisplayBoard(show, ROW, COL);//标记后打印
				}
			}
		}
		else
		{
			printf("坐标非法,请重新输入");
		}				
	}	
}

4.3头文件<game.h>

#pragma once
#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 InitBoard(char board[ROWS][COLS], int rows, int cols, char set);

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

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

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

//周围雷的个数
int get_mine_count(char board[ROWS][COLS], int x, int y);

//标记
void MarkerMine(char show[ROWS][COLS], int row, int col);

//展开一片无雷区域
void NoMineSpace(char show[ROWS][COLS], char mine[ROWS][COLS], int x, int y);

//用‘#’代替雷‘1’展示给玩家
void DisplayAllMines(char mine[ROWS][COLS], int row, int col);

这是扫雷的全部内容,这不仅仅是自我的总结,同时也希望能给你们带来微不足道的帮助!

在这里插入图片描述)
如果你喜欢这篇文章,点赞👍+评论+关注⭐️哦!
欢迎大家提出疑问,以及不同的见解。

  • 9
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值