保姆级!C/C++语言实现扫雷图形化(附源码和素材贴图)

扫雷游戏最早出现在20世纪50年代,一经发布便立即吸引了一大批游戏玩家。这个游戏锻炼玩家的逻辑和规划能力。 尽管它表面上看起来很简单,但该游戏仍需要专注和使用分析技能。

接下来分版块带大家利用C/C++实现图形化扫雷游戏,摆脱黑框框!

33bbcc3090144a14a6a163dddf76aafc.png

 

目录

部分内容说明:

#define Row 10//行
#define Col 10//列
#define Num 10//雷的数量
#define IMGW 40//图片长宽

以上为我在代码中使用的宏。

注意:文章为分结构讲解思路,代码在最后!

可以先看棋盘生成模块再看图形化模块,不然可能会晕。

一,图形化的准备工作

1,首先我们要图形化,必须得先有图

链接:https://pan.baidu.com/s/1d44bNSO8NWWk884zxpSnWA 
提取码:1234

这是图片素材,百度网盘

2,有了图片,接下来我们就要对图片进行引用,插入图片,创造图形化界面。这边推荐使用EasyX。直接在浏览器搜索EasyX就可以(注意,我发现最新版本,也就是大暑版,貌似无法扫描到VS2022版本,所以这边推荐下载另一个版本),如图:

e04f35e4fd6f45bcbf9b5b04c1911b14.png

 安装完成之后,打开安装文件,然后它会自动扫描你的VS版本,你只要点击安装

2580217f9f884874979e157ab12f78f7.png安装对应版本就行,然后在你要编译的C/C++文件里包含这两个头文件(这边推荐创建一个头文件game.h用于存放对应的头文件,引用时只需要在你的C/C++文件包含#include"game.h"就可以使用全部头文件)

#include<graphics.h>
#include<easyx.h>

4ee55d838e7e4bc5a7f648a21c9b1b9f.png

二,游戏的模块划分与实现思路

一个扫雷肯定有以下基本模块:

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存放

评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值