C语言扫雷游戏(逻辑与代码实现)

一、扫雷游戏的介绍

扫雷游戏是一款经典的游戏,相信大家都玩过这个游戏,简单介绍一下,扫雷游戏的规则很简单,就是在9x9【初级】,16x16【中级】,或自定义大小的方块矩阵中随机布置一定量的地雷(初级为10个,中级为40个,高级为99个)。由玩家逐个翻开方块,以找出所有地雷为最终游戏目标。如果玩家翻开的方块有地雷,则游戏结束。

本篇博客将带领大家完成初级(9×9)扫雷游戏的实现

二、游戏功能分析及设计思路:

1 .游戏功能分析:

        通过观察扫雷游戏,得到扫雷游戏的相关功能:

a.菜单(menu)模块
      

我们需要设计一个菜单界面,供玩家进行选项选择,由于我们设计的是最基础的扫雷游戏所以我们可以设置两项功能:

        ??1.进入游戏:当玩家输入1时进入游戏。

        ??2.退出游戏:当玩家输入0时退出游戏。

如下图所示:

b.游戏(game)模块:

        进入游戏后,我们可以看到一个矩阵样的棋盘,那么我们需要思考,如何进行棋盘的设计和地雷的存储呢,不难想到我们可以使用二维数组,那么我们需要多大的二维数组呢,我们在下面进行详细分析。

                首先,我们需要把雷和非雷区分开,雷用字符‘1’表示,非雷用字符‘0’表示,这样在后面我们排查雷的时候,这样容易计算格子周围一圈雷的个数。如下图所示,1则代表该格有雷,0则表示无雷

地雷存储棋盘

       我们设计好了地雷存储棋盘用于记录地雷信息,我们需要设计另一个棋盘供玩家进行游戏,在这里我们使用字符“*”表示棋盘格。同时为了方便玩家进行行号 和列号的确定,我们可以在棋盘周围打印下行号与列号。


       紧接着,我们思考一下,我们翻格子的时候,如果这个格子不是雷,那么就会排查这个格子周围八个格子存在雷的个数,假设我们翻开左上角的格子的时候,但是该格子上面和左边没有格子了怎么办?那就会出现越界访问的情况,为了避免这个情况出现,我们只需要把9*9的格子扩张成11*11的格子就可以了,如图所示,这样遍历的时候就不会出现越界访问的情况了。

2.所需文件:

由于扫雷需要实现的功能稍有复杂,所以我们将其拆分为不同的文件进行设计。

我们将代码拆分为三块,如下图所示,分别是:

game.h--用于写相关函数声明。

game.c--用来写模块相关函数实现。

test.c   --用于扫雷整体功能测试。

三、代码实现:

按照第二部分的分析,我们逐步完成代码的功能实现;

1.菜单(menu)模块代码:

void test()
{
	int input = 0;
	srand((unsigned int)time(NULL));
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);//1 0 x
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("游戏结束,退出游戏
");
			break;
		default:
			printf("选择错误,重新选择
");
			break;
		}

	} while (input);
}

int main()
{
	test();
	return 0;
}

2.游戏(game)模块代码:

a.棋盘初始化(InitBoard):

   我们在game.h文件里对棋盘的行列数进行宏定义,宏定义的好处是方便我们后续修改为其他大小的棋盘例如16x16等,由于我们实现9x9的棋盘所以将行(ROW)定义为9,列(COL)定义为9,为了防止上面提到的越界问题,将棋盘扩大到11x11,所以将扩大后的行(ROWS)定义为ROW+2,扩大后的列(COLS)定义为COL+2。
 

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

下来我们实现初始化棋盘的函数:

        对数组进行初始化操作,InitBoard函数使用了四个参数,分别是字符串类型的二维数组用于传入我们设计的扫雷二维数组,两个int类型的参数用于传入行号和列号,由于我们需要初始化两个棋盘,即地雷存储棋盘(全部初始化为字符’0‘),玩家游戏棋盘(全部初始化为字符'※'),所以我们设计了第四个参数,char类型的参数用于传入’0‘或者’*‘。这样我们就可以使用一个函数初始化两个数组。如图所示

b.棋盘打印 (DisplayBoard):

打印棋盘,我们设计三个参数,分别是字符串类型的二维数组用于传入我们设计的扫雷二维数组,两个int类型的参数用于传入行号和列号。代码如下

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	//打印列号
	for (i = 0; i <= col; i++)
	{
		printf("%d ", i);
	}

	printf("
");

	//打印行号
	for (i = 1; i <= row; i++)
	{
		printf("%d ", i);
		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("
");
	}
}

打印效果如下:

c.地雷设置(SetMine)

       设置地雷,布置雷实际上还是把雷放在中间的9*9的数组中去,也就是说我们这里布置雷只需要操作mine数组。怎么实现呢,这里我们假设布置十个雷,那么我们就需要生成十个随机坐标,来放置雷。但是为了以后方便更改数据,所以这里我们可以把雷的数量在game.h头文件中。在放置雷的时候,我们需要判断该位置是否放置过地雷,具体代码和运行结果如下图示:

#define EASY_COUNT 10
void SetMine(char board[ROWS][COLS], int row, int col)
{
	int count = EASY_COUNT;
	while (count)
	{
		int x = rand() % row + 1;//1~9
		int y = rand() % col + 1;//1~9

		if (board[x][y] == '0')
		{
			board[x][y] = '1';
			count--;
		}

	}
}

生成结果如下:

d.获取周围地雷数量 (get_mine_count):

        获取周围地雷数量,我们排查雷是需要在mine数组里面查找,当我们排查一个单元格时,如果该单元格不是地雷格那么我们需要获取周围一圈格子地雷的数量,代码如下所示:

int get_mine_count(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';//周围8个位置加起来-8*'0'即可得到有几个地雷
}

这里是如何将字符数字和数字字符进行转换的,字符‘0’的ASCLL码值是48,地雷字符为'1'其ASCLL码值为49,我们直接将八个位置的ASCLL码相加并减去八个字符’0‘的ASCLL码就可以得到周围总共地雷的数量。

e.排查地雷(FindMine):

        我们实现了获取周围地雷数量的函数后就可以进行地雷排查函数的设计,我们排查雷是需要在mine数组里面查找,然后将查到的数据放在show数组中展示。我们这个排查雷的函数需要将两个数组都传过去。具体怎么实现呢?代码如下:

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int win = 0;
	//9*9-10 = 71
	while (win < row * col - EASY_COUNT)
	{
		printf("请输入排查雷的坐标:>");
		scanf("%d%d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			//坐标合法
			//1、踩雷

			if (mine[x][y] == '1')
			{
				printf("很遗憾,你被炸死了!
");
				DisplayBoard(mine, row, col);
				break;
			}

			//2、不是雷
			else
			{
				//计算x,y坐标周围有几个雷
				int count = get_mine_count(mine, x, y);
				show[x][y] = count + '0';
				DisplayBoard(show, row, col);
				win++;
			}
		}

		else
		{
			//坐标非法
			printf("输入坐标非法,请重新输入!
");
		}
	}

	if (win == row * col - EASY_COUNT)
	{
		printf("恭喜你,排雷成功!
");
		DisplayBoard(mine, row, col);

	}
}

       对应9*9的棋盘来说,有10个雷,也就是我们需要排查71次才能将十个雷完全排查出来,使得游戏结束,所以在while循环里我们使用row*col-mine_count即计算出安全格的个数,每排查出一个安全格win++直到win=row*col-mine_count时排查完全部安全格,游戏获胜。

四、完整代码:

1.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 EASY_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 board[ROWS][COLS], int row, int col);

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

2. game.c文件代码:

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int win = 0;
	//9*9-10 = 71
	while (win < row * col - EASY_COUNT)
	{
		printf("请输入排查雷的坐标:>");
		scanf("%d%d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			//坐标合法
			//1、踩雷

			if (mine[x][y] == '1')
			{
				printf("很遗憾,你被炸死了!
");
				DisplayBoard(mine, row, col);
				break;
			}

			//2、不是雷
			else
			{
				//计算x,y坐标周围有几个雷
				int count = get_mine_count(mine, x, y);
				show[x][y] = count + '0';
				DisplayBoard(show, row, col);
				win++;
			}
		}

		else
		{
			//坐标非法
			printf("输入坐标非法,请重新输入!
");
		}
	}

	if (win == row * col - EASY_COUNT)
	{
		printf("恭喜你,排雷成功!
");
		DisplayBoard(mine, row, col);

	}
}

3.test.c文件代码:

 
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<time.h>
#include"game.h"
 
void game()
{
	char mine[ROWS][COLS];//地雷记录棋盘
	char show[ROWS][COLS];//玩家使用的扫雷棋盘
	
	//初始化棋盘
	InitBoard(mine,ROWS,COLS,'0');
	InitBoard(show, ROWS, COLS, '*');
 
	//打印棋盘
	ShowBoard(show,ROW,COL);
 
	//随机生成地雷
	SetMine(mine,ROW,COL);
	//展示地雷生成效果 
	//ShowBoard(mine, ROW, COL);
 
	//排查地雷
	FindMine(mine,show,ROW,COL);
}
 
void menu()
{
	printf("*********************
");
	printf("*****  1.play  ******
");
	printf("*****  0.exit  ******
");
	printf("*********************
");
 
}
 
int main() 
{
	int input = 0;
	do
	{	
		menu();
		srand((unsigned int)time(NULL));
		printf("请输入您的选择>");
		scanf_s("%d", &input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("成功退出游戏");
			break;
		default:
			break;
		}
	} while (input);
	
	return 0;
}

以上就是我为大家带来的扫雷小游戏的全部内容。

在这里,给大家留个小问题

当我们在玩扫雷游戏的时候,会出现展开一片与标记的效果。

标记,就是这样子,我们用别的符号代替,每次排查完后,输入1进行标记,符号为!,输入0不进行标记,标记的总数量数量为雷的数量。展开一片就是我没要检查每个炸弹的每一个方向上的每一个方向的雷的数量

根据以上的提示,大家能不能想想怎么实现这样的效果呢?大家可以在评论区留言哦~

本期博客分享到这里,若有不足,望各位在评论区指出,谢谢大家!?( ′???` )比心