笔记13-2(C语言 扫雷游戏)

注:

  本笔记参考B站up鹏哥C语言的视频

 且该笔记内写入方法只是实现目标方法的其中一种。

目录

要求与准备

菜单

主函数基本思路

代码(主函数思想)

扫雷游戏思路 

进一步思考

初始化棋盘

运用函数[InitBoard]

打印棋盘

运用函数[DisplayBoard]

布置雷

运用函数[SetMine]

排查雷

 运用函数[FindMine]

函数[get_mine_count] 部分

判断胜利

额外

可优化处

1.

源代码

game.h

test.c

game.c



要求与准备

  • test.c    - 扫雷游戏的测试
  • game.c - 游戏的函数实现
  • game.h - 游戏的函数的声明

菜单

代码

void menu()
{
	printf("**********************************\n");
	printf("****        play:输入1        ****\n");
	printf("****        exit:输入0        ****\n");
	printf("**********************************\n");
}
规定1 - 玩游戏; 2 - 退出游戏

主函数基本思路

  • 根据输入值判断是否玩游戏
  • 多次游玩
  • 不玩游戏时可以退出
  • 选择错误时可以重新xuan'z

代码(主函数思想)

int main()
{
	int input = 0;
	do
	{
		menu();
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("扫雷\n");
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);//为了多次游玩,选择 do while 语句
	return 0;
}

扫雷游戏思路 

选择一个方块:

  1. 如果是雷,游戏结束;
  2. 如果不是雷,反馈出方块周围一圈上有几个雷
  3. 当找出除了雷之外的所有格子,游戏结束
  4. 当发现不是

这里制作9*9的格子

  • 存放雷的棋盘
  • (1.布置雷、2.排除雷)--- 9*9的二维数组
  • 约定对应1,不是雷的格子对应0

但是 如果格子内放1,难以区分这个1是格子内是雷,或者是不是雷时反馈回来的1(存在歧义)

注:棋盘上的三种信息 - 雷、非雷、反馈(相关信息过多)

解决

再创建一个数组:专门存放反馈出的信息。(第一个数字专门存放雷)

即要创建两个数组

进一步思考

  • 为了游玩体验,在最开始游戏时,显示出来的数组部分要是 '*',故数组用[char类型](注意:此时在存雷数组内存放的是字符1字符0,反馈数组内存放*号,扫雷过程中往反馈数组内打印字符,这样方便打印)
  • 如果要排查列表边缘方块周围的坐标,即,访问中间小的红色方块所在的位置时,反馈需要访问周围8个方块,但是该坐标的右边和上边不在数组内,访问会越界
  • 为了处理上述情况,在开辟数组时,在上下左右都多开一行

结论:如果想实现9*9的棋盘,数组设置为11*11较好。

实践(创建数组)

	char mine[ROWS][COLS];//存放布置好的雷的消息
	char show[ROWS][COLS];//存放排查出来的信息

注意:这里的 ROWS 及 COLS 均是在 game.h 中定义的变量,目的是方便后期改变棋盘大小。

game.h 中

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

当然,在使用变量时得引头文件。


初始化棋盘

放雷棋盘[mine]:全部初始化为 字符0

反馈数组[show]:全部初始化为 '*'

运用函数[InitBoard]

game.h 部分定义函数

void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);

------

test.c 部分(由于函数定义位于 game.h 部分,所有使用时别忘了引头文件)

InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');

(最后输入初始化棋盘时使用的元素)

必须注意:在每一次调用变量时,必须注意 ROWROWSCOLCOLS 使用情况的区别。

------

game.c 部分

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;
		}
	}
}

把数组每一个元素初始化。

打印棋盘

运用函数[DisplayBoard]

game.h - 定义函数

void DisplayBoard(char board[ROWS][COLS], int row, int col);

------

test.c - 将函数写入流程

DisplayBoard(mine, ROW, COL);
DisplayBoard(show, ROW, COL);

注意:这里将两个棋盘都打印了,但是实际上出现在玩家的屏幕上的是[show]的打印效果)

------

game.c - 编写函数本体

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	printf("------扫雷游戏------\n");
	//打印列号
	for ( i = 0; i <= col; 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");
}

依旧是一个一个打印出数组的每一个元素。而为了界面美观,多打印了行和列的数字,效果如下:

(本处只显示打印反馈数列的效果。)

布置雷

将雷的信息传入存雷数组中。

注:

  • rand函数、srand函数 对应的头文件为<stdlib.h>
  • time函数 对应的头文件为<time.h>
  • srand函数 设置随机数的生成器,rand函数 生成随机数,rand() % 常量 限制随机数的生成范围
  • time函数 获得时间戳。

运用函数[SetMine]

game.h

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

------

test.c

SetMine(mine, ROW, COL);

不过除此之外,还要生成随机数。所以在 test.c 的主函数中设置

srand((unsigned int)time(NULL));

注意:这个函数得放在主函数一开始的位置,因为随机数在一次程序运行中只生成一次。

------

game.c

void SetMine(char mine[ROWS][COLS], int row, int col)
{
	//布置10个雷
	int count = 10;
	while (count)
	{
		//生成随机的下标
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (mine[x][y] == '0')//字符0
		{
			mine[x][y] = '1';//字符1
			count--;
		}
	}
}
使用 count 作为计数器,总共10个雷,每成功放入一个雷,count的值就会发生相应的改变。

排查雷

过程:

  1. 输入排查的坐标
  2. 检查坐标(是否为0

           (1)是雷 - 玩家失败 - 游戏结束

           (2)不是雷 - 统计坐标周围有几个雷 - 存储排查雷的信息到 show数组- 游戏继续

 运用函数[FindMine]

game.h

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

test.c 中加入排查雷的步骤

FindMine(mine, show, ROW, COL);

game.c 中进行函数的实现

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	//过程:

	//	1.输入排查的坐标
	//	2.检查坐标(是否为0)
	//		(1)是雷 - 玩家失败 - 游戏结束
	//		(2)不是雷 - 统计坐标周围有几个雷 - 存储排查雷的信息到 show数组 - 游戏继续

	int x = 0;
	int y = 0;
	while(1)//因为排雷是重复过程,使用使用 while循环
	{
		printf("请输入要排查的雷的目标:");
		scanf("%d%d", &x, &y);//坐标范围:x - (1,9); y - (1,9)

		//判断坐标的合法性
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (mine[x][y] == '1')
			{
				printf("很遗憾,踩到雷了\n");
				DisplayBoard(mine, row, col); 
				break;
			}
			else
			{
				//不是雷的情况 - 统计坐标(x,y)周围有几个雷
				int count = get_mine_count(mine, x, y);//获取数字
				show[x][y] = count + '0';//替换时是字符
				DisplayBoard(show, row, col);//显示排查出的信息
			}
		}
		else
		{
			printf("坐标不合法,请重新输入\n");
		}
	}
}

在查询结果不是雷的情况下,需要统计坐标周围八个位置的雷的数量。这里使用 函数[get_mine_count] 实现。

函数[get_mine_count] 部分

注意,在ASCII码表中存在:

0 (数字)对应ASCII值:0'0' (字符)对应ASCII值:48
1 (数字)对应ASCII值:1'1' (字符)对应ASCII值:49
2(数字)对应ASCII值: 2'2' (字符)对应ASCII值:50
3(数字)对应ASCII值: 3'3' (字符)对应ASCII值:51
............

数字变为对应字符:加上48

数字 + '0' = 数字对应的字符

对于坐标(x,y),存在:

(x - 1 , y - 1)(x - 1, y)(x - 1, y + 1)
(x, y - 1)(x, y)(x, y +1)
(x + 1, y - 1)(x + 1, y)(x + 1, y + 1)

结合上述条件,可得代码:

static int get_mine_count(char mine[ROWS][COLS], int x, int y)
//static - 如果只想让该函数在本源文件内使用
{
	//另一种写法
	//int count = 0;
	//int i = 0;
	//for ( i = 1; i <= 1; i++)
	//{
	//	int j = 0;
	//	for (j = 1; j <= 1; j++)
	//	{
	//		if (i != 0 && j != 0)
	//		{
	//			count += mine[x + i][y + j];
	//		}
	//	}
	//}
	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';//较为简单

}

(ps:static函数 在这里可加可不加。)

一点额外

static的三个作用

  1. 修饰局部变量
  2. 修饰全局变量
  3. 修饰函数

判断胜利

在完成了布置雷和排查雷的步骤后,发现程序并没有结束,因为这里还是缺少 - 判断胜利的步骤

为了判断胜利条件,在函数[FindMine]中加入 变量win

规定:当 win = 棋盘(9*9)中除雷以外使用格子数时,玩家胜利。

于是当玩家扫雷时,若所选格子不是雷,则规定 win++ :

//判断坐标的合法性
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
	if (mine[x][y] == '1')
        {
	        printf("很遗憾,踩到雷了\n");
		DisplayBoard(mine, row, col); 
		break;
	}
	else
	{
		//不是雷的情况 - 统计坐标(x,y)周围有几个雷
		int count = get_mine_count(mine, x, y);//获取数字
		show[x][y] = count + '0';//替换时是字符
		DisplayBoard(show, row, col);//显示排查出的信息
		win++;
	}
}
else
{
	printf("坐标不合法,请重新输入\n");
}

        这样就完成了玩家胜利条件的判断。

额外

可是,如果玩家两次选择同一格子怎么办?

提供一种解决例子:

  1. 再加入一个数组,用来存放玩家输入过的坐标;
  2. 当玩家再次输入坐标时,先比较两次输入坐标是否相同;
  3. 相同 --- 跳过本次判断,让玩家重新打印;
  4. 不同 --- 执行下一步。
if (ch[x][y] != mine[x][y])
{
	ch[x][y] = mine[x][y];
}
else
{
	printf("输入值重复,请重新输入\n");
	continue;
}

 (记得创建数组时要初始化数组

可优化处

1.

如果坐标(x,y)处不是雷,坐标周围也不是雷 - 展开(递归),直到展开到展开边界坐标周围有雷;

实现的其中一种方法:

运用函数[Extent_show(mine, show, arr, x, y)]

void Extent_show(char mine[ROWS][COLS], char show[ROWS][COLS], char arr[ROWS][COLS], int x, int y)
{

        if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
	{
		int i = 0;
		for (i = -1; i <= 1; i++)
		{
			int j = 0;
			for (j = -1; j <= 1; j++)
			{
				int count = get_mine_count(mine, x, y);//获取数字
				show[x][y] = count + '0';//替换时是字符
				if (mine[x + i][y + j] != '1' && arr[x + i][y + j] != mine[x + i][y + j])
				{
					arr[x + i][y + j] = mine[x + i][y + j];
					int sur = get_mine_count(mine, x + i, y + j);
					show[x + i][y + j] = sur + '0';
					if (sur = 0)
					{
						Extent_show(mine, show, arr, x + i, y + j);
					}
				}
				
			}
		}
	}
}

这里用一种很笨的方法实现了递归,循环是为了保障每一次的搜索范围是坐标周围的8格, 三个if语句:

第一个

if (x >= 1 && x <= ROW && y >= 1 && y <= COL)

是为了限制递归范围,避免栈溢出;


第二个

if (mine[x + i][y + j] != '1' && arr[x + i][y + j] != mine[x + i][y + j])

这里可以发现多出来了一个数组arr,为了记录查询坐标,防止重复查询;


第三个:

if (sur = 0)

用来判断坐标所在位置附近8个是否有雷,有雷,则停止以该指标为起点进行新的查询。

2.

如果认为某个坐标是雷,标记该坐标。

在函数[FindMine]中插入标记语句:

	char ch_S[ROWS][COLS];
	InitBoard(ch, ROWS, COLS, '?');

	while (win < row * col - ESAY_COUNT)//因为排雷是重复过程,使用使用 while循环
	{
		printf("选择1,标记雷\n");
		printf("选择0,跳过标记雷的过程\n");
		printf("请选择:");
		int x = 0;
		scanf("%d", &x);
		switch (x)
		{
		case 1:
			SignMine(show, mine, ch_S, ROW, COL);
			break;
		case 0:
			break;
		default:
			printf("输入错误,请重新输入\n");
			printf("\n");
			continue;
		}
//之后接原来部分

利用选择处理,让玩家自主选择是否标记雷。

函数[SignMine] 部分

void SignMine(char show[ROWS][COLS], char mine[ROWS][COLS], char ch_S[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;

	again:
	printf("请输入认为是雷的坐标(x y): ");
	scanf("%d%d", &x, &y);
	if (ch_S[x][y] != mine[x][y])
	{
		ch_S[x][y] = mine[x][y];
		show[x][y] = '!';
	}
	else
	{
		printf("输入值重复,请重新输入\n");
		goto again;
	}
	DisplayBoard(show, ROW, COL);
}

这次是多传了 数组ch_S 过去,为的也是方便记录已经输入过的坐标。不过这里使用了 goto语句,在这里是个人认为比较方便的做法。

源代码

game.h

#include<stdio.h>

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

//初始化棋盘
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 mine[ROWS][COLS], int row, int col);

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

test.c

#define _CRT_SECURE_NO_WARNINGS

#include"game.h"
#include<stdlib.h>
#include<time.h>

void menu()
{
	printf("**********************************\n");
	printf("****        play:输入1        ****\n");
	printf("****        exit:输入0        ****\n");
	printf("**********************************\n");
}

void game()
{
	char mine[ROWS][COLS];//存放布置好的雷的消息
	char show[ROWS][COLS];//存放排查出来的信息

	//初始化棋盘
	InitBoard(mine, ROWS, COLS, '0');
	InitBoard(show, ROWS, COLS, '*');

	//打印棋盘(只打印中间9*9的范围)
	//DisplayBoard(mine, ROW, COL);
	DisplayBoard(show, ROW, COL);

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

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

int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));
	//(unsigned int) - 强制类型转化
	do
	{
		menu();
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();//扫雷游戏
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);
	//为了多次游玩,选择 do while 语句
	return 0;
}

game.c

#define _CRT_SECURE_NO_WARNINGS

#include"game.h"
#include<stdlib.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;
		}
	}
}

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	printf("------扫雷游戏------\n");
	//打印列号
	for ( i = 0; i <= col; 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 mine[ROWS][COLS], int row, int col)
{
	//布置10个雷
	int count = 10;
	while (count)
	{
		//生成随机的下标
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (mine[x][y] == '0')//字符0
		{
			mine[x][y] = '1';//字符1
			count--;
		}
	}
}


void SignMine(char show[ROWS][COLS], char mine[ROWS][COLS], char ch_S[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;

	again:
	printf("请输入认为是雷的坐标(x y): ");
	scanf("%d%d", &x, &y);
	if (ch_S[x][y] != mine[x][y])
	{
		ch_S[x][y] = mine[x][y];
		show[x][y] = '!';
	}
	else
	{
		printf("输入值重复,请重新输入\n");
		goto again;
	}
	DisplayBoard(show, ROW, COL);
}


static int get_mine_count(char mine[ROWS][COLS], int x, int y)
//static - 如果只想让该函数在本源文件内使用
{
	//另一种写法
	int count = 0;
	int i = 0;
	for ( i = -1; i <= 1; i++)
	{
		int j = 0;
		for (j = -1; j <= 1; j++)
		{
			if (i != 0 && j != 0 && mine[x+i][y+j] == '1')
			{
				count ++;
			}
		}
	}
	return count;
	//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 Extent_show(char mine[ROWS][COLS], char show[ROWS][COLS], char arr[ROWS][COLS], int x, int y)
{

	if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
	{
		int i = 0;
		for (i = -1; i <= 1; i++)
		{
			int j = 0;
			for (j = -1; j <= 1; j++)
			{
				int count = get_mine_count(mine, x, y);//获取数字
				show[x][y] = count + '0';//替换时是字符
				if (mine[x + i][y + j] != '1' && arr[x + i][y + j] != mine[x + i][y + j])
				{
					arr[x + i][y + j] = mine[x + i][y + j];
					int sur = get_mine_count(mine, x + i, y + j);
					show[x + i][y + j] = sur + '0';
					if (sur = 0)
					{
						Extent_show(mine, show, arr, x + i, y + j);
					}
				}
				
			}
		}
	}
}

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	//过程:

	//	1.输入排查的坐标
	//	2.检查坐标(是否为0)
	//		(1)是雷 - 玩家失败 - 游戏结束
	//		(2)不是雷 - 统计坐标周围有几个雷 - 存储排查雷的信息到 show数组 - 游戏继续

	int x = 0;
	int y = 0;
	int win = 0;
	char ch[ROWS][COLS];
	InitBoard(ch, ROWS, COLS, '?');
	char ch_S[ROWS][COLS];
	InitBoard(ch, ROWS, COLS, '?');

	while (win < row * col - ESAY_COUNT)//因为排雷是重复过程,使用使用 while循环
	{
		printf("选择1,标记雷\n");
		printf("选择0,跳过标记雷的过程\n");
		printf("请选择:");
		int x = 0;
		scanf("%d", &x);
		switch (x)
		{
		case 1:
			SignMine(show, mine, ch_S, ROW, COL);
			break;
		case 0:
			break;
		default:
			printf("输入错误,请重新输入\n");
			printf("\n");
			continue;
		}

	Con:
		printf("请输入要排查的雷的目标(x y):");
		scanf("%d%d", &x, &y);//坐标范围:x - (1,9); y - (1,9)
		
		if (ch[x][y] != mine[x][y])
		{
			ch[x][y] = mine[x][y];
		}
		else
		{
			printf("输入值重复,请重新输入\n");
			goto Con;
		}

		//判断坐标的合法性
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (mine[x][y] == '1')
			{
				printf("很遗憾,踩到雷了\n");
				DisplayBoard(mine, row, col); 
				break;
			}
			else
			{
				//不是雷的情况 - 统计坐标(x,y)周围有几个雷
				char arr[ROWS][COLS];
				InitBoard(arr, ROWS, COLS, '?');
				Extent_show(mine, show, arr, x, y);
				DisplayBoard(show, row, col);//显示排查出的信息
				win++;
			}
		}
		else
		{
			printf("坐标不合法,请重新输入\n");
		}
	}
	if (win == row * col - ESAY_COUNT)
	{
		printf("恭喜,排雷成功\n");
		DisplayBoard(mine, row, col);
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言是一种广泛应用于计算机科学和软件开发的编程语言。它具有强大的功能和灵活性,适用于开发各种类型的应用程序。 C语言专题精讲篇是一个对C语言进行深入学习和讲解的系列文章或课程。它汇总了C语言相关的重要知识点和技巧,旨在帮助学习者更好地理解和运用C语言。 这个专题中的笔记涵盖了C语言的各个方面,包括基本语法、数据类型、运算符、流程控制、函数、数组、指针、结构体、文件操作等。通过系统性的学习和总结,这些笔记可以帮助学习者逐步掌握C语言的核心概念和常用技巧。 在这个专题中,学习者可以学到如何编写简单的C程序,如何使用变量和运算符进行计算,如何使用条件和循环语句控制程序流程,如何使用函数进行代码的模块化,如何使用数组和指针进行数据的处理,如何使用结构体组织复杂数据,如何进行文件的读写等等。 C语言专题精讲篇的目的是帮助学习者全面、深入地了解C语言的各个方面,并能够独立编写和调试简单到中等难度的C程序。通过反复实践和练习,学习者可以逐渐提高自己的编程能力,并为进一步学习更高级的编程语言打下坚实的基础。 总之,C语言专题精讲篇的笔记汇总是一份重要的学习资料,可以帮助学习者系统地学习和掌握C语言的基础知识和常用技巧,为他们未来的编程之路打下坚实的基石。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值