Z_002 扫雷游戏

目录

​​​

【分析已有案例】

【基本思路】

【1】: 前戏

【2】: 游戏实现

【1】前戏

1.1菜单:

1.2选择:

【2】游戏实现

20 分块:

21. 基本

2 . 函数

        21.初始化棋盘:

        22.打印棋盘:

        23.布雷:

        24.排雷:

                 241.子函数 ( 计算附近的雷 ):

        ​​​​​​​         242.子函数 ( 展开1圈 ):

【3】.代码分享

        test.c        ( 主程序 )

        game.c        ( 游戏的主要功能  >  都集成于此 )

        game.h        ( 头文件,game.c的函数声明,宏定义,引用库函数 )



【分析已有案例】


【1】数字代表的含义:
        11.有数字:
                    附近8个格子中有几个雷
        12.无数字:            
                    无数字则附近8个格子没有雷,展开一片


【2】玩游戏时
        21.没点到雷:
                    展开该格子,显示附近雷的个数
        22.点到雷:
                    游戏结束,并向玩家展示所有的雷,让玩家死的明明白白


【3】显示
        31.游戏开始显迷雾状,需要点击揭开该格子
        32.玩家看不到雷,雷是隐藏的



【基本思路】

【1】: 前戏

                        1.菜单
                        2.选择 ( 是否进入游戏 )

【2】: 游戏实现

                        1. 基本
                                布雷棋盘:( 不给玩家看 )
                                展示棋盘:( 展示给玩家看,展开后:显示附近雷的个数 )
                                宏定义:( 便于维护和修改 )
                                        ROW  行
                                        COL   列
                                        MineCnt   雷的个数 ( 游戏难度 )
                        2 . 函数
                                初始化棋盘
                                打印棋盘
                                布雷
                                扫雷
                                



【1】前戏

1.1菜单:

        提示玩家进行选择,是否玩游戏

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

        

1.2选择:

        不喜欢复制粘贴,我们分为三段来理解:

第【1】段:提示,输入

{
    int input = 0;                    //(1)创建变量input

    printf("请选择(1/0):");           //(2)提示玩家选择 ( 1 0 )
    scanf("%d", &input);             //(3)输入
}
第【2】段:选择

switch (input)                        //(0)我们前面输入的 input ,代入这里
{
	case 1:
		printf("进入游戏\n\n");        //(1)玩游戏,调用游戏模块( game.c )
		game();
		break;
	case 0:
		printf("退出游戏\n\n");        //(2)不玩,退出
		break;
	default:
		printf("选择错误\n\n");        //(3)相当于 else ,重新输入
		break;
}

(9) 也可也用 if 语句做代替
第【3】段:循环

	int input = 0;
	do                
	{
		//menu();
		//printf("请选择(1/0):");
		//scanf("%d", &input);

		//switch (input)
		//{
		//case 1:
		//	printf("进入游戏\n\n");
		//	game();
		//	break;
		//case 0:
		//	printf("退出游戏\n\n");
		//	break;
		//default:
		//	printf("选择错误\n\n");
		//	break;
		}
	} while (input);              //(0) input在这里再次做判断条件,输入0时,循环结束


(1) 把前面所说的代码,扔进do while循环里就好了
(2) do while 的执行流程是:
            不管条件是[3x7=21],先执行1次里面的语句,然后再进行判断,要不要循环

完整的代码:

void test()                           //(0) 封装成函数 test , 由 main 函数调用
{
	int input = 0;
	do
	{
		menu();
		printf("请选择(1/0):	");
		scanf("%d", &input);

		switch (input)
		{
		case 1:
			printf("进入游戏\n\n");
			//game();                 //(1) 由于我们还没有写游戏函数,先注释掉
			break;
		case 0:
			printf("退出游戏\n\n");
			break;
		default:
			printf("选择错误\n\n");
			break;
		}
	} while (input);
}



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

 测试一下:
                输入:
                       1:进入游戏
                       0:退出游戏
                       其他:选择错误 ( 循环 > 然后重新输入 )

太棒了!没有问题!我们接下来写游戏实现的部分~



【2】游戏实现

20 分块:

        1.1切记不要把所有代码写在主程序中,上面的代码已经够多啦
        1.2实现游戏的部分,我们单独开一个 [ game.c ] 来写,然后通过主程序来调用他
        

         1.3 然后再开一个 头文件[ game.h ] ,相当于 [ game.c ]  的说明书,声明一些跨文件的函数,如果不声明的话,主程序运行的时候,是不认识他们的哦,所以会报错
                        131.头文件也可以放一些常用的
[ 库函数 ] ,< stdio.h >之类的
                        132.或者将:常用的数字进行
[ 宏定义 ] ,以便于后期修改

 代码如下,我是这样写的:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>        //标准输入输出
#include<stdlib.h>       //生成随机数函数需要
#include<time.h>         //利用 [ 时间戳 ] 生成随机数

然后在test.c中,写一个函数game(),它负责调用 game.c 里的功能
引入头文件 "game.h"

#include"game.h"
void game()
{


}

之后我们写的代码,都是与游戏有关的,放进这个函数内就好了


21. 基本

棋盘(雷阵):
                中间9*9是有效范围
                多1圈是防止下棋越界的安全距离

 实现代码:2维数组

(1)在 test.c 中:
void game()
{
	char show[ROWS][COLS];					// 展示的棋盘
	char mine[ROWS][COLS];					// 布雷的棋盘
	char set = '0';							// 雷的字符
}


(2)在 game.h 中,定义几个常用的宏:

    //下棋的有效范围
    #define ROW 9
    #define COL 9

    //棋盘大小
    #define ROWS ROW+2
    #define COLS COL+2


(9)此时 show mine 2个棋盘的大小都为:11*11


2 . 函数

        21.初始化棋盘:

                        我们有了棋盘,接下来要初始化棋盘

(1)game.c 中,加入:

void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
	int i, j;
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
			board[i][j] = set;         //(0)将棋盘的每1个元素,设置成set(外部传入)
	}
}




(2)test.c中,加入:

    InitBoard(show, ROWS, COLS, '*');        //展示棋盘:初始化为:*
    InitBoard(mine, ROWS, COLS, '0');        //布雷棋盘:初始化为:字符0


 

       22.打印棋盘:

                        220. 9*9的棋盘,那么大,下棋的时候很容易下错坐标
                        221. 我们需要打印出 [ 行号 ] 和 [ 列号 ] ,方便玩家下棋
                        222. 我们只打印中间的有效区域,多出的1圈不打印

                        代码挺复杂的,这里我分2段写:

第(1)段:输出 列号 1 ~ 9

{
	int i, j;
	printf("  ");                    //非重点,格式需要

	for (i = 1; i <= col; i++)       //(1)利用循环变量 i ,输出1~9,中间有1个空格隔开
		printf("%d ", i);

	printf("\n");                    //非重点,格式需要
}
第(2)段:
        1.每行输出:对应行号
        2.每行输出:数据 ( 由棋盘中存放的 [ 字符 ] 来决定 ) 
                展示棋盘:  没揭开的格子,周围雷的数量
                布雷棋盘:  雷 / 不是雷

{
	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 DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i, j;

	printf("  ");
	for (i = 1; 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");
}

写完后,不要忘了在头文件中加入声明哦
和创建函数时,一模一样的参数就好,头文件其实就是告诉程序,要传给这个函数什么类型的参数的才能使用

在头文件 game.h 中,加入

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

 


        23.布雷:

                        220. 让雷随机的分布在棋盘中( 时间戳,取余生成随机数 )
                        221. 1个坐标不会重复多次布雷

                        我们分为3段写:

(1) 随机坐标x,y,随机数 % 9 的范围是 0~8 ,那么我们 +1 就是 1~9 了( 棋盘的有效范围 )

    int x, y;
    x = rand() % row + 1;      //(0~8)+1   =   1~9
    y = rand() % col + 1;




//要使用rand()函数,就需要做下面的操作


(01) test.c中,设置随机数生成器,只需执行1次,永久受益

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



(02) 另外需要在头文件 game.h 中,引入2个库函数

    #include<stdlib.h>
    #include<time.h> 
(2)

这里的set是字符'0',也就是初始化数组的值,代表棋盘是空的 ( 没有雷 )
    
如果坐标 x,y 的坐标是空的,那么就下棋
    
把棋盘中 x,y 的内容替换成字符'1' ( set+1 )



	  if (mine[x][y] == set)
		{
			mine[x][y] = set + 1;
			cnt--;
		}
(3)套上循环,放进函数内,我们的布雷函数就完成了


void SetMine(char mine[ROWS][COLS], int row, int col, char set)
{
	int x, y;
	int cnt = MineCnt;            //(1) MineCnt 是宏定义,雷的数量

	while (cnt)                    //(2) 没有雷可放了,中止循环
	{
		x = rand() % row + 1;
		y = rand() % col + 1;

		if (mine[x][y] == set)
		{
			mine[x][y] = set + 1;
			cnt--;                    //(3) 每成功放1个雷,就会使cnt -1
		}
	}
}

      


  24.排雷:

                        踩雷 ( 游戏结束 )
                        没踩雷:1.显示周围雷的个数
                                       2.如果周围的雷数是0,自动展开1片

                        我们分为3段写:

(1) 输入坐标,并检查是否越界

		int x, y;
		printf("请输入坐标:");
		scanf("%d%d", &x, &y);         //(1) 输入坐标


		if (x >= 1 && x <= row && y >= 1 && y <= col)    // (2)是否越界?
		{
			//游戏代码暂时屏蔽
		}
		else
			printf("坐标越界\n");
(2) 踩雷了,坐标是'1'(雷)

			if (mine[x][y] == set + 1)        
			{
				printf("很遗憾,你踩雷了!\n");
				DisplayBoard(mine, ROW, COL);     //打印雷区,让玩家死得明白
				break;                            //游戏失败了,跳出循环 ( 退出 )
			}
(3) 没踩雷,1 和 2 封装成函数

        else
		{
			show[x][y] = IsCntMine(mine, x, y);    //(1) 周围雷的个数,返回值赋到 show 棋盘
			
			Open(show, mine, x, y);           //(2) 展开空白区
			
            DisplayBoard(show, ROW, COL);    //(3) 打印 show 棋盘,给玩家反馈实时状况
			cnt++;
		}

(4) 前面有代码出现过 cnt++ ,意思是:如果下棋的次数 = 棋盘 - 所有的雷,那么游戏胜利

    	int cnt = 0;

		if (cnt == row * col - MineCnt)
		{
			printf("排雷成功!\n\n");
			break;
		}

 完整代码 ( 直接粘贴就没有食欲了,这就是要分块讲的原因 ) :

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

	while (1)
	{
		printf("请输入坐标:");
		scanf("%d%d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)    //if 坐标合法
		{
			if (mine[x][y] == set + 1)            //if 踩雷
			{
				printf("很遗憾,你踩雷了!\n");
				DisplayBoard(mine, ROW, COL);
				break;
			}
			else                                    //else 没踩雷
			{
				show[x][y] = IsCntMine(mine, x, y);
				Open(show, mine, x, y);
				DisplayBoard(show, ROW, COL);
				cnt++;
			}
		}
		else                                        //else 坐标越界
			printf("坐标越界\n");


		if (cnt == row * col - MineCnt)            //排光所有雷,游戏胜利
		{
			printf("排雷成功!\n\n");
			break;
		}
	}
}

 


 241.子函数 ( 计算附近的雷 ):

        为什么雷区设置的是'0'和'1',而不是其他字符?
        因为数字减去字符'0',就是数字本身 ( ASCII码值 )
        所以,把附近一圈 ( 8个坐标 ) 加起来,再删除8个 字符' 0 '.就是我们所需要的雷的个数

fx(1)计算附近有几个雷,返回雷的个数,如果没有雷 ( 计算结果是‘0’ ) 则返回空格


int IsCntMine(char mine[ROWS][COLS], int x, int y)
{
	int n =    //除了自身之外的,8个坐标的和
		mine[x - 1][y - 1] +
		mine[x - 1][y + 1] +
		mine[x - 1][y] +

		mine[x][y - 1] +
		mine[x][y + 1] +

		mine[x + 1][y - 1] +
		mine[x + 1][y + 1] +
		mine[x + 1][y] -
		8 * '0' +			// - 8个'0': 雷数量(整形)
		'0';				// + '0':  雷数量(字符形式)

	if (n == '0')
		return ' ';
	else
		return n;
}

 


 242.子函数 ( 展开1圈 ):

        若xy不是雷, 则展开附近1圈, 数值为0的棋盘

fx(2) 如果当前坐标是空的,那么周围1圈 ( 8个格子 )再调用附近有几个雷的函数


int Open(char show[ROWS][COLS], char mine[ROWS][COLS],int x, int y)
{
	if (show[x][y] == ' ')
	{
		show[x - 1][y - 1] = IsCntMine(mine, x - 1, y - 1);
		show[x - 1][y + 1] = IsCntMine(mine, x - 1, y + 1);
		show[x - 1][y] = IsCntMine(mine, x - 1, y);

		show[x][y - 1] = IsCntMine(mine, x, y - 1);
		show[x][y + 1] = IsCntMine(mine, x, y + 1);

		show[x + 1][y - 1] = IsCntMine(mine, x + 1, y - 1);
		show[x + 1][y + 1] = IsCntMine(mine, x + 1, y + 1);
		show[x + 1][y] = IsCntMine(mine, x + 1, y);
	}
	return 0;
}

 



好啦,大功告成!那么我们测试一下

老实说,无法递归性的展开一片,对玩家来说非常的不友好,以至于我找雷都得好多步才找出来, 我自己都不太想玩,就这么滴把!还是很垃圾,得继续加油!

【3】代码分享

        test.c        ( 主程序 )

#define _CRT_SECURE_NO_WARNINGS
#include"game.h"

void game()
{
	char show[ROWS][COLS];					// 展示的棋盘
	char mine[ROWS][COLS];					// 布雷的棋盘
	char set = '0';							// 雷的字符

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


	DisplayBoard(show, ROW, COL);			//展示棋盘
	SetMine(mine, ROW, COL,set);			//设置雷
	FindMine(show, mine, ROW, COL,set);		//排雷(内置循环,输赢/是否继续,在内部判断)
}




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




void test()
{
	int input = 0;
	do
	{
		menu();
		printf("请选择(1/0):	");
		scanf("%d", &input);

		switch (input)
		{
		case 1:
			printf("进入游戏\n\n");
			game();
			break;
		case 0:
			printf("退出游戏\n\n");
			break;
		default:
			printf("选择错误\n\n");
			break;
		}
	} while (input);
}



int main()
{
	srand((unsigned int)time(NULL));
	test();
	return 0;
}

        game.c        ( 游戏的主要功能  >  都集成于此 )

#define _CRT_SECURE_NO_WARNINGS
#include"game.h"

//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
	int i, j;

	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, j;

	printf("  ");
	for (i = 1; 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, char set)
{
	int x, y;
	int cnt = MineCnt;


	while (cnt)
	{
		x = rand() % row + 1;	 //(0~8)+1   =   1~9
		y = rand() % col + 1;


		if (mine[x][y] == set)
		{
			mine[x][y] = set + 1;
			cnt--;
		}
	}
}




//附近有几个雷?
int IsCntMine(char mine[ROWS][COLS], int x, int y)
{
	int n = 
		mine[x - 1][y - 1] +
		mine[x - 1][y + 1] +
		mine[x - 1][y] +

		mine[x][y - 1] +
		mine[x][y + 1] +

		mine[x + 1][y - 1] +
		mine[x + 1][y + 1] +
		mine[x + 1][y] -
		8 * '0' +			//-8set:雷数量(整形)
		'0';				//+'0':雷数量(字符形式)

	if (n == '0')
		return ' ';
	else
		return n;
}





//展开空白区域,再检查附近1圈 ( 8个格子 ) 分别有几个雷
int Open(char show[ROWS][COLS], char mine[ROWS][COLS],int x, int y)
{
	if (show[x][y] == ' ')
	{
		show[x - 1][y - 1] = IsCntMine(mine, x - 1, y - 1);
		show[x - 1][y + 1] = IsCntMine(mine, x - 1, y + 1);
		show[x - 1][y] = IsCntMine(mine, x - 1, y);

		show[x][y - 1] = IsCntMine(mine, x, y - 1);
		show[x][y + 1] = IsCntMine(mine, x, y + 1);

		show[x + 1][y - 1] = IsCntMine(mine, x + 1, y - 1);
		show[x + 1][y + 1] = IsCntMine(mine, x + 1, y + 1);
		show[x + 1][y] = IsCntMine(mine, x + 1, y);
	}

	return 0;
}




//排雷
void  FindMine(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col, char set)
{
	int x, y;
	int cnt = 0;

	while (1)
	{
		printf("请输入坐标:");
		scanf("%d%d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (mine[x][y] == set + 1)
			{
				printf("很遗憾,你踩雷了!\n");
				DisplayBoard(mine, ROW, COL);
				break;
			}
			else
			{
				show[x][y] = IsCntMine(mine, x, y);

				//空白展开
				Open(show, mine, x, y);

				DisplayBoard(show, ROW, COL);
				cnt++;
			}
		}
		else
			printf("坐标越界\n");


		if (cnt == row * col - MineCnt)
		{
			printf("排雷成功!\n\n");
			break;
		}
	}
}

        game.h        ( 头文件,game.c的函数声明,宏定义,引用库函数 )

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

//下棋的有效范围
#define ROW 9
#define COL 9

//棋盘大小
#define ROWS ROW+2
#define COLS COL+2

//雷的数量
#define MineCnt 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 mine[ROWS][COLS], int row, int col, char set);

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

88,谢读,有缘再见 ~

Guess you like

Origin blog.csdn.net/qq_29742853/article/details/121311344