扫雷游戏最早出现在20世纪50年代,一经发布便立即吸引了一大批游戏玩家。这个游戏锻炼玩家的逻辑和规划能力。 尽管它表面上看起来很简单,但该游戏仍需要专注和使用分析技能。
接下来分版块带大家利用C/C++实现图形化扫雷游戏,摆脱黑框框!
目录
部分内容说明:
#define Row 10//行
#define Col 10//列
#define Num 10//雷的数量
#define IMGW 40//图片长宽
以上为我在代码中使用的宏。
注意:文章为分结构讲解思路,代码在最后!
可以先看棋盘生成模块再看图形化模块,不然可能会晕。
一,图形化的准备工作
1,首先我们要图形化,必须得先有图
链接:https://pan.baidu.com/s/1d44bNSO8NWWk884zxpSnWA
提取码:1234这是图片素材,百度网盘
2,有了图片,接下来我们就要对图片进行引用,插入图片,创造图形化界面。这边推荐使用EasyX。直接在浏览器搜索EasyX就可以(注意,我发现最新版本,也就是大暑版,貌似无法扫描到VS2022版本,所以这边推荐下载另一个版本),如图:
安装完成之后,打开安装文件,然后它会自动扫描你的VS版本,你只要点击安装
安装对应版本就行,然后在你要编译的C/C++文件里包含这两个头文件(这边推荐创建一个头文件game.h用于存放对应的头文件,引用时只需要在你的C/C++文件包含#include"game.h"就可以使用全部头文件)
#include<graphics.h>
#include<easyx.h>
二,游戏的模块划分与实现思路
一个扫雷肯定有以下基本模块:
1,图形化模块
2,棋盘生成模块
3,操作模块
1,图形化模块
我们可以先将图形化界面创建出来,那么我们就需要用到这个函数
initgraph(Row * IMGW, Col * IMGW, EW_SHOWCONSOLE);
//其中Row,Col为数组的大小(Mine[Row][Col]),IMGW一张图片的长宽
//EW_SHOWCONSOLE让控制台窗口不会消失,将它改变为0或者NULL可以让控制台消失,但是会保存一段时间
此时如果我们将程序运行起来,你应该会发现多了一个窗口,那就是我们需要的图形化窗口
那么现在窗口有了,我们就需要图片来插入到窗口,我上面有给过图片素材,将图片素材image文件夹放到我们的程序同一个文件夹
这样才能正常读取
里面应该有这些,注意他们的下标我们引用要用到。
先创建一个图片数组用于储存你的图片
IMAGE img[12];
//其中IMAGE用于引入图片
//12为数组储存的图片数量
那么下一步自然就是将图片输入下载进来使用了,接下来我们就要用到这些个函数
for (int i = 0; i < 12; i++)
{
char filename[50] = { 0 };
sprintf_s(filename, "./images/%d.jpg", i);
loadimage(img + i, filename, IMGW, IMGW);
}
//我们可以利用这个循环,将图片输入到这个数组中
//loadimage函数帮我们把图片下载进来,并且规定大小
//整个循环体有点难讲清,而且不在主题范围,故不解释,知道思路即可
//其中的./指当前文件所在的文件夹
//利用i我们可以通过下标输入所以的图片
此时,你已经将图片下载进来了,那么现在你就可以引用他们了,那么怎么使用呢?没错,通过下标引用,还需要借助一个函数
putimage(j * IMGW,i * IMGW, img + mine[i][j]);
//前两个用于给图形化贴图j,i表示第几行第几列贴上图片,后面img+mine[i][j]表示在此时数组位置插上下标为mine[i][j]的图片
//注意,不要把I,j颠倒,如果你数组是i,j那么前面贴图就是J,i
那么现在我们可以试一下把图片插入图形化界面
void drapmap(int mine[Row][Col], IMAGE img[])
{
for (int i = 0; i < 12; i++)
{
putimage(i * IMGW, 0, img + i);
}
上面为代码实现,利用循环插入图片,不多解释,成功的结果应该是下面那样(注意,此时Row,Col我设置为12,只是为了方便演示)
接下来我们对其进行填充,但我们会发现,我们的雷全都暴露出来了
由于我们此时使用到的下标在0到11之间,使用mine[x][y]时我们会将雷暴露出去,但我们是游戏扫雷,不能让玩家看到雷,此时我们可以将雷给隐蔽(文章后面会讲到我们将雷设置为-1,然后将其他数组的数的初始化)
我们将数组中所有的数加上一个数(这个数最好大于12,不然会与其他图片下标重合),例如我们加上20,此时数组中所有数的大小范围为19到27。我们可以在插入图片的时候设置一个if语句,对插入的图片进行选择,如果在这个区间内的图片,我们可以覆盖格子进行加密保存
for (int i = 0; i < Row; i++)
{
for (int j = 0; j < Col; j++)
{
mine[i][j] += 20;
}
}
//使用这个循环遍历数组对棋盘进行加密
加密完成后我们的棋盘应该是这样的。
到此,基本图形化已经说完,接下来的图形化会在操作模块补充。
2,棋盘生成模块
想要实现扫雷,最起码我们应该得先有棋盘给我们看,有雷给我们扫吧。(以下代码并非完整形态,完整请看最后代码)
那么第一步肯定就是创造我们的棋盘了,我们一个二维数组,以此作为我们的棋盘
int mine[Row][Col] = { 0 };
然后现在棋盘有了,我们就想要有雷给我们扫吧,那么第二步,我们可以创建一个函数用来埋雷
void Showmine(int mine[Row][Col])//
{
//埋雷
for (int i = 0; i < Num;)
{
int x = rand() % Row;
int y = rand() % Col;
if (mine[x][y] == 0)
{
mine[x][y] = -1;
i++;
}
}
其中我们使用了rand函数,使用这个函数,我们要包含这个头文件(包括等下使用的srand函数也是要包含这个头文件)
#include<stdlib.h>
其中rand函数是用来生成随机数的,我们可以使用它来设置随机数,达到埋雷的目的,然而这个随机是伪随机,它的实现想要种子,我们可以使用srand函数对rand函数设置种子,利用时间戳实现随机设置种子,具体是如何实现的这里就不多说了
srand((unsigned int)time(NULL));
此时我们已经可以实现埋雷了,那么玩过的小伙伴都知道,光是有雷和地图是不够的,还要有数字统计无雷格子附近雷区的数量,函数实现看下
Void Getmine(mine[Row][Col])
{
for (int i = 0; i < Row; i++)
{
for (int j = 0; j < Col; j++)
{
if ( mine[i][j] == -1)
{
for (int k = i - 1; k <= i + 1; k++)
{
for (int l = j - 1; l <= j + 1; l++)
{
if ((k >= 0 && k < Row && l >= 0 && l < Col) && mine[k][l] != -1)
{
mine[k][l]++;
}
}
}
}
}
}
}
这个函数实现原理是,我将雷设置为数字-1,遍历整个数组,如果有数组为-1,则遍历其周围,若为无雷则加1,以此递推算出无雷空格附近雷的个数。
3,操作模块
最后一步,我们想要进行扫雷,就必须对棋盘进行操作吧,这里我们定义建立一个消息结构体来进行消息的获取吧,这里我们就获取鼠标消息即可(想要包含头文件#include<windows.h>)
void MouseEvent(int mine[Row][Col])
{
//定义消息结构体
ExMessage msg;
if (peekmessage(&msg, EM_MOUSE))
{
int r = msg.x / IMGW;
int c = msg.y / IMGW;
if (msg.message == WM_LBUTTONDOWN)//按左键
{
......
}
if (msg.message == WM_RBUTTONDOWN)//按右键
{
......
}
}
其中ExMessage msg;用于定义这个结构体,peekmessage(&msg, EM_MOUSE)用于接受鼠标信息, if (msg.message == WM_L/RBUTTONDOWN)用于判断我们接受的是什么信息,我们需要将接收到的鼠标信息,转换为我们的数组信息
int r = msg.x / IMGW;
int c = msg.y / IMGW;
//r,c为数组的横纵坐标,转化为x,y轴信息
//注意r,c不要搞反
此时我们完成了转换,接下来就是利用鼠标来扫雷了,其实所谓的扫雷,是一种图片的更替,在上面我们提到我们将整个数组的图形化加密了,那么选择我们就要解密,怎么解密呢?很简单,-20即可,-20不就解除了我们的加密吗,此时就完成了扫雷的操作
if (msg.message == WM_LBUTTONDOWN)//按左键
{
if (mine[c][r] >= 19 && mine[c][r] <= 28)
{
mine[c][r] -= 20;
}
//我们默认按左键扫雷
然后我们在扫雷时,看到会有空格,就是那种四周没有雷的空白区域,此时我们要将这片区域自动打开,不然太浪费时间了,此时我们就可以使用函数递归来实现,每点到一次空格,则开空格并且再次进入函数,递归开空格
void OpenNULL(int mine[Row][Col], int row, int col)
{//开空格
if (mine[row][col] == 0)
{
for (int i = row - 1; i <= row + 1; i++)
{
for (int j = col - 1; j <= col; j++)
{
if ((i >= 0 && i <= Row && j >= 0 && j <= Col) && mine[i][j] >= 19 && mine[i][j] <= 28)
{
mine[i][j] -= 20;
OpenNULL(mine, i, j);
}
}
}
}
}
//由于我们初始化棋盘为0,我们解密后,如果格子为0,则为空格,则递归开空格
有时候我们不确定一个格子是不是雷,可以给他打上棋子以此标记,那么如何实现呢?其实就相当于二次加密,这时候就可以用到右键了,跟左键开空格一样
if (msg.message == WM_RBUTTONDOWN)//按右键插旗
{
if (mine[c][r] >= 19 && mine[c][r] <= 28)
{
mine[c][r] += 20;
}
else if (mine[c][r] >= 39)
{
mine[c][r] -= 20;
}
}
//设置一个判断,如果用右键点击,则数组加上一个数字,最好不要在19到28之间,避免重叠或者冲突
//然后再使用插入图片
if (mine[i][j] >= 39 && mine[i][j] <= 48)
{
putimage(j * IMGW, i * IMGW, img + 11);
}
//判断一下,并插入图片,注意下标
最后就是判断输赢了,扫雷肯定有输有赢,我们应该如何实现呢?
bool DEAD=flase;//定义一个bool类型的DEAD为flase(为假)
//进行输赢判断
void Findmine(int mine[Row][Col], int row, int col)
{
//点到雷游戏结束,其他雷显示
if (mine[row][col] == -1)
{
for (int i = 0; i < Row; i++)
{
for (int k = 0; k < Col; k++)
{
if (mine[i][k] == 19)
{
mine[i][k] -= 20;
}
}
}
DEAD = true;
}
}
//定义函数Findminw扫描棋盘,确定你是否踩到了雷,如果踩到了雷,,则将DEAD改为true(为真)
if (DEAD)//输了
{
int ret = MessageBox(GetHWnd(), "你踩到雷了!", "hit", MB_OKCANCEL);
if (ret == IDOK)
{
Showmine(mine);
showmap(mine);
DEAD = false;
}
else if (ret == IDCANCEL)
{
exit(502);
}
//使用一个if句来判断
//如果赢了也是同样的道理,同意定义一个bool win n= flase;
int count = 0;
for (int i = 0; i < Row; i++)
{
for (int j = 0; j < Col; j++)
{
if (mine[i][j] >= 0 && mine[i][j] <= 8)
{
++count;
}
}
}
if (count == Row * Col - Num)
{
WIN = true;
}
//每一次扫雷都遍历棋盘,查找排雷的个数,上面的式子用来判断是否将雷排光了,不多做解释
else if (WIN)
{
int ret = MessageBox(GetHWnd(), "你赢了!是否再来一局?", "hit", MB_OKCANCEL);
if (ret == IDOK)
{
Showmine(mine);
showmap(mine);
WIN = false;
}
else if (ret == IDCANCEL)
{
exit(502);
}
//这样就实现了输赢的判断
最后是输赢判断完后,重新开始的是否。
memset(mine, 0, Row * Col * sizeof(int));
//使用这个可以实现
4,拓展模块
我们也可以做一些拓展模块,增加游戏趣味,比如给游戏增加音乐,此时我们需要另外的头文件和函数
#include<mmsystem.h>
#pragma comment(lib,"Winmm.lib")
//上面两个头文件
PlaySound(TEXT("音乐文件的名字"), NULL, SND_FILENAME | SND_ASYNC | SND_LOOP);
//使用上面的函数可以实现播放音乐,注意,只支持WAV格式的音乐播放,且音乐文件应与启动程序放置于同一文件夹中才可正常使用
可以使用格式工厂软件将音乐文件格式进行转换
格式工厂 - 免费多功能的多媒体文件转换工具 (formatfactory.org)
链接在此
最后希望我的这些能帮助到你们,感谢观看,求求点赞,码了好久(qwq)
以下是源代码
这是游戏代码主运行程序(其中包含两首音乐,你可能没有,可以自行删除并添加新音乐!!!)
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
bool DEAD = false;
bool WIN = false;
int main()
{
initgraph(Col * IMGW, Row * IMGW, EW_SHOWCONSOLE);
//播放开始音乐
music();
srand((unsigned int)time(NULL));
int mine[Row][Col] = { 0 };//埋雷区数组
Showmine(mine);
//创造图片数组
IMAGE img[12];
for (int i = 0; i < 12; i++)
{
char filename[50] = { 0 };
sprintf_s(filename, "./images/%d.jpg", i);
loadimage(img + i, filename, IMGW, IMGW);
}
//主函数*************************************
//********************************************
while (true)
{
//鼠标操作
Mouseoperation(mine);
//转换数组数据到图形
drapmap(mine, img);
//判断生死
if (DEAD)//输了
{
int ret = MessageBox(GetHWnd(), "你踩到雷了!", "hit", MB_OKCANCEL);
if (ret == IDOK)
{
Showmine(mine);
showmap(mine);
DEAD = false;
}
else if (ret == IDCANCEL)
{
exit(502);
}
}
else if (WIN)
{
int ret = MessageBox(GetHWnd(), "你赢了!是否再来一局?", "hit", MB_OKCANCEL);
if (ret == IDOK)
{
Showmine(mine);
showmap(mine);
WIN = false;
}
else if (ret == IDCANCEL)
{
exit(502);
}
}
}
int count = 0;
for (int i = 0; i < Row; i++)
{
for (int j = 0; j < Col; j++)
{
if (mine[i][j] >= 0 && mine[i][j] <= 8)
{
++count;
}
}
}
if (count == Row * Col - Num)
{
WIN = true;
}
getchar();
return 0;
}
void music()
{
PlaySound(TEXT("DJ OKAWARI - Luv Letter (情书)"), NULL, SND_FILENAME | SND_ASYNC | SND_LOOP);
PlaySound(TEXT("Dream_It_Possible"), NULL, SND_FILENAME | SND_ASYNC | SND_LOOP);
}
void showmap(int mine[Row][Col])//没问题
{
//打印棋盘
for (int a = 0; a < Row; a++)
{
for (int b = 0; b < Col; b++)
{
printf("%d ", mine[a][b]);
}
printf("\n");
}
}
void Showmine(int mine[Row][Col])
{
//把地图清零
memset(mine, 0, Row * Col * sizeof(int));
//埋雷
for (int i = 0; i < Num;)
{
int x = rand() % Row;
int y = rand() % Col;
if (mine[x][y] == 0)
{
mine[x][y] = -1;
i++;
}
}
//计算雷数量
for (int i = 0; i < Row; i++)
{
for (int j = 0; j < Col; j++)
{
if ( mine[i][j] == -1)
{
for (int k = i - 1; k <= i + 1; k++)
{
for (int l = j - 1; l <= j + 1; l++)
{
if ((k >= 0 && k < Row && l >= 0 && l < Col) && mine[k][l] != -1)
{
mine[k][l]++;
}
}
}
}
}
}
//全部加密
for (int i = 0; i < Row; i++)
{
for (int j = 0; j < Col; j++)
{
mine[i][j] += 20;
}
}
}
void drapmap(int mine[Row][Col], IMAGE img[])
{
for (int i = 0; i < Row; i++)
{
for (int j = 0; j < Col; j++)
{
if (mine[i][j] >= 0 && mine[i][j] <= 8)
{
putimage(j * IMGW, i * IMGW, img + mine[i][j]);
}
else if (mine[i][j] == -1)
{
putimage(j * IMGW, i * IMGW, img + 9);
}
else if (mine[i][j] >= 19 && mine[i][j] <= 28)
{
putimage(j * IMGW, i * IMGW, img + 10);
}
else if (mine[i][j] >= 39 && mine[i][j] <= 48)
{
putimage(j * IMGW, i * IMGW, img + 11);
}
}
}
}//转换数组数据到图形
void Mouseoperation(int mine[Row][Col])
{
//定义消息结构体
ExMessage msg;
if (peekmessage(&msg, EM_MOUSE))
{
int r = msg.x / IMGW;
int c = msg.y / IMGW;
if (msg.message == WM_LBUTTONDOWN)//按左键
{
if (mine[c][r] >= 19 && mine[c][r] <= 28)
{
mine[c][r] -= 20;
OpenNULL(mine, c, r);
Findmine(mine, c, r);
//打印雷区
showmap(mine);
}
}
else if (msg.message == WM_RBUTTONDOWN)//按右键插旗
{
if (mine[c][r] >= 19 && mine[c][r] <= 28)
{
mine[c][r] += 20;
}
else if (mine[c][r] >= 39)
{
mine[c][r] -= 20;
}
}
}
}
void OpenNULL(int mine[Row][Col], int row, int col)
{//开空格
if (mine[row][col] == 0)
{
for (int i = row - 1; i <=row + 1; i++)
{
for (int j = col - 1; j <=col; j++)
{
if ((i >= 0 && i <= Row && j >= 0 && j <= Col) && mine[i][j] >= 19 && mine[i][j] <= 28)
{
mine[i][j] -= 20;
OpenNULL(mine, i, j);
}
}
}
}
}//
void Findmine(int mine[Row][Col], int row, int col)
{
//点到雷游戏结束,其他雷显示
if (mine[row][col] == -1)
{
for (int i = 0; i < Row; i++)
{
for (int k = 0; k < Col; k++)
{
if (mine[i][k] == 19)
{
mine[i][k] -= 20;
}
}
}
DEAD = true;
}
}
#pragma once
#include<graphics.h>
#include<easyx.h>
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<windows.h>
#include<mmsystem.h>
#pragma comment(lib,"Winmm.lib")
#define Row 10
#define Col 10
#define Num 10
#define IMGW 40
void drapmap(int mine[Row][Col], IMAGE img[]);
void Mouseoperation(int mine[Row][Col]);
void Showmine(int mine[Row][Col]);
void OpenNULL(int mine[Row][Col], int row, int col);
void Findmine(int mine[Row][Col], int row, int col);
void showmap(int mine[Row][Col]);
void music();
这些是头文件内容,创建一个game.h存放