综合练习:扫雷

目录

一、写游戏代码思路:

二、全部代码讲解:

①test.c文件中:

②game.h文件中:

③game.c文件中:

三、实现代码:

①test.c文件中:

②game.h文件中:

③game.c文件中:

四、实现效果:



游戏说明:

第一次点击不会是雷。格子里的数字表示它周围(8个格子中)有几个雷。游戏目标是找出所有雷。“触雷” 则输,排查完所有没有雷的地方即成功。

一、写游戏代码思路:

1、首先基础思路:

(1)设置棋盘(例如在9*9的棋盘上布置10个雷),

(2)布置雷,

(3)存储雷。

2、开始设想:

设置两个一模一样的棋盘:

一个①(mine数组)棋盘布置好雷的信息(存储雷放置的位置);

一个②(show数组)棋盘存储排查出的信息(存储周围8个格子有多少雷)。

给玩家展示的是棋盘②数组,展示给自己看的是设置的内部信息①数组。

实际想要实现的是9*9的棋盘效果,而在创建数组的时候需要创建的是11*11的数组,在内存中多开辟了两行两列的空间。

因为创建①mine数组和②show数组的坐标是严格一一对应的,所以两棋盘大小一样

因为要把棋盘②上的信息打印展示出来给玩家看,如看到1说明这个格子周围的其他8个格子中有1个雷。若有雷,把1标记为雷,即在坐标中放进字符1,没有雷的位置放进字符0

棋盘①:mine数组初始还没有放雷的时候希望里面放的都是字符'0'。

为了保持棋盘②的神秘感,没有排查过的格子放置一个*,所以最开始的棋盘②满棋盘上全是'*',排查一个格子就在这个格子上放置一个想要放的数字(排查出周围雷的个数如1没有雷则是0),这里的数字也是字符数字,因为*是符号,所以这个棋盘的二维数组是个字符数组,char类型。

注意:ASCII值也是整数,字符的ASCII值也是整数,如字符3也是整型类型,字符3在底层数据中存的是它的ASCII值,ASCII值就是整数。整型范围比字符类型大很多。

因为①和②个棋盘是对应的关系,所以最好两个都设置成char类型的数组。

3、操作过程中部分代码的实现:

打印棋盘代码:

打印棋盘的时候为了方便查看可以把相应的列号(第几列)和行号(第几行)标出来。

所以:

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 1; i <= row; i++)
	{
		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}//完成一行的打印
		printf("\n");
	}

}

变成:

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

	//列号的打印:
	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");
	}
}

实现效果:

 发现错位:则让列号从0开始

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("\n");
	//因为要在一行之前加上一个行号
	for (i = 1; i <= row; i++)
	{
		printf("%d ", i);//所以行号的打印放在这
		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}//完成一行的打印
		printf("\n");
	}
}

得到正确效果:

而实际上不应该打印mine数组,应该打印show数组,因为show数组打印出来看到的是全*,玩家可以在show数组上选择坐标进行排雷

4、布置雷时,把雷放在mine数组里,此时打印展示show数组。也可以自己检测打印展示mine数组是否成功布置雷的信息(随机生成在何位置是否有雷)。

这里运行显示已经布置好10个雷:

5、排雷时,是在mine数组中找雷,找到雷之后把雷的信息放到show数组中。即在此实现了两个独立功能棋盘的衔接。

这里用到的相关知识:

字符0的ASCII值是48;

字符1的ASCII值是49;

字符2的ASCII值是50;

2+‘0’=50(2+字符0,即2+字符0的ASCII值)而50是字符2的ASCII值。

所以:

2+‘0’=50=‘2’;

所以:

数字+'0'='数字';

数字加上字符‘0’可以转化为对应的数字字符

数字——+'0'——>数字字符

数字字符——-'0'——>数字

再如:5+'0'='5',则数字n+'0'='n',‘n’-0=n。

排雷排好后: 

若想玩就可以在布置雷处把DisplayBoard(mine, ROW, COL)这行注释掉,实现DisplayBoard(show, ROW, COL)。

二、全部代码讲解:

①test.c文件中:

#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
	printf("*************************\n");
	printf("*****    1.play    ******\n");
	printf("*****    0.exit    ******\n");
	printf("*************************\n");
}
void game()
{
//0、创建棋盘(数组)
	//printf("扫雷\n");
	//创建的数组是11*11,暂时初始化为全0
	char mine[ROWS][COLS] = { 0 };//mine数组存放的是布置好的雷的信息
	char show[ROWS][COLS] = { 0 };//show数组存放的是排查出雷的信息

	//mine数组还没有放雷的时候希望里面放的都是字符0(因为是字符数组)
	//show数组希望最开始放的全部是*
	
//1、初始化棋盘(数组)
	//初始化mine数组为全'0'
	//初始化show数组为全'*'
	//初始化棋盘的函数:InitBoard()函数
	InitBoard(mine, ROWS, COLS, '0');//意思是:InitBoard()函数把mine数组里的ROWS行和COLS列的内容全部初始化为字符0
	InitBoard(show, ROWS, COLS, '*');//意思是:InitBoard()函数把show数组里的ROWS行和COLS列的内容全部初始化为字符*
	//尽量做到实现同类型功能用一个函数就能搞定,InitBoard()函数里依次是:数组名,行、列,要初始化的内容

//2、打印棋盘
	//打印mine数组和show数组,因为这两个数组类型一样(char),行和列一样,所以写一个函数就可以搞定了
	//这里只需要打印9*9棋盘即可,大一圈的11*11在防止越界的时候才用得到
	//DisplayBoard(mine, ROW, COL);//这里操作的是9*9
	//DisplayBoard(show, ROW, COL);
	//这里只是打印出来看一看

//3、布置雷
	//如在棋盘上放置10个雷,用SetMine()函数来设置雷
	//把雷放在mine数组里,布置到中间的9*9的格子中
	SetMine(mine, ROW, COL);
	//DisplayBoard(mine, ROW, COL);//这里把布置好的雷打印出来看一下
	DisplayBoard(show, ROW, COL);//打印出来看一下

//4、排雷
	//即是在mine数组中找雷,找到雷之后把雷的信息放到show数组中,即涉及两个数组,而且这两个数组都是9行9列
	FindMine(mine, show, ROW, COL);//注意这里FindMine()函数的参数形式

}
void test()
{
	int input = 0;
	srand((unsigned int)time(NULL));
	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;
}

②game.h文件中:

头文件里放的有头文件的包含符号的声明函数的声明这三类。

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ROW 9
#define COL 9
//9行9列
//再定义:
#define ROWS ROW+2
#define COLS COL+2

#define EASY_COUNT 10

//这里表示:操作的是中间的9*9,但实际数组创建的是11*11,
//所以又创建ROWS和COLS来代表11行和11列

//1、初始化棋盘的函数声明:
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
//Board的数组是几行几列传过来的这里就用几行几列(是指定数组的行和列的符号)来接收;rows和cols是形参,接收真实的实参,传过来的字符(0或*)用字符变量set接收,(把字符*和字符0传给了set)

//2、打印棋盘的函数声明:
void DisplayBoard(char board[ROWS][COLS],int row,int col);
//虽然只操作9*9,但是show数组传show过来的还是11*11的棋盘(之前已经初始化完数组)

//3、布置雷的函数声明:
void SetMine(char mine[ROWS][COLS], int row, int col);

//4、排查雷的函数声明
void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col);

③game.c文件中:

#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
//1、实现初始化棋盘的函数:
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++)//这两个嵌套的for循环用形参的两个变量来限制访问这个数组的几行几列(11行11列)
		{
			board[i][j] = set;
		}
	}
}

//2、实现打印棋盘的函数:
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	//是想要把board的内容9*9的棋盘打印到屏幕上
	//行是1~9
	//列是1~9

	int i = 0;
	int j = 0;

	//列号的打印:
	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");
	}
}

//3、实现布置雷的函数:
void SetMine(char mine[ROWS][COLS], int row, int col)
{
	int count = EASY_COUNT;//计算布置雷的个数(这里布置的是10),布置的是10个,所以写个循环
	//雷的布置一定是随机布置的(在随机产生的坐标中放进去雷)
	//行坐标x是1~9,列坐标y也是1~9,生成的范围的值也是1~9
	while (count)
	{
		int x = rand() % row + 1;//模9的余数是1~8,+1就是1~9
		int y = rand() % col + 1;
		//布置雷
		//看mine数组x行y列是不是适合布置雷,,先判断坐标处是否已经有雷(因为之前设置的是若有雷,把1标记为雷,即在坐标中放进字符1;没有雷的位置放进字符0)
		if (mine[x][y] == '0')//没有雷则接下来布置雷
		{
			mine[x][y] = '1';//放雷
			count--;
			//这里没有else,如果判断的坐标处已经有雷,则也不count--了,直到count为0,则说明已经放了10个雷了
		}
	}
	//这里运行显示已经布置好10个雷,打印完就把test.c文件中的DisplayBoard(mine, ROW, COL);这行注释掉,不能让别人看到
}

//get_mine_count()函数的实现在这里实现,因为get_mine_count()函数是为了支撑FindMine()函数,只是在FindMine()函数中单独用一下
//别人不需要看到,也不需要暴露出来,所以不需要声明。如果在(如在game.c文件中的某一)函数彻彻底底的不想让别人看到,则可以在函数前面加+static,彻底保护这个函数了
static int get_mine_count(char mine[ROWS][COLS],int x,int y)//找在mine数组的x,y坐标周围有几个雷,算好返回
{
	//在mine数组中:
	//遍历某一个坐标(x,y)周围,判断是雷就+1,写8个if语句,但是这样写比较麻烦
	//因为不是雷放的是0,是雷处放的是1,即一共有几个雷加起来的和就是
	//但是遗憾的是放的是字符1,字符0,不是数字1、数字0
	//所以让每一个元素减去'0',再加起来即可
	//所以是周围8个元素的值加起来减去8*'0',即相当与每个都减了
	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';
}//这里不会越界,因为设计的数字大一圈
//注意这个函数被static修饰之后只能在game.c文件内部使用。

//4、实现排查雷的函数:
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	//在mine数组里查找,查找到之后再写到show数组中去
	//怎么查找呢?——输入坐标
	int x = 0;
	int y = 0;
	int win = 0;

	while (win < row * col - EASY_COUNT)
	{
		printf("请输入要排查的坐标:>");
		scanf("%d %d", &x, &y);

		//注意:这里要检测x和y的合法性
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			//坐标合法,则:
			if (mine[x][y] == '1')
			{
				printf("很遗憾你被炸死了\n");
				//怎么炸死的,显示当时雷的布置情况——死的明白:
				//所以调用Displayboard()函数,把mine数组的row行col列打印出来
				DisplayBoard(mine, row, col);//注意这里传的是row和col不是ROW和COL
				break;
			}//除了被炸死一种结束还有81个格子中放了10个雷,把71个格子排完也就是排雷成功而结束
			else
			{
				//计算x,y坐标周围有几个雷
				//即是mine数组坐标是x,y的位置周围有几个雷
				int n = get_mine_count(mine, x, y);//该函数统计mine数组x,y坐标处周围有几个雷
				//此时应该把n写到另外一个数组中,即由mine数组写到show数组中
                //get_mine_count()函数返回的值是整型数字,是值,要把它转化为字符n才能再传给字符数组show,(show数组本身是字符类型,给它赋值要首先是个字符)
				//show[x][y] = n;//注意这里的是字符n.因为若是整型数字n,则以n为ASCII值打印的字符是不认识的,
				//而如果是字符n,以%c的形式放进去打印出来就是n
				//数字n怎么转化为字符n呢?——加上个'0'即可
				/*show[x][y] = n + '0';*/
				show[x][y] = n + '0';//这里即是把mine数组的雷的信息传给show数组中了,实现了两个独立功能的棋盘的衔接
				//则到这里已经排了一个位置了,可以打印一下查看
				DisplayBoard(show, row, col);//注意这里传的是row和col不是ROW和COL
				//进来一次排雷成功一次
				win++;
			}
			
		}
		else
		{
			printf("输入坐标非法,无法排雷,请重新输入!\n");
		}
	}
	if (win == row * col - EASY_COUNT)
	{
		printf("恭喜你,排雷成功\n");
		//也显示一下雷的情况
		DisplayBoard(mine, row, col);//注意这里传的是row和col不是ROW和COL
	}

}

三、实现代码:

①test.c文件中:

#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
	printf("*************************\n");
	printf("*****    1.play    ******\n");
	printf("*****    0.exit    ******\n");
	printf("*************************\n");
}
void game()
{
	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);
}
void test()
{
	int input = 0;
	srand((unsigned int)time(NULL));
	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;
}

②game.h文件中:

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

③game.c文件中:

#define _CRT_SECURE_NO_WARNINGS 1
#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;
		}
	}
}

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("\n");
	for (i = 1; i <= row; i++)
	{
		printf("%d ", i);
		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
}

void SetMine(char mine[ROWS][COLS], int row, int col)
{
	int count = EASY_COUNT;
	while (count)
	{
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (mine[x][y] == '0')
		{
			mine[x][y] = '1';
			count--;
		}
	}
}

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

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");
				DisplayBoard(mine, row, col);
				break;
			}
			else
			{
				int n = get_mine_count(mine, x, y);
				show[x][y] = n + '0';
				DisplayBoard(show, row, col);
				win++;
			}
		}
		else
		{
			printf("输入坐标非法,无法排雷,请重新输入!\n");
		}
	}
	if (win == row * col - EASY_COUNT)
	{
		printf("恭喜你,排雷成功\n");
		DisplayBoard(mine, row, col);
	}
}

四、实现效果:

1、一种结果:

另一种结果:

(在game.h文件把#define EASY_COUNT 10改成#define EASY_COUNT 80,再在test.c文件中注释掉DisplayBoard(mine, ROW, COL);而不注释DisplayBoard(show, ROW, COL);

注意:

1)、传的实参数组名无论mine还是show数组,用来接收的形参变量用的都是一个board数组名

(尽量做到实现同类型功能用一个函数就能搞定)

而在布置雷的时候,是void SetMine(char mine[ROWS][COLS], int row, int col),特定是在mine数组中布置。

在排雷时:void FineMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);传的是两个数组,所以用两个数组接收。

2)、只有在创建棋盘和初始化棋盘的时候用的是(传的实参变量是)ROWS和COLS,在打印棋盘、布置雷、排雷时用的是(传的实参变量是)ROW和COL。

附加内容:

实现完整版扫雷游戏:(递归的应用)

 扫雷游戏有一个功能是:会有展开一片的效果

要想实现这个功能需要的条件:1、该坐标处不是雷;2、该坐标周围也没有雷;3、该坐标没有被排查过。

一个坐标不是雷,这个坐标周围的8个格子没有雷,看该格子周围8个坐标展开的坐标没有雷,……(从一个点(某一个坐标)往外散,爆炸式往外扩展),若其中展开的有一个坐标的周围有雷,这个坐标显示1(或其他数字)就不需要爆炸式向外展开了,就停下来了。

该坐标周围的8个坐标各自展开排查雷时需要条件:排查过的坐标(已经排查过这个坐标没有雷)就不要再展开了(否则会形成死递归:向外扩展,又回来),所以把排查过的坐标的内容改一下,

比如设置为空格‘ ’,即标记起来。

当满足上述3个条件时,才开始递归展开去排查。

整体思路:

比如:排查时发现(4,4)这个坐标处不是雷,这个坐标周围没有雷,所以把(4,4)这个坐标设置为空格,(意味着该坐标已经被排查过),然后:因为这个坐标处不是雷,这个坐标周围没有雷,这个坐标没有被排查过这3个条件要再展开依次去排查(4,4)周围的8个坐标;

由一个坐标牵扯到周围8个坐标的展开,即形成了递归的效果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值