创作不易,欢迎 关注 点赞 收藏 留言
水平有限,不足之处,望友人指出
博主介绍:一枚不知所措的大学生
博客首页:Gredot
所属专栏《C语言》
相信大家都玩过扫雷吧,这里简单介绍一下扫雷的规则:
点击某个位置,如果这个位置有雷,玩家就会被炸,展示雷盘中所有雷的位置,游戏结束,如果这个位置没有雷,且以这个位置为中心的九宫格也没有雷,则这个位置被翻开,若以这个位置为中心的九宫格有雷,则统计以这个位置为中心的九宫格中雷的个数,并且显示在九宫格的中心位置,点开所有非雷位置则游戏胜利,在途中点到雷则游戏失败。
思路:使用EasyX(上期有介绍)图形库实现;口和鼠标点击操作,加上图片素材进行贴图,实现扫雷界面。通过图片素材和游戏数据的交互实现游戏效果。项目采用多文件联合编程,让整个项目的框架更加清晰,包括game.h存放头文件,函数声明等文件,game.cpp存放项目所需函数的定义(实现),test.cpp中为主函数。
接下来让我们顺着这个思路对扫雷游戏进行实现吧。
由于受Easy X图形库限制,我们创建的源文件后缀要为 .cpp,接下来我们创建本次项目的源文件和头文件。
至此,项目就创建完成了。接下来正式开始实现扫雷,准备好了吗?
首先将这次项目所要用到的头文件定义到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,默认地图十行十列
接下来我们封装函数对地图进行打印,注意我们采用多文件联合编程,将函数的定义(实现)放到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");
}
下面我们给地图中随机埋雷(默认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);
接下来我们实现一个逻辑,统计格子为中心九宫格内的雷的数量
先遍历二维数组,在遍历九宫格,就可以统计到以该各自为中心的九宫格中所有雷的数量了,为防止数组越界,我们加上一点条件判断。
并且将这个功能封装到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]++;
}
}
}
}
}
}
}
下面我们将数据和图形界面进行交互,素材放在结尾自取
//把数据转成对应的图形界面
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;
}
}
}
我们需要将我们的素材放到项目对应的文件夹中
下面封装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);
}
}
}
}
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;
}
}
}
}
用递归展开点击区域周围的非雷区,封装函数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);
}
}
}
}
}
最后我们判断游戏结束的逻辑,首先,如果展开的数量等于格子总数量减去雷的数量,则游戏胜利,若点击到雷则游戏失败
//判断游戏是否结束
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);//退出程序
}
}
}
#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);//退出程序
}
}
}
#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;
}
#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]);
部分细节没有介绍到,大家可以自行揣摩,也可以在评论区留言哦。
链接:https://pan.baidu.com/s/1wmUwtWYppMFYamWfgUBssg ---------------提取码:GRED
第一次使用Markdown写Blog,感觉真不错!EasyX图形库容易上手,如果你能将这个项目进行复刻,相信你对C/C++中的知识点的理解会更加深刻,为以后的编程学习筑基,小伙伴们加油啊,万丈高楼平地起。
PS:世间一场大梦,人生几度秋凉。学习是一个永无止境的前进的过程,你会越来越享受这个过程!