C语言——扫雷游戏的实现

1.简要叙述:

       扫雷这款游戏大家基本都玩过,这里不对规则进行过多陈述,直接对扫雷游戏中的一些功能进行解析:

1.1 一个可以选择游玩还是退出的目录

1.2 可以设置不同大小的棋盘来实现不同的游戏难度

1.3 在有解的情况下,可以随意更改雷的个数

1.4 若玩家选到雷则游戏失败并告诉玩家所有雷的分布

1.5若玩家成功排除所有雷则游戏成功

2.代码展示:

//game.h文件代码
#define _CRT_SECURE_NO_WARNINGS 1
#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 type 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 board[ROWS][COLS], int row, int col);//放置雷
void Findmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);//寻找地雷


//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 = 0;
	for (i = 0; i < rows; i++)
	{
		int j = 0;
		for (j = 0; j < cols; j++) 
		{
			board[i][j] = set;
		}
	}
}
void Display(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	printf("-------扫雷游戏-------\n");
	for (i = 0; i <= col; i++)
	{
		printf("%d ", i);//打印列号
	}
	printf("\n");
	for (i = 1; i <= row; i++)
	{
		printf("%d ", i);//打印行号
		int j = 0;
		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
}

void Setmine(char board[ROWS][COLS], int row, int col)//放置10个雷
{
	int x = 0;
	int y = 0;
	int count = type;
	while (count)
	{
		x = rand() % row + 1;
		y = rand() % col + 1;
		if (board[x][y] == '0')
		{
			board[x][y] = '1';
			count--;
		}
	}

}
int getminecount(char mine[ROWS][COLS], int x, int y)
{
	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');
}
void Findmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int win = 0;
	while ( win < row * col - type)
	{
		printf("请输入想要排查的坐标");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (mine[x][y] == '1')
			{
				printf("很遗憾,你被炸死了");
				Display(mine, ROW, COL);
				break;
			}
			else
			{
				win++;
				int count = 0;
				count = getminecount(mine, x, y);
				show[x][y] = count + '0';
				Display(show, ROW, COL);
			}
		}
		else
		{
			printf("坐标非法");
			break;
		}
	}
	if (win == row * col - type)
	{
		printf("恭喜你,成功通关\n");
		Display(mine, ROW, COL);

	}
}


//test.c文件代码
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"


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

void game()
{
	char mine[ROWS][COLS] = { 0 };//存放布置好的雷
	char show[ROWS][COLS] = { 0 };//存放已查询出来的雷的信息
	Initboard(mine, ROWS, COLS, '0');//棋盘初始化
	Initboard(show, ROWS, COLS, '*');
	Display(mine, ROW, COL);//打印棋盘
	/*Display(show, ROW, COL);*/
	Setmine(mine, ROW, COL);//放置雷
	//Display(mine, ROW, COL);//打印放置雷后的棋盘

	Findmine(mine, show, ROW, COL);//排雷
	

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

3.代码解析:

3.1目录:

#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"


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


int main()
{
	int input = 0;
	do
	{
		printf("请输入:\n");
		menu();
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("退出");
			break;
		default:
			printf("请重新输入:");
			break;
		}
	} while ( input);
	return 0;
}

首先声明:此升序把0定义为没有雷,把1定义为存在雷

和上次的三子棋一样,都是先利用menu函数设置一个目录,再在主函数中对menu函数进行引用,利用了switch函数对玩家输入的不同数字进行选择来进入到不同的内容。同时,如果遇到玩家输入的输入不是0/1的情况,需要重新输入,因此,在最外面利用while循环进行检测,如果输入0,则因为是假达到退出游戏的目的。若输入的是除了1/0之外的数,则再次进入循环,输入数字。

3.2 对于设置char mine 和 char show两个数组的解析:

首先,我们先随便找一个扫雷的游戏进行查看:

可以看到,在我们对其中的一个区块点击后,如果区块不是雷且周围没有雷,则不显示,如果区块不是雷,但是其周围有雷,则显示该区块周围雷的个数,不过对于区块周围,则有两种情况:

 第一种情况,则是区块周围有八个雷

第二种情况,则是区块处于游戏区域的边缘,周围区块数量<8

第一种情况是正常的,针对第二种情况,我们可以采用两个棋盘的办法进行解决:

即放置一定数量的雷时,按照n*n的大小进行放置,但是创建棋盘时,则按照(n+2)*(n+2)进行创建(或者可以理解为在初始化棋盘的时候按照(n+2)*(n+2)进行创建,但是在对棋盘进行打印的时候,则是按照n*n进行打印的):在绿色的棋盘中放置地雷,但是创建棋盘时确实以(n+2)*(n+2)创建,这样,即使雷的位置在绿色区域的边缘,也会因为红色区域并不会对结果造成影响,从而达到第一种情况的效果,对雷的数目进行正确的显示。

同时,创建的两个数组,一个被用来存放我们布置好的雷依次来达到玩家游戏失败时,可以看到雷分布消息的目的。一个用来显示以查询的雷的信息。我们把这两个数组分别命名为:mine  show.同时为了区分二者,mine数组在初始化时填满‘0’,show数组在初始化时填入‘*’

void game()
{
	char mine[ROWS][COLS] = { 0 };//存放布置好的雷
	char show[ROWS][COLS] = { 0 };//存放已查询出来的雷的信息
	Initboard(mine, ROWS, COLS, '0');//棋盘初始化
	Initboard(show, ROWS, COLS, '*');
	Display(mine, ROW, COL);//打印棋盘
	//Display(show, ROW, COL);
}

如图,在game函数中创建了两个数组,并且,定义了棋盘初始化函数为Initboard,同时在game.h文件中对Initboard函数进行声明

#define _CRT_SECURE_NO_WARNINGS 1
#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 type 10 //定义雷的数目

void Initboard(char board[ROWS][COLS], int rows, int cols, char set);//初始化
void Displayboard(char board[ROWS][COLS], int row, int col);//打印棋盘

 这里可以看到,用#define分别定义了五个量,其中,为了达到上面所说的打印(n+2)*(n+2)棋盘的目的,还额外设置了ROWS变量。

void Initboard(char board[ROWS][COLS], int rows, int cols, char set);//初始化
Initboard(mine, ROWS, COLS, '0');//棋盘初始化
	Initboard(show, ROWS, COLS, '*');

再看到初始化函数,上面说到两个数组的初始化内容是不同的,为了达到这个目的地,可以额外设置一个set变量,并且在函数的定义中,对set变量进行说明,之后,在game.c文件中,通过设置board[ii][j]= set 可以达到上面的目的,代码如下:

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

下面的打印棋盘,和三子棋中的思路一样,唯一需要注意的是函数的定义中,我们创建数组的大小是(n+2)*(n+2),但是后面的rows 和 cols的值是 ROW 和 COL 即n .

void Display(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	printf("-------扫雷游戏-------\n");
	for (i = 0; i <= col; i++)
	{
		printf("%d ", i);//打印列号
	}
	printf("\n");
	for (i = 1; i <= row; i++)
	{
		printf("%d ", i);//打印行号
		int j = 0;
		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
}

3.2如何放置棋子:

#define type 10 //定义雷的数目
void Setmine(char board[ROWS][COLS], int row, int col)//放置10个雷
{
	int x = 0;
	int y = 0;
	int count = type;
	while (count)
	{
		x = rand() % row + 1;
		y = rand() % col + 1;
		if (board[x][y] == '0')
		{
			board[x][y] = '1';
			count--;
		}
	}

}

首先,雷的放置位置一定是随机的,也就是说,需要产生两个随机数来当作雷的横纵坐标。这里便可以使用rand函数,来随机生成随机数,不过在调用rand函数之前,需要先调用srand函数,在srand函数中,调用time函数来满足其随机性,这里需要注意,每次游戏雷的位置都是随机的,所以,每次开始游戏之前都要让产生一次随机数

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

同时,随机数的大小不可以大于设置的棋盘的大小,也就是必须大于等于1同时小于等于ROW/COL,为了达成这个目标,可以通过

x = rand() % row + 1;
		y = rand() % col + 1;

来实现,同时,雷的位置不可以重复,也就是说,每个位置只能存在一颗雷。可以通过if进行判断,如果生成随机数所在的位置不为1,则填入1,如果为一,则本次填入雷不生效,再生成一组随机数,进行判断,填入。

3.4 排除地雷


void Findmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int win = 0;
	while ( win < row * col - type)
	{
		printf("请输入想要排查的坐标");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (mine[x][y] == '1')
			{
				printf("很遗憾,你被炸死了");
				Display(mine, ROW, COL);
				break;
			}
			else
			{
				win++;
				int count = 0;
				count = getminecount(mine, x, y);
				show[x][y] = count + '0';
				Display(show, ROW, COL);
			}
		}
		else
		{
			printf("坐标非法");
			break;
		}
	}
	if (win == row * col - type)
	{
		printf("恭喜你,成功通关\n");
		Display(mine, ROW, COL);

	}
}

在判断排雷时,需要注意以下几个点:

1.输入的排雷坐标是否正确:

这里就涉及到三个情况:   坐标合法   坐标非法     坐标重复

上面说到过,输入的坐标的的大小必须>=1 并且 <= ROW/COL,首先使用一次if语句进行判断,如果输入的坐标满足这个条件,则进入内部进一步判断,如果坐标不满足条件,也就是坐标非法,则通过打印函数告知,坐标非法。

2.如何判断游戏胜利或者失败:

上面说过,定义1为地雷,0则不是地雷,如果在排雷时输入的坐标对应的二维数组的内容恰好是1,则判断为游戏失败。如果输入坐标所对应的二维数组中的内容为0,上面说到过,为0的地方需要显示此处附近的八个区域的存在雷的数目:对于如何判断存在雷的数目,这里需要引入数字字符和数字之间的转换关系:

字符‘0’对应的ASCII值为48,同时,对于数字来说,ASCII值是连贯的,也就是说‘1’,‘2’,‘3’数字字符对应的ASCII值依次为49 50 51,所以,可以得出一个结论,任何数字字符-‘0’=这个数字字符所对应的数字,任何数字+‘0’ = 这个数字所对应的数字字符

借由上面的补充,我们可以把排查地址周围的八个字符中的数字字符加在一起,最后再减去8个‘0’,也就可以得出这八个区域的数字之和,因为我们设置的存在雷为1,所以,所得到的结果就是周围存在的雷的个数,把排查区域的坐标定义为 x y,就可以得到他周围8个区域的坐标为:

因此;通过上面的思路,便可以得到如下代码

int getminecount(char mine[ROWS][COLS], int x, int y)
{
	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');
}

不过这里依然存在一个问题,就是排查雷这个过程循环的次数排查雷是一个循环的过程,游戏胜利的条件是排查出所有的雷,或者可以理解为,我们找到了所有不是雷的区域,而这个区域的大小正是创建数组的ROW*COL-布置的雷的个数,所以得到循环的条件正是创建的win变量(用于记录找到了几次非雷地区)小于ROW*COL-布置的雷的个数。

while ( win < row * col - type)

并且每次输入的坐标所对应的内容是非雷时,win便++一次,可以得到如下代码

win++;
				int count = 0;
				count = getminecount(mine, x, y);
				show[x][y] = count + '0';
				Display(show, ROW, COL);

最后,当排雷过程没有失败并且win不满足上述while循环中的条件时,则可以判断为胜利,也就是,win = 上述条件时:

if (win == row * col - type)
	{
		printf("恭喜你,成功通关\n");
		Display(mine, ROW, COL);

	}

  • 11
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

起床写代码啦!

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

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

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

打赏作者

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

抵扣说明:

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

余额充值