C语言数组基础:简易扫雷游戏

目录:

        1.扫雷游戏分析和设计

        2.扫雷游戏代码实现

引言

正文开始          

1.扫雷游戏分析和设计

1.1 扫雷游戏的功能说明

1.2 游戏的分析与设计

1.2.1 数据结构分析

1.2.2 文件结构设计

2. 扫雷游戏的代码实现

2.1 test.c

2.1.1 基础架构

2.1.2 菜单功能

2.1.3 游戏流程

2.2 game.c

2.2.1 棋盘初始化

2.2.2 打印棋盘

2.2.3 放置雷

2.2.4 查找雷

2.3 game.h


引言

        扫雷是一款经典的单人益智游戏,玩家通过点击格子并避免踩雷来获胜。本文将用 ​​C语言​​ 实现一个简易的 ​​9 × 9 扫雷游戏​​,重点讲解如何设计数据结构、生成雷区、计算周围雷数,以及交互逻辑。

正文开始          

1.扫雷游戏分析和设计

1.1 扫雷游戏的功能说明

  • 使用控制台实现经典的扫雷游戏
  • 游戏可以通过菜单实现继续玩或者退出游戏
  • 扫雷的棋盘是9*9的格子
  • 默认随机布置10个雷
  • 可以排查雷
    • 如果位置不是雷,就显示周围有几个雷
    • 如果位置是雷,就炸死游戏结束
    • 把除10个雷之外的所有非雷都找出来,排雷成功,游戏结束
游戏界面:

1.2 游戏的分析与设计

1.2.1 数据结构分析
扫雷的过程中,布置的雷和排查出的雷的信息都需要存储,所以我们需要一定的数据结构来存储这些信息。
因为我们需要在9 * 9的棋盘上布置雷的信息和排查雷,我们首先想到的就是创建一个9 * 9的二维数组来存放信息。
那如果这个位置布置雷,我们就存放1,没有布置雷就存放0。
假设我们排查(2,5)这个坐标时,我们访问周围的一圈8个黄色位置,统计周围雷的个数是1
假设我们排查(8,6)这个坐标时,我们访问周围的一圈8个黄色位置,统计周围雷的个数时,最下面的三个坐标就会越界,为了防止越界,我们在设计的时候,给数组扩大一圈,雷还是布置在中间的9*9的坐标上,周围一圈不去布置雷就行,这样就解决了越界的问题。所以我们将存放数据的数组创建成11 * 11比较合适。
再继续分析,我们在棋盘上布置了雷,棋盘上雷的信息(1)和非雷的信息(0),假设我们排查了某一个位置后,这个坐标处不是雷,这个坐标的周围有1个雷,那我们需要将排查出的雷的数量信息记录存储,并打印出来,作为排雷的重要参考信息的。那这个雷的个数信息存放在哪里呢?
如果存放在布置雷的数组中,这样雷的信息和雷的个数信息就可能或产生混淆和打印上的困难,就比如我排查出附近有(1)个雷,那么我就该把这个坐标标记为(1),但是这样就会让附近的坐标又误以为当前这个坐标是雷(1)。
这里我们肯定有办法解决,比如:雷和非雷的信息不要使用数字,使用某些字符就行,这样就避免冲突了,但是这样做,棋盘上有雷和非雷的信息,还有排查出的雷的个数信息,就比较混杂,不够方便。
这里我们采用另外一种方案,我们专门给⼀个棋盘(对应一个数组mine)存放布置好的雷的信息,再给另外一个棋盘(对应另外一个数组show)存放排查出的雷的信息。这样就互不干扰了,把雷布置到mine数组,在mine数组中排查雷,排查出的数据存放在show数组,并且打印show数组的信息给后期排查参考。
同时为了保持神秘,show数组开始时初始化为字符 '*',为了保持两个数组的类型⼀致,可以使⽤同一套函数处理,mine数组最开始也初始化为字符'0',布置雷改成'1'。如下如:

对应的数组应该是:
char mine[11][11] = {0};//⽤来存放布置好的雷的信息
char show[11][11] = {0};//⽤来存放排查出的雷的个数信息
1.2.2 文件结构设计

为了让整体程序有更好的可维护性、可读性、可扩展性,我们可以采取多文件的方式:

test.c //⽂件中写游戏的测试逻辑 
game.c //⽂件中写游戏中函数的实现等
game.h //⽂件中写游戏需要的数据类型和函数声明等

2. 扫雷游戏的代码实现

2.1 test.c

2.1.1 基础架构

首先我们知道程序运行后,需要什么要求

        1.在菜单上选择,如果选择开始,那么我们开始游戏,选择结束,退出游戏

        2. 只要不选择退出游戏,游戏就会一直保持运行

那么我们就可以使用while + switch函数来实现,但是使用while的话我们首先得知道条件是什么,可我们的需求是先运行出菜单页面,先让我们选择,再进行判断条件,那么我们就可以使用do while

void test()
{
	int input = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("游戏结束,退出游戏\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);
}
2.1.2 菜单功能

在上面代码中,我们需要一个菜单函数menu()进行打印可视化的界面

void menu()
{
	printf("***********************\n");
	printf("*******  1.play  ******\n");
	printf("*******  0.exit  ******\n");
	printf("***********************\n");
}
2.1.3 游戏流程

如果选择开始时,我们需要运行游戏,那么我们就可以写出一个game()函数来实现

在上面分析中,我们知道首先需要设计出两个11 * 11的棋盘mine和show

//可操作棋盘范围
#define ROW 9
#define COL 9

//实际棋盘范围(包含行 列)
#define ROWS ROW+2
#define COLS COL+2

char mine[ROWS][COLS] = { 0 };	//源棋盘 用于找周围有几颗雷 初始化为'0'
char show[ROWS][COLS] = { 0 };	//最终棋盘 由源棋盘得出的雷数改写最后的棋盘 初始化为'*'

下一步,我们需要初始化棋盘,将两个棋盘都初始化,可以先写上需要使用的函数,在后续game.c中再详细编写函数功能。

//初始化棋盘
InitBoard(mine, ROWS, COLS, '0');	//将源棋盘全部初始化为'0'
InitBoard(show, ROWS, COLS, '*');	//将终棋盘全部初始化为'*'

下一步就要设置雷的位置,同样先写上

//安放雷
SetMine(mine, ROW, COL);

因为在开始游戏后,我们首先先得看一眼棋盘,然后选择坐标,所以我们先打印一次棋盘,供玩家更直观地查看棋盘

//打印棋盘
DisplayBoard(show, ROW, COL);

最后我们要做的就是排查雷了,要写出一个排查雷的函数,排查完后还需要让玩家看到排查后的棋盘状态所以需要再打印一次棋盘

//排查雷
FindMine(mine, show, ROW, COL);
DisplayBoard(show, ROW, COL);

所以最后总体是这样的


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 };	//最终棋盘 由源棋盘得出的雷数改写最后的棋盘 初始化为'*'

	//初始化棋盘
	InitBoard(mine, ROWS, COLS, '0');	//将源棋盘全部初始化为'0'
	InitBoard(show, ROWS, COLS, '*');	//将终棋盘全部初始化为'*'

	//安放雷
	SetMine(mine, ROW, COL);

	//打印棋盘
	
	//DisplayBoard(mine, ROW, COL);
	DisplayBoard(show, ROW, COL);
	//排查雷
	FindMine(mine, show, ROW, COL);
	DisplayBoard(show, ROW, COL);
}
void test()
{
	srand(time(NULL));    // 2.2.3函数需要
	int input = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("游戏结束,退出游戏\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);
}

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

2.2 game.c

2.2.1 棋盘初始化

首先接收的参数需要1.行 2.列 3.初始化的目标,然后遍历整个数组,完成初始化

void InitBoard(char arr[ROWS][COLS], int rows, int  cols, char  set)
{
	for (int i = 0; i < rows; ++i)
		for (int j = 0; j < cols; ++j)
			arr[i][j] = set;	//将数组内容全部初始化为set
}
2.2.2 打印棋盘

为了方便玩家能清晰地选出坐标,所以在打印棋盘时,我们还需要先将行号列号打印出来,然后再按序打印棋盘

void DisplayBoard(char arr[ROWS][COLS], int row, int col)
{
	for (int i = 0; i <= row; ++i)
	{
		printf("%2d ", i);	// 行号右对齐
		if (i == 0)
			// 打印列号(1, 2, ..., col)
			for (int j = 1; j <= col; ++j) printf("%2d ", j);
		else
		{
			// 打印内容
			for (int j = 1; j <= col; ++j)
				printf("%2c ", arr[i][j]);	// 内容右对齐
		}
		printf("\n");
	}
}
2.2.3 放置雷
我们需要每一局游戏雷的位置都随机,我们可以使用 随机数,以 时间为种子进行生成,然后再将生成的坐标位置替换为雷(‘1’),为了保证只生成所需要的雷的数量,我们用一个临时变量 count作为计数器使用 while循环将所有雷都放置完成
void SetMine(char arr[ROWS][COLS], int row, int col)
{
	int count = EASY_COUNT;
	while (count)
	{
		//随机生成雷的坐标
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (arr[x][y] == '0')
		{
			arr[x][y] = '1';
			count--;
		}
	}
}
2.2.4 查找雷

游戏胜利的条件就是找到所有的雷,那也就是将除了雷以外的所有棋盘全部都探查完毕。所以我们最多可以查找棋盘的大小 - 雷的数量次。然后要让玩家输入需要排查的坐标并判定合法性(有没有越界),如果是雷,打印出相应提示语并结束游戏,如果不是雷则查找此坐标附近有多少雷,并标记,如果循环结束都没有踩到雷那么通关游戏。

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	//计算一共可输入的次数(棋盘大小 - 雷的数量)
	int cnf = (row * col) - EASY_COUNT;
	while (cnf)
	{
		int x = 0, y = 0;
		printf("请输入要排查的坐标:>");
		scanf("%d %d", &x, &y);
		//判断坐标的合法性
		if (x > 0 && x <= row && y > 0 && y <= col)
		{
			if (mine[x][y] == '0')
			{
				//该坐标不是雷 计算附近有几颗雷
				int count = GetMineCount(mine, x, y);
				show[x][y] = count + '0';
				DisplayBoard(show, row, col);
				cnf--;
			}
			else
			{
				show[x][y] = '#';
				printf("很遗憾,你被炸死了,游戏结束\n");
				break;
			}
		}
		else
			printf("坐标不合法,请重新输入\n");
	}
	if (cnf == 0) printf("恭喜你,通关游戏\n");
}

在上述代码中,if (mine[x][y] == '0') 也就是没踩到雷时,需要将该坐标标记为附近九宫格内的雷的数量,所以我们还需要写一个查找九宫格雷的函数GetMineCount()

int GetMineCount(char arr[ROWS][COLS], int x, int y)
{
	int count = 0;
	//将九宫格内的所有值相加,计算有几颗雷
	for (int i = x - 1; i <= x + 1; ++i)
	{
		for (int j = y - 1; j <= y + 1; ++j)
		{
			if (arr[i][j] == '1')
				count++;
		}
	}
	return count;
}

2.3 game.h

在上述所有代码中,我们还需要一个头文件来声明所有的函数

#include<stdio.h>
#include<stdlib.h>

//可操作棋盘范围
#define ROW 9
#define COL 9

//实际棋盘范围(包含行 列)
#define ROWS ROW+2
#define COLS COL+2

//放置的雷的数量
#define EASY_COUNT 10


void InitBoard(char arr[ROWS][COLS], int rows, int  cols, char  set);
void DisplayBoard(char arr[ROWS][COLS], int row, int col);
void SetMine(char arr[ROWS][COLS], int row, int col);
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

这样就不会让其他文件显得非常繁杂,但是需要使用这个文件的话,得需要在 test.c game.c 文件中引入这个文件头文件

#include "game.h"

这样一个简易扫雷游戏就完成了

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值