【手把手教你实现C语言】扫雷游戏


在这里插入图片描述

一、扫雷游戏功能

  1. 使⽤控制台实现经典的扫雷游戏
  2. 游戏可以通过菜单实现继续玩或者退出游戏
  3. 扫雷的棋盘是9 * 9的格子:9 * 9 的二维数组存放数据
  4. 默认随机布置10个雷
  5. 排查雷
    (1) 如果是雷,就炸死了,游戏结束。
    (2) 如果不是雷,显示该位置周围有几个雷。
    (3) 把所有不是雷的位置全部找出来,剩余的就是雷,游戏就结束了,排雷成功。

二、项目准备

(1)项目创建:在VS2019创建新项目Minesweeper,里面创建3个文件,分别为:

test.c —— 包含main函数,主要用来测试游戏的主逻辑

game.h —— 函数的声明

game.c —— 函数的实现

在这里插入图片描述

(2)扫雷游戏要求控制台输出以及菜单控制继续玩或者退出游戏,可以参考之前的 手把手教你实现C语言】猜数字游戏 的游戏菜单来实现。

test.c 文件中:

在这里插入图片描述

运行测试一下:
在这里插入图片描述
(3)将实现扫雷游戏的过程封装成game()函数。

test.c 文件中:

在这里插入图片描述

三、扫雷游戏分析

1. 游戏实现流程

打开 在线扫雷游戏:https://www.minesweeper.cn/,我们会发现扫雷游戏的界面是一个 9 * 9 的棋盘格。
在这里插入图片描述

随机点击一个格子,它会显示数字。以下图为例,以红框圈中的1为圆心,它周围8个格子(黄框中)有1个雷。
在这里插入图片描述
扫雷游戏的玩法就是选中雷被炸死,没选中雷,则继续选择,直至排雷成功。

在这里插入图片描述

2. 几点思考

(1)利用2个二维数组,存放布置雷的信息和排查出的雷的信息。

  • 玩扫雷游戏时,根据游戏界面,我们可以利用9 * 9 的格子随机布置10个雷,1表示雷,0表示非雷。
    在这里插入图片描述

  • 当我们布置好雷时,可以开始排雷。
    在这里插入图片描述

但是当我们将这个1存放到这个棋盘上,就会产生歧义,让我们不知道1代表的是布置的雷还是排查出它周围雷的个数是1。

  • 解决方法
 方法1

利用2个二维数组存放,1个存放布置雷的信息(用字符 ’ 1 ’ 表示雷,字符 ’ 0 ’ 表示非雷),另1个将排查出雷的个数放在对应的位置上(用字符 ’ * ’ 表示)。

在这里插入图片描述

方法2

有雷的地方用“ # ”表示,无雷的地方用“ * ”表示,这样就不会和排查出的雷的数量搞混了。
在这里插入图片描述
这两种创建数组的方法都可行。

  • 而后,玩家排查雷的时候,需要根据雷的坐标进行排查,所以我们需要打印出扫雷棋盘的行和列,以便确定雷所在的坐标位置。

在这里插入图片描述

  • 综上,我们可以创建两个二维数组 ,一个用来存放布置的雷的信息,一个用来打印并储存排查出来的雷的信息。
    (即考虑到打印的问题,使用上述问题中的第1种方法更好。)

(2)将9 * 9 棋盘扩大一圈,变成11*11的棋盘,好排查边界的雷。

  • 当我们排雷的时候,我们要根据玩家给出的坐标像四周拓展来统计雷的个数。但如果这个坐标在表格的边缘时,我们再向四周拓展的时候就会造成越界。

在这里插入图片描述

  • 那么我们该如何解决呢?

一般我们可以分情况讨论:

先判断玩家给出的坐标在哪个位置,如果在中间,就向四周拓展计算雷的个数;如果在边缘处,就根据在哪个边缘分情况拓展计算。

但是这种方法听起来就好麻烦……除了上下左右四条边,还有角落处的四个位置都要另外分情况计算。
在这里插入图片描述

  • 最好的解决方法是给棋盘扩大一圈,变成11*11的棋盘。

在这里插入图片描述

  • 但是为了让存放雷的信息的位置坐标和布置雷的位置坐标一样,所以我们也要把存放雷信息的格子设计为11*11的数组。
    在这里插入图片描述

(3)上面的2个数组创建为字符数组。

上面的数组要利用字符进行填充,所以创建为字符数组类型。

四、扫雷游戏实现整体流程

扫雷游戏实现流程:
(1)控制台输出和菜单选择继续玩还是退出游戏
(2)初始化2个二维数组,一个布置雷(用字符),一个排查雷(存放雷的位置信息)
(3)这期间,可以打印两个数组看看效果
(4)随机布置10个雷(1随机生成雷的坐标rand1~9,保证位置合法;2不是雷,才布置雷)
(5)排查雷(1看排查的坐标是否越界,x、y在范围内<没越界,看是不是雷;越界,输入坐标不合法>;
2看是否是雷<是雷,就炸死;不是雷,就存show数组>)
(6)看看排查雷是否排查完,谁赢了

五、代码实现

1. 控制台输出和菜单控制在 二(2)中。

  • test.c 中:
    在这里插入图片描述
    在这里插入图片描述
    运行一下,看看效果~
    在这里插入图片描述

2. 创建 11 * 11 的2个二维数组。

(1) 封装game()函数,创建2个字符数组(一个布置雷,一个排雷)

  • test.c文件中:

在这里插入图片描述

(2) 在game.h 文件中利用 #define 定义数组的行和列

若下次使用30*30的数组,可以不用在test.c文件中逐个更改。

  • game.h文件中:

在这里插入图片描述

因为在game.h中声明函数和定义变量,所以在test.c中记得引用game.h哦~

test.c文件中:
在这里插入图片描述

(3)初始化数组

  • test.c 文件中:

在这里插入图片描述

  • game.h 文件中:声明InitBoard函数

在这里插入图片描述

  • game.c 文件中:定义InitBoard函数

在这里插入图片描述

3. 打印看看棋盘效果

在初始化之后,我们想看一下初识化棋盘的效果,所以这里我们需要写一个打印函数。

  • test.c文件中:
    在这里插入图片描述
  • game.h文件中:声明DisplayBoard函数

在这里插入图片描述

  • game.c 文件中:定义DisplayBoard函数
    在这里插入图片描述

在这里插入图片描述
下面运行,看看效果吧~(为了美观,打印出了棋盘的行号、列号以及“--------扫雷--------”提示符)

在这里插入图片描述

4. 随机布置10个雷

  • test.c 文件:调用布置雷的函数SetMine

在这里插入图片描述

  • game.h文件中:声明SetMine函数
    在这里插入图片描述

  • game.c文件: 定义SetMine函数,随机布置10个雷

(a. 生成雷的随机坐标,保证雷的位置合法 b.不是雷,才布置雷)

在这里插入图片描述

随机生成雷中 :

(a) game.h文件中定义布置雷的个数 EASY_COUNT

在这里插入图片描述
(b) srand在test.c 文件中的main函数中
在这里插入图片描述

(c)game.h文件包含所需头文件

在这里插入图片描述

最后,在test.c 中调用
DisplayBoard(mine, ROW, COL);
DisplayBoard(show, ROW, COL);
看看效果。

在这里插入图片描述

5. 排查雷

判断mine数组中雷坐标的合法性,如果坐标不合法,就重新输入
如果坐标合法,则要判断玩家输入的坐标处是否有雷,是雷就炸死
不是雷,就统计它周围的8个坐标雷的个数(GetMineCount函数),并存到show数组

  • test.c 文件:调用FindMine()函数排查雷
    mine数组排查雷,排查的信息放到show数组,排查的范围是9*9

在这里插入图片描述

  • game.h文件中:声明FindMine()函数
    在这里插入图片描述
  • game.c文件: 实现FindMine()函数

(1) 首先,我们需要玩家输入一个坐标,然后,我们要判断坐标的合法性,如果坐标不合法则重新输入;如果坐标合法,则要判断玩家输入的坐标处是否有雷,如果有雷,则玩家提示玩家被炸死,游戏结束。

game.c 文件中:
在这里插入图片描述

运行一下,看看结果。
在这里插入图片描述

(2)如果不是雷,就需要统计此坐标位置周围的8个坐标中雷的个数,然后放到show数组中,显示出来(用GetMineCount函数)。

但我们会发现,统计雷的个数,得到的是一个数,而我们存放在show数组中的元素是一个字符,这里就涉及到了字符和整型数字之间的转换

在ASCII码表中,每个字符对应一个整型数字,所以如果我们把两个字符相减,就能得到一个整形数字。如:‘9’ - ‘0’ = 9。同样地,把9+‘0’,就能得到字符‘9’。

GetMineCount函数统计周围雷个数(game.c 文件中的GetMineCount函数):

在这里插入图片描述

统计出的雷的个数,放到show数组中(game.c 文件中的FindMine()函数):

在这里插入图片描述
运行一下,结果如下。
在这里插入图片描述

在这里插入图片描述

6. 设计游戏结束条件(game.c 文件中的FindMine函数)

由于排雷的循环条件是while(1),所以游戏会一直排雷,不能结束;除非被炸死,才能结束游戏。
回头看,我们是在9*9的棋盘上,布置了10个雷,所以有71个格子非雷。意味着我们排除71个不是雷的位置,游戏结束。

定义排查次数 win
在这里插入图片描述

不要忘记在game.h文件中设置布置雷的个数EASY_COUNT哦~
在这里插入图片描述

不是雷,可以提示玩家还需要排查多少个位置。
在这里插入图片描述

不是雷,并且排雷次数用完后,打印“排雷成功”。

在这里插入图片描述

测试一下,我们不妨把上面的EASY_COUNT设为80,说明只有一个位置不是雷。
在这里插入图片描述
执行一下,看看:
在这里插入图片描述

在这里插入图片描述

六、全部代码

1. test.c

#define _CRT_SECURE_NO_WARNINGS 1
//test.c --- 包含main函数,主要是来测试游戏的主逻辑

#include "game.h"
void menu()//菜单
{
	printf("************************\n");
	printf("******** 1.play ********\n");
	printf("******** 0.exit ********\n");
	printf("************************\n");
}

//完成扫雷游戏的测试
void game()
{
	char mine[ROWS][COLS] = { 0 };//用来存放布置好的雷的信息 --- '0'
	char show[ROWS][COLS] = { 0 };//用来存放排查出的雷的信息 --- '*'

	//a.下面的函数都是函数的调用
	//1.初始化数组
	InitBoard(mine, ROWS, COLS, '0');
	InitBoard(show, ROWS, COLS, '*');

	//2.打印棋盘(只是看一下棋盘打印是否正确,还轮不到打印)
	//DisplayBoard(mine, ROW, COL);
	//DisplayBoard(show, ROW, COL);

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

	//DisplayBoard(show, ROW, COL);

	//4.排查雷
	FindMine(mine, show, ROW, COL);
}

int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));
	do
	{
		menu();
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
			case 1:
				game();
				//printf("开始扫雷游戏\n");
				break;
			case 0:
				printf("退出游戏\n");
				break;
			default:
				printf("选择错误,请重新选择!\n");
				break;
		}
	} while (input);
	
	return 0;
}

2. game.h

#pragma once
//game.h --- 函数的声明
//game.h和game.c负责游戏逻辑的实现,然后在test.c中调用即可

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//#define定义符号
#define ROW 9
#define COL 9

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

#define EASY_COUNT 10

//b.下面的函数都是函数的声明

//1.初始化棋盘
void InitBoard(char board[ROWS][COLS],int row,int col,char set);

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

//3.布置雷
void SetMine(char board[ROWS][COLS], int row, int col);//传过来的行、列参数依然为11*11,只不过只用9*9

//4.排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

3. game.c

#define _CRT_SECURE_NO_WARNINGS 1
//game.c -- 函数的定义
//game.h和game.c负责游戏逻辑的实现,然后在test.c中调用即可

#include "game.h"
//c.下面的函数都是函数的定义

//1.初始化棋盘:mine数组全为'0',show数组全为'*'
void InitBoard(char board[ROWS][COLS], int row, int col,char set)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			board[i][j] = set;
		}
	}
}
//2.打印棋盘:将初始化好的'0'布置雷的棋盘和'*'排查雷的棋盘,打印出来
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;

	printf("------  扫雷  ------\n");

	for (j = 0; j <= col; j++)//显示棋盘的列号
	{
		printf("%d ", j);
	}
	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");
}

//3.布置雷
void SetMine(char board[ROWS][COLS], int row, int col)
{
	int count = EASY_COUNT;//布置10个雷
	while (count)
	{
		//(1)生成雷的随机下标,保证雷位置的合法性
		//随机雷的行x:1~9
		//随机雷的列y:1~9
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		//(2)布置雷:不是雷,才布置雷
		if (board[x][y] != '1')
		{
			board[x][y] = '1';
			count--;
		}
	}
}




int GetMineCount(char mine[ROWS][COLS], int x,int y)//统计排查的雷,周围8个坐标中雷的个数
{
	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';
}
//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 - EASY_COUNT)
	{
		printf("请输入要排查的坐标: ");
		scanf("%d %d", &x, &y);

		
		if (x >= 1 && x <= row && y >= 1 && y <= col)//判断mine数组中雷坐标是否合法
		{
			if (mine[x][y] == '1')//是雷
			{
				printf("很遗憾,此处是雷,被炸死了\n");
				DisplayBoard(mine, ROW, COL);//打印布置好的雷的信息
				break;
			}
			else//不是雷
			{
				win++;
				printf("还需要排查%d个位置\n", row * col - EASY_COUNT - win);
				//就统计x,y周围的8个坐标中雷的个数
				int c = GetMineCount(mine,x,y);	//调用GetMineCount函数统计周围雷的数量
				show[x][y] = c + '0';	//统计出来的数据转化为字符,存到show数组中
				DisplayBoard(show, ROW, COL);

			}
		}
		else
		{
			printf("输入的坐标有误,请重新输入\n");
		}
	}
	if (win == row * col - EASY_COUNT)
	{
		printf("恭喜你,排雷成功\n");
		DisplayBoard(mine, ROW, COL);
	}

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值