扫雷游戏制作过程
一、创作工具
1、visual studio 2022
2、easyx图形库
二、准备工作
1、图片
2、背景音乐
整合在一个文件夹里,与源文件放在一起,重命名方便操作
三、创作思路
1、设置雷(-1)代表雷,空白格子为0,数字[0-8](雷周围整体加一,可叠加)雷周围数字出现情况:1—8
运用二维数组知识
静态数组int map[ ](不能改变)
动态数组int **map = malloc(10,sizeof(int):
生成的数组中每一个格子装了一个int*的指针
for(int i =0;i<10;i++)
{
map[i] = malloc(10,sizeof(int);
}
以此完成动态数组的申请;优势:可以随时扩容
2、利用easyx图形库函数创建一个窗口,创建一个存储图片数组,将准备好的图片放入指定位置(利用坐标实现)
3、排雷:
鼠标左击消除对应小格,右击标记(可用别的按键实现)
4、判定输赢条件
点到地雷,游戏结束;所有地雷被红旗标记,获得胜利,游戏结束
四、代码实现
头文件设置,宏观设置
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<easyx.h>//图形库
//动态数组分配需要用到的头文件
#include<mmsystem.h>//添加音乐用到的头文件
#pragma comment(lib,"winmm.lib")//取消警告(库文件)
#define SIZE 20//设置图片尺寸大小
#define num 100//设置雷的数量
//加载资源,图片
IMAGE imgs[12];//存储图片数组
加载填充图片
void loadpng()
{
for (int i = 0;i < 12;i++)
{
char buf[50] = { 0 };
sprintf(buf,"./photos/%d.png", i);//使得下面的图片也能跟着变换,注意要用到spintf完成
//用sprintf时1.项目2.c/c++3.常规4.SDL检查5.选择否,软件问题可以去掉
loadimage(imgs + i, buf, SIZE, SIZE);//用这个加图片时,1.项目2.属性3.高级4.字符集5.选则使用多节字符集
}
};
初始化Map,内存申请
struct Map
{
int** arr;//二维数组指针
int rows;//行数
int cols;//列数
};
void map_init(Map* map, int rows, int cols)
{
map->rows = rows;
map->cols = cols;
map->arr = (int**)calloc(map->rows,sizeof(int*));//calloc是malloc的兄弟函数
if (!map->arr)
{
printf("内存申请失败\n");
return;
}
for (int i = 0;i < map->rows;i++)
{
map->arr[i] = (int*)calloc(map->cols, sizeof(int));//(int*)强转为int
if (!map->arr[i])
{
printf("内存申请失败");//一般来说必须返回确认是否成功
return;
}
}
}
设置雷(-1)
//设置雷 -1表示在0处随机设置-1形成雷
void map_setMine(Map* map)
{
//设置随机数种子
srand(time(NULL));//时间头文件
for (int i = 0;i < num;)
{
//随机产生下标[0-10]
int r = rand() % map->rows;
int c = rand() % map->cols;
if (map->arr[r][c] == 0)
{
map->arr[r][c] = -1;//雷设置完成,注意:此过程可能会使-1位置重复,
//导致设置雷小于指定数,解决方法:加循环
i++; //从for里把i++拿出来表示只有成功设置雷,才自增
}
}
//把以雷为中心的九宫格的数字都加一(雷除外)形成数字0-8
for (int i = 0; i < map->rows; i++)
{
for (int k = 0; k < map->cols; k++)
{
if (map->arr[i][k] == -1)
{
//以雷为中心的九宫格
for (int r = i-1; r <= i+1; r++)
{
for (int c = k-1; c <= k+1; c++)
{
//排除掉雷
if ((r >= 0 && r < map->rows && c >= 0 && c < map->cols)
&& map->arr[r][c] != -1)//此时加入边界判断即可恢复代码中断问题
{
map->arr[r][c] += 1;//此时代码有问题,可能会导致代码运行中断,
//因为会越界(雷在边界),九宫格出现行超出列超出的情况
}
}
}
}
}
}
}
输出一下所有数组,用于观察纠错,修正,
添加排查操作,用于检测标记方格是否为雷,如果排查标记出所有雷,则胜利,游戏结束
//输出一下所有数组
void map_show(Map* map)
{
int cir=0;//设置变量
for (int i = 0;i < map->rows;i++)
{
for (int k = 0;k < map->cols;k++)
{
printf(" %2d ", map->arr[i][k]);
if (map->arr[i][k] == 59)//判断标记的红旗是否为雷
{
cir += 1;
if (cir == num)//如果排查出雷的数量与设置雷的数量一致则胜利
{
{
WinWindow();//输出成功窗口,游戏结束
getchar();
}
}
}
printf("\n\n");
}
}
覆盖起来,进行加密操作,将(-1—8)数字加20变为(19—28)进行加密
//覆盖起来,加密操作
void map_cover(Map* map)
{
//让现在数组里的数据变得不一样
for (int i = 0; i < map->rows; i++)
{
for (int k = 0; k < map->cols; k++)
{
map->arr[i][k] += 20;
}
}
}
绘制界面,图形窗口
//绘制界面(图形窗口)
void map_draw(Map* map)
{
for (int i = 0;i < map->rows;i++)
{
for (int k = 0; k < map->cols; k++)
{
//求每个格子的坐标
int x = k * SIZE;
int y = i * SIZE;
if (map->arr[i][k] >= 0 && map->arr[i][k] <= 8)
{
putimage(x, y, imgs + map->arr[i][k]);//放置图片
}
else if (map->arr[i][k] == -1)
{
putimage(x, y, imgs + 11);//防置雷的图片
}//加密后最小为19,最大为28
else if (map->arr[i][k] >= 19 && map->arr[i][k] <= 28)
{
putimage(x, y, imgs + 9);//放置蓝色格子
}
else if (map->arr[i][k] >= 59 && map->arr[i][k] <= 68)
{
putimage(x, y, imgs + 10);//插旗标记
}
if (map->arr[i][k] == -1)
{
DieWindow();//点到雷了,生成游戏结束窗口,游戏结束
getchar();//防止闪屏
}
}
}
}
左击格子打开与右击格子标记插旗,二次加密
void map_mouseMsg(Map* map, ExMessage* msg)//消息参数,用鼠标需要的参数
{
//根据鼠标坐标,获得点击的格子的下标
int r = msg->y / SIZE;
int c = msg->x / SIZE;
//判断鼠标左键是否点击
if (msg->message == WM_LBUTTONDOWN&& map->arr[r][c] >= 19 && map->arr[r][c] <= 68)
{
if (map->arr[r][c] != 59)
{
map->arr[r][c] -= 20;//左击减数字,消除加密;
map_recOpen(map, r, c);
system("cls");
map_show(map);//换进来不需要取地址&
}
}
//判断是否为右键点击
if (msg->message == WM_RBUTTONDOWN && map->arr[r][c] >= 19 && map->arr[r][c] <= 28)
{
map->arr[r][c] += 40;//右键标记雷,插旗子二次加密
map_recOpen(map, r, c);
system("cls");
map_show(map);//换进来不需要取地址&
}
}
用递归法实现:若点击为空白格子则打开周围所有空白格子和数字
//如果点到空白格子,递归打开周围的所有的空白格子和数字格子
void map_recOpen(Map* map,int row,int col)
{
//先判断一下是否为空白格子
if (map->arr[row][col] != 0)
{
return;
}
//延申到以空白格子为中心的九宫格
for (int r = row - 1; r <= row + 1; r++)
{
for (int c = col - 1; c <= col + 1; c++)
{
if ((r >= 0 && r < map->rows && c >= 0 && c < map->cols)
&&map->arr[r][c]>=19&&map->arr[r][c]<=28)
{
map->arr[r][c] -= 20;
map_recOpen(map, r, c);//递归,自己调用自己
}
}
}
}
绘制游戏失败窗口,要用到easyx图形库
void DieWindow()
{
BeginBatchDraw();//双缓冲绘图
initgraph(400, 400);
setbkmode(TRANSPARENT);
settextcolor(LIGHTBLUE);
outtextxy(150, 150, "BOOM!BOOM!");
outtextxy(150, 120, "GameOver");
EndBatchDraw();
}
绘制游戏胜利窗口
void WinWindow()
{
BeginBatchDraw();//双缓冲绘图
initgraph(400, 400);
setbkmode(TRANSPARENT);
settextcolor(LIGHTBLUE);
outtextxy(150, 150, "WOW!COOL!");
outtextxy(150, 120, "恭喜你通关了");
EndBatchDraw();
}
主函数
int main()
{
//创建图形窗口
initgraph(800, 600,EW_SHOWCONSOLE);//长,宽,显示控制台
//播放背景音乐
mciSendString("open ./photos/trip.mp3 alias bgm", NULL, 0, NULL);
mciSendString("play bgm repeat", NULL, 0, NULL);//循环播放
loadpng();
Map map;
map_init(&map,30,40);//行,列
map_setMine(&map);//&取地址
map_cover(&map);
//消息循环
while (true)
{
ExMessage msg = {0};
while (peekmessage(&msg, EM_MOUSE))
{
map_mouseMsg(&map, &msg);
//map_show(&map);
map_draw(&map);
//WinGame(&map,m,n);
}//用while循环因为括号内消息为真则继续循环
}
getchar();
return 0;
}
五、代码整体预览
注意函数调用问题和逻辑问题
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<easyx.h>//图形库
//动态数组分配需要用到的头文件
#include<mmsystem.h>//添加音乐用到的头文件
#pragma comment(lib,"winmm.lib")//取消警告(库文件)
#define SIZE 20//图片尺寸
#define num 10//雷的数量
//加载资源,图片
IMAGE imgs[12];//存储图片数组
void loadpng()
{
for (int i = 0;i < 12;i++)
{
char buf[50] = { 0 };
sprintf(buf,"./photos/%d.png", i);//使得下面的图片也能跟着变换,注意要用到spintf完成
//用sprintf时1.项目2.c/c++3.常规4.SDL检查5.选择否,软件问题可以去掉
loadimage(imgs + i, buf, SIZE, SIZE);//用这个加图片时,1.项目2.属性3.高级4.字符集5.选则使用多节字符集
}
};
struct Map
{
int** arr;//二维数组指针
int rows;//行数
int cols;//列数
};
void map_recOpen(Map* map, int row, int col);
void DieWindow();
void WinWindow();
//初始化MAP
void map_init(Map* map, int rows, int cols)
{
map->rows = rows;
map->cols = cols;
map->arr = (int**)calloc(map->rows,sizeof(int*));//calloc是malloc的兄弟函数
if (!map->arr)
{
printf("内存申请失败\n");
return;
}
for (int i = 0;i < map->rows;i++)
{
map->arr[i] = (int*)calloc(map->cols, sizeof(int));//(int*)强转为int
if (!map->arr[i])
{
printf("内存申请失败");//一般来说必须返回确认是否成功
return;
}
}
}
//输出一下所有数组
void map_show(Map* map)
{
int cir=0;
for (int i = 0;i < map->rows;i++)
{
for (int k = 0;k < map->cols;k++)
{
printf(" %2d ", map->arr[i][k]);
if (map->arr[i][k] == 59)
{
cir += 1;
if (cir == num)
{
WinWindow();
getchar();
}
}
}
printf("\n\n");
}
}
//设置雷 -1表示在0处随机设置-1形成雷
void map_setMine(Map* map)
{
//设置随机数种子
srand(time(NULL));//时间头文件
for (int i = 0;i < num;)
{
//随机产生下标[0-10]
int r = rand() % map->rows;
int c = rand() % map->cols;
if (map->arr[r][c] == 0)
{
map->arr[r][c] = -1;//雷设置完成,但是这样会使=1位置重复,导致小于10个,方法:加循环
i++;//从for里把i++拿出来表示只有成功设置雷,才自增
}
}
//把以雷为中心的九宫格的数字都加一(雷除外)形成数字0-8
for (int i = 0; i < map->rows; i++)
{
for (int k = 0; k < map->cols; k++)
{
if (map->arr[i][k] == -1)
{
//以雷为中心的九宫格
for (int r = i-1; r <= i+1; r++)
{
for (int c = k-1; c <= k+1; c++)
{
//排除掉雷
if ((r >= 0 && r < map->rows && c >= 0 && c < map->cols)
&& map->arr[r][c] != -1)//此时加入边界判断即可恢复代码中断问题
{
map->arr[r][c] += 1;//此时代码有问题,可能会导致代码运行中断,因为会越界(雷在边界),九宫格出现行超出列超出的情况
}
}
}
}
}
}
}
//绘制界面(图形窗口)
void map_draw(Map* map)
{
for (int i = 0;i < map->rows;i++)
{
for (int k = 0; k < map->cols; k++)
{
//求每个格子的坐标
int x = k * SIZE;
int y = i * SIZE;
if (map->arr[i][k] >= 0 && map->arr[i][k] <= 8)
{
putimage(x, y, imgs + map->arr[i][k]);
}
else if (map->arr[i][k] == -1)
{
putimage(x, y, imgs + 11);
}//加密后最小为19,最大为28
else if (map->arr[i][k] >= 19 && map->arr[i][k] <= 28)
{
putimage(x, y, imgs + 9);
}
else if (map->arr[i][k] >= 59 && map->arr[i][k] <= 68)
{
putimage(x, y, imgs + 10);
}
if (map->arr[i][k] == -1)
{
DieWindow();
getchar();
}
}
}
}
//覆盖起来,加密操作
void map_cover(Map* map)
{
//让现在数组里的数据变得不一样
for (int i = 0; i < map->rows; i++)
{
for (int k = 0; k < map->cols; k++)
{
map->arr[i][k] += 20;
}
}
}
//点击格子打开
void map_mouseMsg(Map* map, ExMessage* msg)//消息参数,用鼠标需要的参数
{
//根据鼠标坐标,获得点击的格子的下标
int r = msg->y / SIZE;
int c = msg->x / SIZE;
//判断鼠标左键是否点击
if (msg->message == WM_LBUTTONDOWN&& map->arr[r][c] >= 19 && map->arr[r][c] <= 68)
{
if (map->arr[r][c] != 59)
{
map->arr[r][c] -= 20;//左击减数字,消除加密;
map_recOpen(map, r, c);
system("cls");
map_show(map);//换进来不需要取地址&
}
}
//判断是否为右键点击
if (msg->message == WM_RBUTTONDOWN && map->arr[r][c] >= 19 && map->arr[r][c] <= 28)
{
map->arr[r][c] += 40;//右键标记雷,插旗子二次加密
map_recOpen(map, r, c);
system("cls");
map_show(map);//换进来不需要取地址&
}
}
//如果点到空白格子,递归打开周围的所有的空白格子和数字格子
void map_recOpen(Map* map,int row,int col)
{
//先判断一下是否为空白格子
if (map->arr[row][col] != 0)
{
return;
}
//遍历空白格子为中心的九宫格
for (int r = row - 1; r <= row + 1; r++)
{
for (int c = col - 1; c <= col + 1; c++)
{
if ((r >= 0 && r < map->rows && c >= 0 && c < map->cols)
&&map->arr[r][c]>=19&&map->arr[r][c]<=28)
{
map->arr[r][c] -= 20;
map_recOpen(map, r, c);//递归,自己调用自己
}
}
}
}
void DieWindow()
{
BeginBatchDraw();//双缓冲绘图
initgraph(400, 400);
setbkmode(TRANSPARENT);
settextcolor(LIGHTBLUE);
outtextxy(150, 150, "BOOM!BOOM!");
outtextxy(150, 120, "GameOver");
EndBatchDraw();
}
void WinWindow()
{
BeginBatchDraw();//双缓冲绘图
initgraph(400, 400);
setbkmode(TRANSPARENT);
settextcolor(LIGHTBLUE);
outtextxy(150, 150, "WOW!COOL!");
outtextxy(150, 120, "恭喜你通关了");
EndBatchDraw();
}
int main()
{
//创建图形窗口
initgraph(800, 600,EW_SHOWCONSOLE);//长,宽,显示控制台
//播放背景音乐
mciSendString("open ./photos/trip.mp3 alias bgm", NULL, 0, NULL);
mciSendString("play bgm repeat", NULL, 0, NULL);//循环播放
loadpng();
Map map;
map_init(&map,30,40);//行列
map_setMine(&map);//&取地址
map_cover(&map);
//消息循环
while (true)
{
ExMessage msg = {0};
while (peekmessage(&msg, EM_MOUSE))
{
map_mouseMsg(&map, &msg);
//map_show(&map);
map_draw(&map);
//WinGame(&map,m,n);
}//用while循环因为括号内消息为真则继续循环
}
getchar();
return 0;
}
六、运行结果预览
生成游戏
点击空白格子,递归打开周围空白格子和数字
点击到雷,游戏结束,失败
标记出所有雷,游戏胜利