【C语言】从扫雷玩家到开发者

创作不易,欢迎  关注  点赞  收藏  留言
水平有限,不足之处,望友人指出
博主介绍:一枚不知所措的大学生
博客首页:Gredot
所属专栏《C语言》

效果展示

【C语言】从扫雷玩家到开发者_第1张图片
  雷区可以连续展开
【C语言】从扫雷玩家到开发者_第2张图片

一、扫雷规则介绍

相信大家都玩过扫雷吧,这里简单介绍一下扫雷的规则:
  点击某个位置,如果这个位置有雷,玩家就会被炸,展示雷盘中所有雷的位置,游戏结束,如果这个位置没有雷,且以这个位置为中心的九宫格也没有雷,则这个位置被翻开,若以这个位置为中心的九宫格有雷,则统计以这个位置为中心的九宫格中雷的个数,并且显示在九宫格的中心位置,点开所有非雷位置则游戏胜利,在途中点到雷则游戏失败。

二、扫雷的设计思路

  思路:使用EasyX(上期有介绍)图形库实现;口和鼠标点击操作,加上图片素材进行贴图,实现扫雷界面。通过图片素材和游戏数据的交互实现游戏效果。项目采用多文件联合编程,让整个项目的框架更加清晰,包括game.h存放头文件,函数声明等文件,game.cpp存放项目所需函数的定义(实现),test.cpp中为主函数。

三、扫雷框架介绍

  1. 创建二位数组开辟地图(默认10*10)初始化为0
  2. 使用随机数给地图布置雷场默认10颗雷(-1为雷)
  3. 对用户操作进行响应
  4. 响应用户操作(判断游戏是否结束)
  5. 若踩雷,展示所有雷的位置,游戏结束;若没有踩雷则在显示以这个位置为中心的九宫格所有雷数和,并继续游戏继续扫雷
  6. 循环对用户操作进行相应
  7. 若非雷区全部操作完,用户未点击到雷,则判为用户赢

接下来让我们顺着这个思路对扫雷游戏进行实现吧。

四、扫雷游戏的实现

  由于受Easy X图形库限制,我们创建的源文件后缀要为 .cpp,接下来我们创建本次项目的源文件和头文件。
【C语言】从扫雷玩家到开发者_第3张图片【C语言】从扫雷玩家到开发者_第4张图片
【C语言】从扫雷玩家到开发者_第5张图片【C语言】从扫雷玩家到开发者_第6张图片
  至此,项目就创建完成了。接下来正式开始实现扫雷,准备好了吗?

1.头文件

  首先将这次项目所要用到的头文件定义到game.h中,然后再game.cpp和test.cpp中引用game.h库,注意这里应用本地的库使用 " "

#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
#include
#include

  然后将常用的常量采用宏定义方式定义到game.h中

#define ROW 10
#define COL 10
#define IMGW 40

  接下来正式进入扫雷逻辑的实现。
  通过对游戏地图的理解我们不难得出结论:我们可以使用二位数组来存放游戏数据,并将0作为初始状态,-1作为雷。

//定义地图
int map[ROW][COL] = {0};
//ROW和COL为宏定义常量10,默认地图十行十列

2.游戏的图的打印

  接下来我们封装函数对地图进行打印,注意我们采用多文件联合编程,将函数的定义(实现)放到game.cpp中,在game.h进行声明

//声明
void showMap(int map[ROW][COL];
void showMap(int map[ROW][COL])
{
	//使用嵌套循环遍历数组
	for (int i = 0; i < ROW; i++)
	{
		for (int k = 0; k < COL; k++)
		{
			printf("%2d ", map[i][k]);//%2d让数据进行右对齐
		}
		printf("\n");
	}
	printf("\n");
}

3.雷区的设置

  下面我们给地图中随机埋雷(默认10颗,用-1表示),这里要用到随机数,srand函数和rand函数,需要引time.h和stdlib.h,随机数的生成这里不过多赘述。

	//设置随机数种子
	srand((unsigned int)time(NULL));
	//给地图里随机埋雷,雷用-1表示
	for (int i = 0; i < 10; )
	{
		int r = rand() % 10;  //[0,10)
		int c = rand() % 10;
		//避免布置雷的坐标相同(去重)
		if (map[r][c] == 0)
		{
			map[r][c] = -1;
			i++;
		}
	}

  我们将这个功能封装成initMap函数,在game.cpp中进行声明,在test.cpp中的主函数中调用即可

initMap(map);

  接下来我们实现一个逻辑,统计格子为中心九宫格内的雷的数量
  先遍历二维数组,在遍历九宫格,就可以统计到以该各自为中心的九宫格中所有雷的数量了,为防止数组越界,我们加上一点条件判断。
【C语言】从扫雷玩家到开发者_第7张图片
  并且将这个功能封装到showMap函数中

void initMap(int map[ROW][COL])
{
	//每次游戏前重置map  (置0)
	 cnt = 0;
	memset(map, 0, ROW * COL * sizeof(int));
	//给地图里随机埋雷,雷用-1表示
	for (int i = 0; i < 10; )
	{
		int r = rand() % 10;  //[0,10)
		int c = rand() % 10;
		//避免布置雷的坐标相同(去重)
		if (map[r][c] == 0)
		{
			map[r][c] = -1;
			i++;
		}
	}
	//雷所在的九宫格所有的格子的数值加一  (雷除外)  遍历地图
	for (int i = 0; i < ROW; i++)
	{
		for (int k = 0; k < COL; k++)
		{
			//遍历雷所在的九宫格
			if (map[i][k] == -1)
			{
				for (int r = i - 1; r <= i + 1; r++)
				{
					for (int c = k - 1; c <= k + 1; c++)
					{
						//边界九宫格可能会越界(数组越界问题)
						if ((c >= 0 && c < ROW) && (r >= 0 && r < COL) && (map[r][c] != -1))
						{
							map[r][c]++;
						}
					}
				}
			}
		}
	}
}

4.游戏数据和图像的交互

  下面我们将数据和图形界面进行交互,素材放在结尾自取

//把数据转成对应的图形界面
	IMAGE img[12];//定义图片数组
	//加载图片
	for (int i = 0; i < 12; i++)
	{
		char fileName[50] = { 0 };
		sprintf(fileName, "./images/%d.jpg", i);
		loadimage(img + i, fileName, IMGW, IMGW);
	}
	//把数组里面的数据转换成对应的图片,贴图贴到图形窗口上
	while(true)
	{
		mouseEvent(map, img);
		drawMap(map, img);
		if (isOver)
		{
			int ret =MessageBox(GetHWnd(), "你踩到雷了,再来一把?", "提示:", MB_OKCANCEL);
			if (ret == IDOK)
			{
				initMap(map);
				showMap(map);
				isOver = false;
			}
			else if(ret == IDCANCEL)
			{
				exit(666);
			}
		}
	}

  为了更好的交互,我们对数据进行了加密操作,给二维数组中的元素全部+20(这个功能也封装到initMap中),这样便于我们处理图片、数据、和鼠标操作之间的交互,至此initMap函数就已经完成了;

void initMap(int map[ROW][COL])
{
	//每次游戏前重置map  (置0)
	 cnt = 0;
	memset(map, 0, ROW * COL * sizeof(int));
	//给地图里随机埋雷,雷用-1表示
	for (int i = 0; i < 10; )
	{
		int r = rand() % 10;  //[0,10)
		int c = rand() % 10;
		//避免布置雷的坐标相同(去重)
		if (map[r][c] == 0)
		{
			map[r][c] = -1;
			i++;
		}
	}

	//雷所在的九宫格所有的格子的数值加一  (雷除外)  遍历地图
	for (int i = 0; i < ROW; i++)
	{
		for (int k = 0; k < COL; k++)
		{
			//遍历雷所在的九宫格
			if (map[i][k] == -1)
			{
				for (int r = i - 1; r <= i + 1; r++)
				{
					for (int c = k - 1; c <= k + 1; c++)
					{
						//边界九宫格可能会越界(数组越界问题)
						if ((c >= 0 && c < ROW) && (r >= 0 && r < COL) && (map[r][c] != -1))
						{
							map[r][c]++;
						}
					}
				}
			}
		}
	}
	//把所有的格子用遮罩图盖起来,给每个格子都加个密,+20
	for (int i = 0; i < ROW; i++)
	{
		for (int k = 0; k < COL; k++)
		{
			map[i][k] += 20;
		}
	}
}

  我们需要将我们的素材放到项目对应的文件夹中
【C语言】从扫雷玩家到开发者_第8张图片【C语言】从扫雷玩家到开发者_第9张图片【C语言】从扫雷玩家到开发者_第10张图片
  下面封装drawMap函数使数据和图片进行交互

void drawMap(int map[ROW][COL], IMAGE img[])
{
	for (int i = 0; i < ROW; i++)
	{
		for (int k = 0; k < COL; k++)
		{
			//空白格子和数字
			if (map[i][k] >= 0 && map[i][k] <= 8)
			{
				putimage(k * IMGW, i * IMGW, img + map[i][k]);

			}//雷
			else if (map[i][k] == -1)
			{
				putimage(k * IMGW, i * IMGW, img + 9);
			}
			else if (map[i][k] >= 19 && map[i][k] <= 28)
			{
				putimage(k * IMGW, i * IMGW, img + 10);
			}
			else if (map[i][k] > 30)
			{
				putimage(k * IMGW, i * IMGW, img + 11);
			}
		}
	}
}

5.鼠标点击操作的处理

void mouseEvent(int map[ROW][COL], IMAGE img[])
{
	//定义消息结构体(鼠标,按键,字符消息)
	ExMessage msg;
	if (peekmessage(&msg, EX_MOUSE))
	{
		//把鼠标的坐标转成数组的下标
		int r = msg.y / IMGW;
		int c = msg.x / IMGW;
		if (msg.message == WM_LBUTTONDOWN)//按下左键
		{
			if (map[r][c] >= 19 && map[r][c] <= 28)
			{
				map[r][c] -= 20;
				openNull(map, r, c);
				judge(map, r, c);
				showMap(map);
			}
		}
		if (msg.message == WM_RBUTTONDOWN)//按下右键
		{
			if (map[r][c] >= 19 && map[r][c] <= 28) //插旗
			{
				map[r][c] += 20;
			}
			else if (map[r][c] > 30)//取消插旗
			{
				map[r][c] -= 20;
			}
		}
	}
}

6.递归展开

  用递归展开点击区域周围的非雷区,封装函数openNull

void openNull(int map[ROW][COL], int row, int col)
{
	//判断当前点击的是不是空白
	if (map[row][col] == 0)
	{		//遍历九宫格   递归展开
		for (int i = row - 1; i <= row + 1; i++)
		{
			for (int k = col - 1; k <= col + 1; k++)
			{
				if ((i >= 0 && i < ROW) && (k >= 0 && k < COL) && map[i][k] >= 19 && map[i][k] <= 28)
				{
					map[i][k] -= 20;
					openNull(map, i, k);
				}
			}
		}
	}
}

7.游戏结束逻辑

  最后我们判断游戏结束的逻辑,首先,如果展开的数量等于格子总数量减去雷的数量,则游戏胜利,若点击到雷则游戏失败

//判断游戏是否结束
void judge(int map[ROW][COL], int row, int col)
{
	//如果点到了雷,就game over点一个雷,其他的雷都显示出来
	if (map[row][col] == -1)
	{
		for (int i = 0; i < ROW; i++)
		{
			for (int k = 0; k < COL; k++)
			{
				if (map[i][k] == 19)
				{
					map[i][k] -= 20;//  或者直接赋值成-1(雷)
				}
			}
		}
		isOver = true;
	}
	//遍历地图,统计打开过的格子数量
	int cnt = 0;
	for (int i = 0; i < ROW; i++)
	{
		for (int k = 0; k < COL; k++)
		{
			if (map[i][k] <= 8 && map[i][k] >= 0)
				cnt++;
		}
	}

	if (cnt == ROW*COL-10)
	{
		int isok = MessageBox(GetHWnd(), "恭喜你,获胜啦,再来一把?", "提示:", MB_OKCANCEL);
		if (isok == IDOK)
		{
			initMap(map);
			showMap(map);
			isOver = false;
		}
		else
		{
			exit(666);//退出程序
		}
	}
}

五、完整源码及素材

1.game.cpp

#include"game.h"
//声明外部变量
extern int cnt;
extern bool isOver;
//显示地图(数据)+
void showMap(int map[ROW][COL])
{
	for (int i = 0; i < ROW; i++)
	{
		for (int k = 0; k < COL; k++)
		{
			printf("%2d ", map[i][k]);
		}
		printf("\n");
	}
	printf("\n");
}
//初始化地图
void initMap(int map[ROW][COL])
{
	//每次游戏前重置map  (置0)
	 cnt = 0;
	memset(map, 0, ROW * COL * sizeof(int));
	//给地图里随机埋雷,雷用-1表示
	for (int i = 0; i < 10; )
	{
		int r = rand() % 10;  //[0,10)
		int c = rand() % 10;
		//避免布置雷的坐标相同(去重)
		if (map[r][c] == 0)
		{
			map[r][c] = -1;
			i++;
		}
	}

	//雷所在的九宫格所有的格子的数值加一  (雷除外)  遍历地图
	for (int i = 0; i < ROW; i++)
	{
		for (int k = 0; k < COL; k++)
		{
			//遍历雷所在的九宫格
			if (map[i][k] == -1)
			{
				for (int r = i - 1; r <= i + 1; r++)
				{
					for (int c = k - 1; c <= k + 1; c++)
					{
						//边界九宫格可能会越界(数组越界问题)
						if ((c >= 0 && c < ROW) && (r >= 0 && r < COL) && (map[r][c] != -1))
						{
							map[r][c]++;
						}
					}
				}
			}
		}
	}
	//把所有的格子用遮罩图盖起来,给每个格子都加个密,+20
	for (int i = 0; i < ROW; i++)
	{
		for (int k = 0; k < COL; k++)
		{
			map[i][k] += 20;
		}
	}
}
//绘制地图(贴图)
void drawMap(int map[ROW][COL], IMAGE img[])
{
	for (int i = 0; i < ROW; i++)
	{
		for (int k = 0; k < COL; k++)
		{
			//空白格子和数字
			if (map[i][k] >= 0 && map[i][k] <= 8)
			{
				putimage(k * IMGW, i * IMGW, img + map[i][k]);

			}//雷
			else if (map[i][k] == -1)
			{
				putimage(k * IMGW, i * IMGW, img + 9);
			}
			else if (map[i][k] >= 19 && map[i][k] <= 28)
			{
				putimage(k * IMGW, i * IMGW, img + 10);
			}
			else if (map[i][k] > 30)
			{
				putimage(k * IMGW, i * IMGW, img + 11);
			}
		}
	}
}
//鼠标点击响应
void mouseEvent(int map[ROW][COL], IMAGE img[])
{
	//定义消息结构体(鼠标,按键,字符消息)
	ExMessage msg;
	if (peekmessage(&msg, EX_MOUSE))
	{
		//把鼠标的坐标转成数组的下标
		int r = msg.y / IMGW;
		int c = msg.x / IMGW;
		if (msg.message == WM_LBUTTONDOWN)//按下左键
		{
			if (map[r][c] >= 19 && map[r][c] <= 28)
			{
				map[r][c] -= 20;
				openNull(map, r, c);
				judge(map, r, c);
				showMap(map);
			}
		}
		if (msg.message == WM_RBUTTONDOWN)//按下右键
		{
			if (map[r][c] >= 19 && map[r][c] <= 28) //插旗
			{
				map[r][c] += 20;
			}
			else if (map[r][c] > 30)//取消插旗
			{
				map[r][c] -= 20;
			}
		}
	}
}
//展开周围非雷区(递归)
void openNull(int map[ROW][COL], int row, int col)
{
	//判断当前点击的是不是空白
	if (map[row][col] == 0)
	{		//遍历九宫格   递归展开
		for (int i = row - 1; i <= row + 1; i++)
		{
			for (int k = col - 1; k <= col + 1; k++)
			{
				if ((i >= 0 && i < ROW) && (k >= 0 && k < COL) && map[i][k] >= 19 && map[i][k] <= 28)
				{
					map[i][k] -= 20;
					openNull(map, i, k);
				}
			}
		}
	}
}
//判断游戏是否结束
void judge(int map[ROW][COL], int row, int col)
{
	//如果点到了雷,就game over点一个雷,其他的雷都显示出来
	if (map[row][col] == -1)
	{
		for (int i = 0; i < ROW; i++)
		{
			for (int k = 0; k < COL; k++)
			{
				if (map[i][k] == 19)
				{
					map[i][k] -= 20;//  或者直接赋值成-1(雷)
				}
			}
		}
		isOver = true;
	}
	//遍历地图,统计打开过的格子数量
	int cnt = 0;
	for (int i = 0; i < ROW; i++)
	{
		for (int k = 0; k < COL; k++)
		{
			if (map[i][k] <= 8 && map[i][k] >= 0)
				cnt++;
		}
	}

	if (cnt == ROW*COL-10)
	{
		int isok = MessageBox(GetHWnd(), "恭喜你,获胜啦,再来一把?", "提示:", MB_OKCANCEL);
		if (isok == IDOK)
		{
			initMap(map);
			showMap(map);
			isOver = false;
		}
		else
		{
			exit(666);//退出程序
		}
	}
}

2.test.cpp

#include"game.h"
//计数器,用来记录打开的格子的数量
int cnt = 0;

bool isOver = false; 
int main ()
{
	//创建图像窗口
	initgraph(COL*IMGW,ROW*IMGW);

	//设置随机数种子
	srand((unsigned int)time(NULL));

	//定义游戏地图
	int map[ROW][COL] = { 0 };
	//初始化数据
	initMap(map);
	//把数据转成对应的图形界面
	IMAGE img[12];//定义图片数组
	//加载图片
	for (int i = 0; i < 12; i++)
	{
		char fileName[50] = { 0 };
		sprintf(fileName, "./images/%d.jpg", i);
		loadimage(img + i, fileName, IMGW, IMGW);
	}

	//把数组里面的数据转换成对应的图片,贴图贴到图形窗口上
	while(true)
	{
		mouseEvent(map, img);
		drawMap(map, img);
		if (isOver)
		{
			int ret =MessageBox(GetHWnd(), "你踩到雷了,再来一把?", "提示:", MB_OKCANCEL);
			if (ret == IDOK)
			{
				initMap(map);
				showMap(map);
				isOver = false;
			}
			else if(ret == IDCANCEL)
			{
				exit(666);
			}
		}
	}
	//显示地图
	showMap(map);
	return 0;
}

3.game.h

#pragma once

#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
#include
#include

#define ROW 10
#define COL 10
#define IMGW 40

//函数声明
void initMap(int map[ROW][COL]);
void drawMap(int map[ROW][COL], IMAGE img[]);
void mouseEvent(int map[ROW][COL], IMAGE img[]);
void openNull(int map[ROW][COL], int row, int col);
void judge(int map[ROW][COL], int row, int col);
void showMap(int map[ROW][COL]);

部分细节没有介绍到,大家可以自行揣摩,也可以在评论区留言哦。

4.素材连接:

链接:https://pan.baidu.com/s/1wmUwtWYppMFYamWfgUBssg ---------------提取码:GRED

六、结语

  第一次使用Markdown写Blog,感觉真不错!EasyX图形库容易上手,如果你能将这个项目进行复刻,相信你对C/C++中的知识点的理解会更加深刻,为以后的编程学习筑基,小伙伴们加油啊,万丈高楼平地起。
PS:世间一场大梦,人生几度秋凉。学习是一个永无止境的前进的过程,你会越来越享受这个过程!

你可能感兴趣的:(【C语言】,c语言,开发语言,c++)