文章目录
步骤实现
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、可以改进的地方
- 胜利条件:有一种是在每个雷上插了旗帜就胜利了;
- 地图大小:将宏改为99会执行不了,改了20可以执行,不知道是不是电脑原因;
- 雷的个数:想要增加难度可以多埋雷,而且雷的生成算法可以优化(比如不让雷相隔生成);
- 游戏体验:可以配个音乐啥的(笔者还是小白,所以不太会);
一些可以改进的地方先说到这,以后有机会再来添加,笔者以后可能会发改进后的代码。