扫雷项目的详细讲解

步骤实现

1、准备工作

我们所需要使用的扫雷图片:
网盘链接:https://pan.baidu.com/s/1IYrbecnL8mmO_KLurLOqjQ
提取码:1vor

1、定义需要使用数据

#include<iostream>
#include<easyx.h>    //easyx图形库,用于插图之类的活动
using namespace std;
#include<time.h>     //后面需要用到随机数,现在可以不管,等看到后面就知道了

#define MAP_SIZE 10//宏定义地图大小

struct nodeMap  //地图中的各个点
{
	int val;//对应地图的里面值
	int x, y;//对应地图里面的坐标
	int sign;//标记,用于右键点击是的标记
};
int map[MAP_SIZE][MAP_SIZE] = { 0 };//定义MAP_SIZE*MAP_SIZE的地图,并且初始化
nodeMap myMap[MAP_SIZE][MAP_SIZE] = { 0 };

2、加载需要插入的图片

//加载所需要使用的图片
IMAGE img[14]; //一共需要十四张图片
void initImg() //初始化加载图片
{
	char str[30] = { 0 };        //定义一个足以存放相对路径大小的字符数组
	for (int i = 0; i < 14; i++)
	{
        //我所使用的图片在相对路径的res文件夹下,并且名称就是1.png、2.png之类的
        //图片名称对应的图片在画图的那一段注释出来了
		sprintf_s(str, "res/%d.png", i); 
        //&img[i]表示载入图片所存放的地方,str是图片所在路径,50表示图片的长和宽
		loadimage(&img[i], str, 50, 50);      
	}
}

2、棋盘(地图)的一系列操作

1、初始化第一层图层

//我们用-1这个数字表示这个数组相应位置是雷
//我们用0表示这个地方没有被翻开过,即没有被点击
void initMap()
{
	int x = 0, y = 0;//表示雷的坐标
	for (int i = 0; i < MAP_SIZE;)
	{
        //利用随机数在棋盘中随机产生MAP_SIZE个雷
		x = rand() % MAP_SIZE;  //0~MAP_SIZE - 1  
		y = rand() % MAP_SIZE;  //0~MAP_SIZE - 1
		if (map[x][y] != -1)
		{
			map[x][y] = -1;
			i++;         //产生了雷再加一,就能确保一定产生MAP_SIZE个雷。这个在这种小游戏中很常见!!!
		}
	}
	//雷放入地图之后,让雷的周围一圈+1。扫雷规则:每个数字旁边都有相对于数字的雷数,用这个算法可以显示数字
	for (int i = 0; i < MAP_SIZE; i++)
	{
		for (int j = 0; j < MAP_SIZE; j++)
		{
			if (map[i][j] == -1)
			{
				//遍历雷的周围八个位置
				for (int m = i - 1; m <= i + 1; m++)
				{
					for (int n = j - 1; n <= j + 1; n++)
					{
						//不越界,当前位子不是雷的地方加一
                        //这样就实现了棋盘中数字对应于数组中的数字
						if (m >= 0 && m <= 9 && n >= 0 && n <= 9 && map[m][n] != -1)
						{
							map[m][n]++;
						}
					}
				}
			}
		}
	}
}

2、初始化覆盖图层

//初始化我们的结构体,这个结构体便是用来做覆盖以及翻开操作
void initMyMap()
{
	for (int i = 0; i < MAP_SIZE; i++)
	{
		for (int j = 0; j < MAP_SIZE; j++)
		{
			myMap[i][j].sign = 0;//表示都没有标记过
			myMap[i][j].val = map[i][j]; //将底层图形覆盖
			myMap[i][j].x = i;
			myMap[i][j].y = j;
		}
	}
}

3、数字对应贴图的实现(画图)

在这里就是一个遍历整个数组,然后使用switch在对于数字的地方贴相关的图片

解答一下为什么switch中的myMap值会是MAP_SIZE…而不是0…,因为我们需要在点击之后再将图片翻开,而获取鼠标消息之后,对应的数字就会加MAP_SIZE,然后我们再将它贴上。这里看不懂没关系,等看到获取鼠标消息的时候再回来看。

void drawMap()
{
	for (int i = 0; i < MAP_SIZE; i++)
	{
		for (int j = 0; j < MAP_SIZE; j++)
		{
			switch (myMap[i][j].val)
			{
            //img[0]表示空地
			case MAP_SIZE:
				putimage(j * 50, i * 50, &img[0]);
				break;
            //img[1]~img[8]表示对于数字
			case 1+MAP_SIZE:
				putimage(j * 50, i * 50, &img[1]);
				break;
			case 2+MAP_SIZE:
				putimage(j * 50, i * 50, &img[2]);
				break;
			case 3+MAP_SIZE:
				putimage(j * 50, i * 50, &img[3]);
				break;
			case 4+MAP_SIZE:
				putimage(j * 50, i * 50, &img[4]);
				break;
			case 5+MAP_SIZE:
				putimage(j * 50, i * 50, &img[5]);
				break;
			case MAP_SIZE+6:
				putimage(j * 50, i * 50, &img[6]);
				break;
            case MAP_SIZE+7:
				putimage(j * 50, i * 50, &img[7]);
				break;
			case MAP_SIZE+8:
				putimage(j * 50, i * 50, &img[8]);
				break;
            //img[9]表示地雷
			case MAP_SIZE-1:
				putimage(j * 50, i * 50, &img[9]);
				break;
            //这个123和321是随便取的,用来做右键标记,img[11]表示旗帜,img[10]表示问号
			case MAP_SIZE+111:
				putimage(j * 50, i * 50, &img[11]);
				break;
			case MAP_SIZE+110:
				putimage(j * 50, i * 50, &img[10]);
				break;
            //111是随便取的,用来记录被点到的雷,img[13]是被点到的雷的图片
            case MAP_SIZE+113:
                putimage(j * 50, i * 50, &img[13]);
				break; 
            //img[12]表示为被点击的空白
			default:
				putimage(j * 50, i * 50, &img[12]);
				break;
			}
		}
	}
}

3、获取鼠标消息

右键的消息在我们的结构体中我们设置了一个sign用来和鼠标右键连接。

void overeturn(int,int);

//在点到雷之后,我们需要把所有雷都翻开
void show()
{
	for (int i = 0; i < MAP_SIZE; i++)
	{
		for (int j = 0; j < MAP_SIZE; j++)
		{
			if (myMap[i][j].val == -1)//将雷翻开
			{
				myMap[i][j].val += MAP_SIZE;
			}
		}
	}
}
void play()
{
	int x = 0, y = 0;
	MOUSEMSG msg = GetMouseMsg();//获取鼠标消息,easyx.h中的一个函数,msg是我们获得的鼠标消息结构体
	switch (msg.uMsg)           //.uMsg就是当前鼠标消息
	{
	case WM_LBUTTONDOWN:          //当前鼠标消息为左键
		x = msg.y / 50;
		y = msg.x / 50;
		if (myMap[x][y].val >= MAP_SIZE)break;  // 已经被翻开的地方不能再用左键点击
		else if (myMap[x][y].val == -1)  //当你左键点到雷的时候,会弹出失败窗口
		{
            myMap[x][y].val = MAP_SIZE+113;   //将这个被点到的雷单独处理
			show();
			drawMap();
			//MessageBox弹窗  
			//MB_YESNO  有两个按钮   是和否
			if (MessageBox(GetHWnd(), "失败!!!是否重新开始", "是", MB_YESNO) == IDYES)
			{
				memset(map, 0, MAP_SIZE * MAP_SIZE * 4);//把数组map全部初始化为0
                //重新初始化地图,重开
				initMap();                  
				initMyMap();
				drawMap();
			}
			else
			{
				exit(0);//退出程序
			}
		}
		else if (myMap[x][y].val == 0)
		{
			myMap[x][y].val += MAP_SIZE;
			overeturn(x, y);          //当左键点击的是空地0的时候,会显示一连串的地方
		}
		else
		{
			myMap[x][y].val += MAP_SIZE;
		}
		break;
	case WM_RBUTTONDOWN:           //当前鼠标消息为右键点击
        //将鼠标所在的坐标转化为数组中的位置
		x = msg.y / 50;    //利用了整型除以整型会取整的特性          
		y = msg.x / 50;
		if (myMap[x][y].val < 9 || myMap[x][y].val>100)//没有翻开过(也就是显示空白)就可以被标记
		{
			if (myMap[x][y].sign == 0)//当这个地方是没有做标记的时候
			{
				myMap[x][y].val = MAP_SIZE+111;//表示旗帜
				myMap[x][y].sign++;//1
			}
			else if (myMap[x][y].sign == 1)//当这个地方被标记过一次的时候
			{
				myMap[x][y].val = MAP_SIZE+110;//表示问好
				myMap[x][y].sign++;//2
			}
			else if (myMap[x][y].sign == 2)//当这个地方被标记了两次之后
			{
				myMap[x][y].val = map[x][y];
				myMap[x][y].sign = 0;//标记了两次我们需要让其恢复为空白地方
			}
		}
		break;
	}
}
//代表周围八个方向
int dir[8][2] = {{ 0, 1 },{ 0, -1 },{ 1, 1 },{ 1, -1 },{ 1, 0 },{ -1, 0 },{ -1, 1 },
	{ -1, -1 }
};
void overeturn(int x, int y)
{
/*这里我们实现的算法是:利用递归,先对遍历空地0的周围,当遍历到的地方是数字1~9,不是0的时候直接将其翻开,当遍历到的地方依旧是空地0,这递归调用该函数*/
	for (int i = 0; i < 8; i++)
	{
		if (myMap[x][y].val != -1)
		{
            //dx、dy表示周围某一个方向的坐标
			int dx = dir[i][1] + x;   
			int dy = dir[i][0] + y;  
			if (dx < 0 || dx >= MAP_SIZE || dy < 0 || dy >= MAP_SIZE)//如果越界这个地方就不需要判断
				continue;
			if (myMap[dx][dy].val == 0)//翻的是空地0的地方就递归
			{
				myMap[dx][dy].val += MAP_SIZE;
				overeturn(dx, dy);
			}
			else if (myMap[dx][dy].val < 9 && myMap[dx][dy].val != -1)//翻的不是空地,则直接翻开即可
			{
				myMap[dx][dy].val += MAP_SIZE;
			}
		}
	}
}

4、胜利的判断

void isWin()
{
    //sum用来记录剩余未翻开的个数
	int sum = 0;
	for (int i = 0; i < MAP_SIZE; i++)
	{
		for (int j = 0; j < MAP_SIZE; j++)
		{
            //被翻开的地方都会加MAP_SIZE,而被标记的地方大于MAP_SIZE+100
			if (myMap[i][j].val < 9 || myMap[i][j].val>MAP_SIZE + 100)
			{
				sum++;
			}
		}
	}
	if (sum == MAP_SIZE)
	{
		//游戏胜利
        if (MessageBox(GetHWnd(), "胜利!!!是否再来一遍", "是", MB_YESNO) == IDYES)
		{
			memset(map, 0, MAP_SIZE * MAP_SIZE * 4);//把数组map全部初始化为0
            //重新开始
			initMap();
			initMyMap();
			drawMap();

		}
		else
		{
			exit(0);//退出程序
		}
	}
}

5、主函数

int main()
{
	initgraph(MAP_SIZE * 50, MAP_SIZE * 50);//初始化画布
	srand((unsigned)time(NULL));//随机数种子
	initMap();
	initMyMap();
	initImg();
	while (1)
	{
		play();
		drawMap();
        isWin();
	}
	closegraph();
	return 0;
}

6、可以改进的地方

  1. 胜利条件:有一种是在每个雷上插了旗帜就胜利了;
  2. 地图大小:将宏改为99会执行不了,改了20可以执行,不知道是不是电脑原因;
  3. 雷的个数:想要增加难度可以多埋雷,而且雷的生成算法可以优化(比如不让雷相隔生成);
  4. 游戏体验:可以配个音乐啥的(笔者还是小白,所以不太会);

一些可以改进的地方先说到这,以后有机会再来添加,笔者以后可能会发改进后的代码。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值