C语言实现扫雷小游戏(上)

  业精于勤,荒于嬉;行成于思,毁于随。
  2021农历辛丑牛年,祝所有人心想事成,万事胜意!
  二维数组练习第二弹,实现扫雷小游戏。



一、扫雷游戏规则

  把所有非地雷的格子揭开即胜利,踩到地雷格子即失败。


二、游戏流程

电脑打印游戏菜单(1.开始游戏 0.退出游戏)——>玩家选择(开始游戏——>进入游戏函数)——>电脑打印出雷盘——>玩家输入需要排雷的坐标——>可能性1:被炸死 可能性2:坐标安全,系统显示出周围八个格子内的地雷总数——>雷盘更新——>玩家输入需要排雷的坐标,继续排雷——>游戏胜利


三、扫雷游戏的双层数组

  前文有写到三(多)子棋小游戏,三子棋游戏只需要一个数组就够了,但是对于扫雷游戏的实现,一个数组是不够的,需要创建两个数组。


两个二维数组

  第一个数组,存放雷的分布信息,面向游戏设计者创建,暂且称之为雷盘布置数组,简称布雷数组,(1代表雷,0代表安全坐标,这里是符号’1’和符号’0’)如下所示。

0 1 2 3 4 5 6 7 8 9
1 0 0 0 0 0 1 0 1 0
2 0 0 0 0 0 0 1 0 0
3 0 1 0 0 0 0 1 1 0
4 0 0 0 0 0 0 0 0 0
5 0 0 0 0 1 0 0 0 0
6 0 0 0 0 0 0 0 0 0
7 0 0 0 0 1 0 0 0 0
8 0 1 0 0 0 0 1 0 0
9 0 0 0 0 0 0 0 0 0

  第二个数组,存放排雷后的雷盘中雷的分布个数信息,面向玩家创建,暂且称之为玩家数组,如下所示。

0 1 2 3 4 5 6 7 8 9
1 * * * * * * * * *
2 * * * * * * * * *
3 * * 1 * * * * * *
4 * * * * * * * * *
5 * * * * * * * * *
6 * * * * * * * * *
7 * * * * * * * * *
8 * * * * * * * * *
9 * * * * * * * * *

布雷数组巧妙的设计

  程序设计的时候,需要判断玩家坐标周围八个格子中雷的总个数,那么这就带来一个问题:
  玩家选择边角最外环的一层坐标和玩家选择内环坐标判断方法不同。目测有两种解决方案。

  第一种:加if判断

如果是最外面的一圈的就单独拉出来判断其周围的雷的总数,但是这又导致了一个问题,四个角和每条棱的判断方法各不相同,所以这个解决方案很繁琐。

  第二种:巧妙地让布雷数组膨胀一圈

假设让布置雷的雷盘是11X11规格的,但是只在9X9的格子里布雷,那么对于每个9X9格子里的坐标,判断周围格子里雷的总数的算法都是一样的。(把巧妙打在公屏上!!!)


四、程序设计

  创建三个文件,game.h 游戏的头文件functions.c游戏所用到功能函数 game.c 游戏框架 三个文件。


头文件

  头文件的书写是一步一步需要什么写什么建立而来的,这里先把所有的宏定义、库函数引用、函数声明放这里,后面用到会有解释。

#define _CRT_SECURE_NO_WARNINGS 1

#define ROW 9
#define COL 9

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

#define EASY_COUNT 10

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

void test(void);
void menu(void);
void game(void);

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[ROW][COL], int row, int col);
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

游戏实现框架

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

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

//游戏执行测试框架
void test(void)
{
    
    
	int input = 0;

	do {
    
    
		menu();//系统打印游戏菜单,调用menu函数

		printf("请选择:>>>");
		scanf("%d", &input);

		switch (input)//switch开关语句,作用显而易见
		{
    
    
		case 1:
			game();//调用game函数
			break;
		case 2:
			printf("退出游戏\n");
			input = 0;
			break;
		default:
			printf("输入值无效\n");
			break;
		}

	} while (input);
}

//菜单打印函数
void menu(void)
{
    
    
	printf("************************************\n");
	printf("*****1.开始游戏     0.退出游戏******\n");
	printf("************************************\n");
}

//游戏主函数
void game(void)
{
    
    
	//第一步,创建两个数组,一个是布盘数组,一个是用户数组
	//两个数组大小相同,属于叠加的双层结构。
	char mine[ROWS][COLS] = {
    
     0 };
	char show[ROWS][COLS] = {
    
     0 };
	//第二步,初始化
	InitBoard(mine, ROWS, COLS, '0');//初始化两个数组。
	InitBoard(show, ROWS, COLS, '*');
	//第三步,布置雷
	SetMine(mine, ROW, COL);//布置雷
	DisplayBoard(mine, ROW, COL);//打印雷盘分布,上帝视角,测试需要。
	DisplayBoard(show, ROW, COL);//更新打印好后的雷盘
	//第四步,扫雷
	FindMine(mine, show, ROW, COL);
}

功能函数

  游戏主函数中除游戏框架外的所有函数,习惯单独写在一个文件里。


InitBoard

  数组创建好,需要对两个数组进行初始化,但是因为两个数组初始化的分辨元素不一样,所以这里加入一个调用参数set。

//初始化函数
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
    
    
	int i = 0;
	int j = 0;
	for (i = 0; i < rows; i++)
	{
    
    
		for (j = 0; j < cols; j++)
		{
    
    
			board[i][j] = set;//让布雷数组全部元素为字符'0',玩家数组全部为字符'*'
		}
	}
}

DisplayBoard

  初始化函数写好之后可以先写雷盘打印函数进行核验,所以先介绍DisplayBoard函数。

//雷盘打印
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
    
    
	int i = 0;
	int j = 0;

	printf("\n");//打印个换行符分割下,不是必要

	//打印列号
	for (i = 0; i <= row; i++)
	{
    
    
		printf("%d ", i);
	}
	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");//打印个换行符分割下,不是必要
}

SetMine

  数组初始化好就要布置雷了,采用随机值的方法进行布雷,传递整个数组,但需要控制雷的分布只出现在内环9*9的网格里。

//布置雷盘
void SetMine(char board[ROWS][COLS], int row, int col)
{
    
    
	int count = EASY_COUNT;
	srand((unsigned int)time(NULL));//使用系统时间作为随机值的种子

	while (count)//控制雷的个数
	{
    
    
		int x = rand() % row + 1;
		int y = rand() % col + 1;

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

FindMine


  进行扫雷,如果玩家踩雷就被炸死,未踩雷,系统报告周围雷的个数。

//扫雷
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)
		{
    
    
			if (mine[x][y] == '1')
			{
    
    
				printf("\n啊哦,你被炸死了!!!\n");
				DisplayBoard(mine, row, col);
				break;
			}
			else
			{
    
    //计算周围8个方块中雷的总数
				int count = (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';
				show[x][y] = count + '0';
				DisplayBoard(show, row, col);
				win++;//玩家输入一次win加一次,等到棋盘上只有雷就胜利了
			}
		}
		else
		{
    
    
			printf("非法输入!!!\n");
		}
	}
}

五、完整程序

#define _CRT_SECURE_NO_WARNINGS 1

#define ROW 9
#define COL 9

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

#define EASY_COUNT 10

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

void test(void);
void menu(void);
void game(void);

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);
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include "game.h"

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

//游戏执行测试框架
void test(void)
{
    
    
	int input = 0;

	do {
    
    
		menu();//系统打印游戏菜单,调用menu函数

		printf("请选择:>>>");
		scanf("%d", &input);

		switch (input)//switch开关语句,作用显而易见
		{
    
    
		case 1:
			game();//调用game函数
			break;
		case 2:
			printf("退出游戏\n");
			input = 0;
			break;
		default:
			printf("输入值无效\n");
			break;
		}

	} while (input);
}

//菜单打印函数
void menu(void)
{
    
    
	printf("************************************\n");
	printf("*****1.开始游戏     0.退出游戏******\n");
	printf("************************************\n");
}

//游戏主函数
void game(void)
{
    
    
	//第一步,创建两个数组,一个是布盘数组,一个是用户数组,两个数组大小相同,属于叠加的双层结构。
	char mine[ROWS][COLS] = {
    
     0 };
	char show[ROWS][COLS] = {
    
     0 };
	//第二步,初始化
	InitBoard(mine, ROWS, COLS, '0');//初始化两个数组。
	InitBoard(show, ROWS, COLS, '*');
	//第三步,布置雷
	SetMine(mine, ROW, COL);//布置雷
	DisplayBoard(mine, ROW, COL);//打印雷盘分布,上帝视角,测试需要。
	DisplayBoard(show, ROW, COL);//更新打印好后的雷盘
	//第四步,扫雷
	FindMine(mine, show, ROW, COL);
}
#include "game.h"

//初始化函数
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
    
    
	int i = 0;
	int j = 0;
	for (i = 0; i < rows; i++)
	{
    
    
		for (j = 0; j < cols; j++)
		{
    
    
			board[i][j] = set;//让布雷数组全部元素为字符'0',玩家数组全部为字符'*'
		}
	}
}

//打印雷盘
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
    
    
	int i = 0;
	int j = 0;

	printf("\n");//打印个换行符分割下,不是必要

	//打印列号
	for (i = 0; i <= row; i++)
	{
    
    
		printf("%d ", i);
	}
	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");//打印个换行符分割下,不是必要
}

//布置雷盘
void SetMine(char board[ROWS][COLS], int row, int col)
{
    
    
	int count = EASY_COUNT;
	srand((unsigned int)time(NULL));//使用系统时间作为随机值的种子

	while (count)//控制雷的个数
	{
    
    
		int x = rand() % row + 1;
		int y = rand() % col + 1;

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

//扫雷
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)
		{
    
    
			if (mine[x][y] == '1')
			{
    
    
				printf("\n啊哦,你被炸死了!!!\n");
				DisplayBoard(mine, row, col);
				break;
			}
			else
			{
    
    //计算周围8个方块中雷的总数
				int count = (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';
				show[x][y] = count + '0';
				DisplayBoard(show, row, col);
				win++;//玩家输入一次win加一次,等到棋盘上只有雷就胜利了
			}
		}
		else
		{
    
    
			printf("非法输入!!!\n");
		}
	}
}

六、游戏测试

  这里将ROWCOL更改成3EASY_COUNT改成了5进行测试。

************************************
*****1.开始游戏       0.退出游戏******
************************************
请选择:>>>1

0 1 2 3
1 1 1 0
2 1 0 1
3 0 0 1


0 1 2 3
1 * * *
2 * * *
3 * * *

请输入排查雷的坐标:>>>1 1

啊哦,你被炸死了!!!

0 1 2 3
1 1 1 0
2 1 0 1
3 0 0 1

************************************
*****1.开始游戏       0.退出游戏******
************************************
请选择:>>>1

0 1 2 3
1 0 1 1
2 1 0 1
3 0 1 0


0 1 2 3
1 * * *
2 * * *
3 * * *

请输入排查雷的坐标:>>>2 2

0 1 2 3
1 * * *
2 * 5 *
3 * * *

请输入排查雷的坐标:>>>1 1

0 1 2 3
1 2 * *
2 * 5 *
3 * * *

请输入排查雷的坐标:>>>3 1

0 1 2 3
1 2 * *
2 * 5 *
3 2 * *

请输入排查雷的坐标:>>>3 3

0 1 2 3
1 2 * *
2 * 5 *
3 2 * 2


恭喜你,历经千辛万苦排雷成功!
    
************************************
*****1.开始游戏     0.退出游戏******
************************************
请选择:>>>

七、写在后面

  总的来说,这个程序只用了四个功能函数(处游戏框架外)实现了基本的扫雷程序,但是游戏还是非常鸡肋的,在真正的扫雷游戏中,当玩家选择的坐标周围八个坐标均没有雷时雷盘会之间展开,这还是很必要的,因为当扫雷的棋盘很大时,一个一个的选择坐标实属太没有游戏体验了,这个需要加入一个递归展开函数。
  加入递归展开后,游戏胜利判断条件也需要改变,相对复杂,所以写在了下一篇博客,欢迎各位大佬进行检阅、批评和指正,非常感谢!!!非常感谢!!!

猜你喜欢

转载自blog.csdn.net/weixin_50915462/article/details/113802595