一些说明
关于扫雷的基本实现,我在这篇博客已经详细介绍Easyx-----c语言实现简易版扫雷_考拉爱睡觉鸭~的博客-CSDN博客
这里不再描述,主要是以c++单例设计模式的方式实现扫雷,多加了右键点击笑脸作弊功能,不会扫雷的小伙伴也可以愉快玩耍了
效果展示
右键点击笑脸作弊,点击左键还原
Common.h 公共的头文件
#pragma once
#include <graphics.h>
#include <iostream>
#include <string>
#include <map>
#include<array>
#include <list>
#include <thread>
#include <vector>
#include <ctime>
#include <mmsystem.h>
#pragma comment(lib,"winmm.lib")
using namespace std;
Res.h 资源文件加载
#pragma once
#include "Common.h"
class Res
{
private:
Res();
public:
static int WIDTH(string name);
static int HEIGHT(string name);
static Res* GetInstance();
static void Draw_IMG(int x, int y, string name); //背景图片绘制
static void DrawIMG(int x, int y, string name,int preIndex); //其他图片绘制
static DWORD WINAPI PlayMusic(LPVOID lparame); //播放音乐
~Res();
public:
static map<string, IMAGE*> img; //图片
static map<string, string> music; //音乐
static int MineNum; //雷的数量
static int ROW; //行|列
static int COL;
};
Res.cpp
#include "Res.h"
map<string, IMAGE*> Res::img; //图片
map<string, string> Res::music; //音乐
int Res::MineNum = 10; //雷的数量
int Res::ROW = 9; //行|列
int Res::COL = 9;
/*———————————————————————————————————————————
加载 和 初始化 图片相关内容
————————————————————————————————————————————*/
Res::Res()
{
//背景
string background = "./res/background.png";
//砖块
string block[2] = { "./res/block/common_block.png","./res/block/press_block.png" };
//笑脸标识 smile victory fail error(外挂)
string face[4] = { "./res/face/smile.png","./res/face/victory.png",
"./res/face/fail.png","./res/face/error.png" };
//地雷 未踩雷 标记雷 踩中雷
string mine[3] = { "./res/mine/unstep_mine.png","./res/mine/sign_mine.png",
"./res/mine/step_mine.png" };
//标识 旗帜 问号 按压问号
string sign[3] = { "./res/sign/flag.png","./res/sign/question.png",
"./res/sign/press_question.png" };
//普通数字 1-8
for (int i = 1; i <= 8; i++) {
string init = "./res/numbers/";
string strURL = "0";
strURL[0] += i;
strURL += ".png";
strURL = init + strURL;
img[to_string(i - 1)] = new IMAGE; //img["0"]
loadimage(img[to_string(i - 1)], strURL.c_str());
}
//倒计时数字 0-9
for (int i = 0; i <= 9; i++) {
string init = "./res/timeing/";
string strURY = "0";
strURY[0] += i;
strURY += "_1.png";
strURY = init + strURY;
img[to_string(i - 0)+"_"] = new IMAGE; //img["0_"]
loadimage(img[to_string(i - 0) + "_"], strURY.c_str());
//cout << strURL << endl;
}
img["背景"] = new IMAGE;
img["砖块"] = new IMAGE[2];
img["笑脸"] = new IMAGE[4];
img["地雷"] = new IMAGE[3];
img["标识"] = new IMAGE[3];
loadimage(img["背景"], background.c_str()); //370-250=120 479-320=159
for (int i = 0; i < 3; i++)
{
loadimage(img["地雷"] + i, mine[i].data());
loadimage(img["标识"] + i, sign[i].data());
}
for (int i = 0; i < 4; i++)
{
loadimage(img["笑脸"] + i, face[i].data());
}
for (int i = 0; i < 2; i++)
{
loadimage(img["砖块"] + i, block[i].data());
}
}
//获取图片的高度
int Res::WIDTH(string name)
{
return GetInstance()->img[name]->getwidth();
}
int Res::HEIGHT(string name)
{
return GetInstance()->img[name]->getheight();
}
Res* Res::GetInstance()
{
static Res* res = new Res;
return res;
}
//只有一张图片的贴图: 背景图贴图
void Res::Draw_IMG(int x, int y, string name)
{
putimage(x, y, GetInstance()->img[name]);
}
void Res::DrawIMG(int x, int y, string name, int preIndex)
{
putimage(x, y, GetInstance()->img[name] + preIndex);
}
DWORD __stdcall Res::PlayMusic(LPVOID lparame)
{
return 0;
}
Res::~Res()
{
delete img["背景"];
delete[]img["砖块"];
delete[]img["笑脸"];
delete[]img["地雷"];
delete[]img["标识"];
}
Data.h 数据的处理
#pragma once
#include"Common.h"
//根据鼠标消息,做数据的改变
class Data {
public:
void Set(); //设置雷|按键开盖|设置标记
void constValue(int i, int j, int value); //设置数据(地图中的值) 限定数据|改变数据
void changeValue(int i, int j, int value);
array<array<int, 9>, 9>& getMap(); //在外面画图要访问数据,提供外部访问接口
protected:
array <array< int, 9 >, 9 > map = {0};
};
Data.cpp
#include "Data.h"
#include "Res.h" //用到资源
#include"Common.h"
void Data::constValue(int i, int j, int value)
{
map[i][j] = value;
}
void Data::changeValue(int i, int j, int value)
{
map[i][j]+= value;
}
void Data::Set()
{
//设置随机数种子
srand((unsigned)time(NULL));
//把map全部初始化为0
for (int i = 0; i < Res::ROW; i++)
{
for (int j = 0; j < Res::COL; j++)
{
map[i][j] = 0;
cout << map[i][j]<<"\t";
}
cout << endl;
}
cout << endl;
//随机设置十个雷 用-1表示
for (int i = 0; i < Res::MineNum; )
{
//数组的有效下标 [0,9]
int r = rand() % Res::ROW;
int c = rand() % Res::COL;
//随机下标可能有重复的---需要判断当前位置是否有设置为雷
if (map[r][c] == 0)
{
changeValue(r, c, -1);
//只有执行了这里的代码,才成功设置了雷 -1 后++
i++;
}
}
//把以雷为中心的九宫格数据都+1,雷除外
for (int i = 0; i < Res::ROW; i++)
{
for (int k = 0; k < Res::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++)
{
//对周围的数据加1,注意要防止出现数组下标为-1的情况(越界)
if ((r >= 0 && r < Res::ROW && c >= 0 && c < Res::COL) && map[r][c] != -1)
{
changeValue(r, c, 1);//++map[r][c];
}
}
}
}
}
}
//加密格子 遍历每一个元素,对每一个元素加一个数处理,让它与原来不同,就不会输出原来对应的图片
for (int i = 0; i <Res::ROW; i++)
{
for (int k = 0; k <Res::COL; k++)
{
changeValue(i,k,20); //所有的都需要加密
}
}
}
array<array<int, 9>, 9>& Data::getMap()
{
// TODO: 在此处插入 return 语句
return map;
}
timeer.hpp 定时器制作
#pragma once
#include "Common.h"
class MyTimer
{
public:
static bool Timer(int duration, int id) //时间间隔 id
{
static int startTime[10]; //开始时间---静态变量自动初始化为0
int endTime = clock(); //结束时间
if (endTime - startTime[id] >= duration)//结束时间-开始时间>=时间间隔
{
startTime[id] = endTime; //把原来的结束时间改为新的开始时间
return true;
}
return false;
}
};
button.h 封装按钮
#pragma once
#include "Common.h"
#include"DrawImg.h"
class Data;
/*———————————————————————————————————————————
鼠标按键控制
分两种情况: 1.鼠标是游戏控制的按键 2.鼠标是地图变化的按键
————————————————————————————————————————————*/
class Button
{
public:
Button(int ImgSize = 24); //地图变化的按键
void ClickButton(ExMessage msg, Data* pData); /*鼠标操作的事件响应: 鼠标点击...*/
void boomBlank(Data* pData,int row,int col); //标记格子和连环炸开空白格子
int judge(Data* pData, int row, int col); //游戏结束条件
protected:
//图片的大小
int ImgSize;
};
button.cpp
#include "button.h"
#include"Common.h"
#include"Res.h"
#include "Data.h"
Button::Button(int ImgSize) {
this->ImgSize = ImgSize;
Res::DrawIMG(105, 20, "笑脸", 0);
}
void Button::ClickButton(ExMessage msg, Data* pData)
{
int r = (msg.x - 16) / ImgSize; //x
int c = (msg.y - 84) / ImgSize; //y
//游戏控制的边框
if ((msg.x >= 105 && msg.y >= 20) && (msg.x <= 141 && msg.y <= 56))
{
//printf("%d %d\n", msg.x, msg.y);
if (msg.message == WM_LBUTTONDOWN) //左键按下还原游戏
{
Res::DrawIMG(105, 20, "笑脸", 0);
for (int i = 0; i < Res::ROW; i++)
{
for (int k = 0; k < Res::COL; k++)
{
if (pData->getMap()[i][k] >= -1 && pData->getMap()[i][k] <= 8) //数字的还原
pData->changeValue(i, k, 20);
if(pData->getMap()[i][k] >= 39 && pData->getMap()[i][k] < 59) //旗子的还原
pData->changeValue(i, k,-20);
if (pData->getMap()[i][k] >= 59) //问号的还原
pData->changeValue(i, k, -40);
}
}
}
else if (msg.message == WM_RBUTTONDOWN) //右键按下开启bug
{
Res::DrawIMG(105, 20, "笑脸", 3);
for (int i = 0; i < Res::ROW; i++)
{
for (int k = 0; k < Res::COL; k++)
{
if (pData->getMap()[i][k] >= 19 && pData->getMap()[i][k] <= 28)
pData->changeValue(i, k, -20);
}
}
}
}
//地图的边框
if ((msg.x >= 16 && msg.y >= 84) && (msg.x <= 232 && msg.y <= 300))
{
//鼠标左键按下,有事件响应,左键打开格子
if (msg.message == WM_LBUTTONDOWN)
{
printf("%d %d\n", msg.x, msg.y);
printf("%d %d\n", r, c);
//什么时候能够打开,没有打开的时候就打开(只能点击1次有效数字不会再变化)
if (pData->getMap()[r][c] >= 19 && pData->getMap()[r][c] <= 28)
{
pData->changeValue(r, c, -20); //map[r][c] -= 20
boomBlank(pData, r, c); //检测一下是不是空白格子,是,炸开,不是直接退出
}
}
//鼠标右键按下,有事件响应,右键标记格子
else if (msg.message == WM_RBUTTONDOWN)
{
//是否能够标记:如果没有打开就能标记
if (pData->getMap()[r][c] >= 19 && pData->getMap()[r][c] <= 28)
{
pData->changeValue(r, c, 20); //再次加密 map[r][c] += 20
}
else if (pData->getMap()[r][c] >= 39 && pData->getMap()[r][c] < 59)
{
pData->changeValue(r, c, 20); //再次点击能够出现问号 再次加密 map[r][c] += 20
}
else if (pData->getMap()[r][c] >= 59) //再次点击能够取消格子 双层解密 map[r][c] -= 20
{
pData->changeValue(r, c, -40);
}
}
}
}
void Button::boomBlank(Data* pData, int row, int col)
{
//判断row col位置是不是空白格子(如果不是直接退出)
if (pData->getMap()[row][col] == 0)
{
for (int r = row - 1; r <= row + 1; r++) //遍历九宫格,是空白直接炸开
{
for (int c = col - 1; c <= col + 1; c++)
{
if ((r >= 0 && r < Res::ROW && c >= 0 && c < Res::COL) //没越界
&& pData->getMap()[r][c] >= 19 && pData->getMap()[r][c] <= 28) //没有打开
{
pData->changeValue(r, c, -20); //map[r][c] -= 20
boomBlank(pData, r, c); //继续遍历新的九宫格,继续打开
}
}
}
}
}
//游戏结束条件 [每点击一次就判断一下] 输了返回 -1 没结束返回 0 赢了返回 1
int Button::judge(Data* pData, int row, int col)
{
if (!(row >= 0 && col >= 0 && row< Res::ROW && col< Res::COL))
{
return 0;
}
//点到了雷,结束 输了
if (pData->getMap()[row][col] == -1 || pData->getMap()[row][col] == 19) //任何时候都可以判断
{
return -1;
}
//点完了格子,结束 赢了 点开了81 - 10 = 71 个格子(都点开了)
int cnt = 0;
for (int i = 0; i < Res::ROW; i++)
{
for (int k = 0; k < Res::COL; k++)
{
//统计打开的格子的数量
if (pData->getMap()[i][k] >= 0 && pData->getMap()[i][k] <= 8)
{
++cnt; //最终有71个
}
}
}
if (Res::ROW * Res::COL - Res::MineNum == cnt)
{
return 1;
}
return 0;
}
DrawImg.h 绘制图片
#pragma once
#include"Common.h"
#include"Data.h"
class Data;
class Draw
{
public:
Draw();
void Game_Draw(); //根据数据画图片|数据的处理封装在Game中
void showData();
Data* getData() { return pData; }
int& getTime() { return timeCnt; }
protected:
Data* pData; //游戏需要用到数据,包含数据的指针
int timeCnt = 0; //秒数变量
};
DrawImg.cpp
#include "DrawImg.h"
#include"Res.h"
#include"Data.h"
#include"time.hpp"
Draw::Draw():pData(new Data)
{
pData->Set();
//背景
initgraph(Res::WIDTH("背景"), Res::HEIGHT("背景"), EW_SHOWCONSOLE);
Res::Draw_IMG(0, 0, "背景");
Res::Draw_IMG(21, 21, "0_");
Res::Draw_IMG(21 + 20, 21, "1_");
Res::Draw_IMG(21 + 20 * 2, 21, "0_");
Res::Draw_IMG(21 + 20 * 6 + 25, 21, "0_");
Res::Draw_IMG(21 + 20 * 8 + 4, 21, "0_");
Res::Draw_IMG(21 + 20 * 9 + 4, 21, "0_");
}
void Draw::showData()
{
for (int i = 0; i < Res::ROW; i++)
{
for (int k = 0; k < Res::COL; k++)
{
cout << pData->getMap()[k][i] << "\t";
}
cout << endl;
}
cout << endl;
}
void Draw::Game_Draw()
{
int ge=0, shi=0, bai=0;
if (MyTimer::Timer(1000, 0))
{
getTime()++; //秒数变量的叠加
}
ge = getTime() % 10;
shi = getTime() / 10 % 10;
bai = getTime() / 100;
//ge
Res::Draw_IMG(21 + 20 * 9 + 4, 21,to_string(ge) + "_");
//shi
Res::Draw_IMG(21 + 20 * 8 + 4, 21, to_string(shi) + "_");
//bai
Res::Draw_IMG(21 + 20 * 6 + 25, 21, to_string(bai) + "_");
for (int i = 0; i < Res::ROW; i++)
{
for (int k = 0; k < Res::COL; k++)
{
//周围全是雷中间是8---周围没有雷中间是0
if (pData->getMap()[i][k] >= 0 && pData->getMap()[i][k] <= 8) //范围[0,8]
{
switch (pData->getMap()[i][k]) { //0 1 2 3 4 5 6 7
case 0:
Res::DrawIMG(16 + 24 * i, 84 + 24 * k, "砖块",1);
break;
case 1:
Res::Draw_IMG(16 + 24 * i, 84 + 24 * k, "0"); //对应数字1
break;
case 2:
Res::Draw_IMG(16 + 24 * i, 84 + 24 * k, "1"); //对应数字2
break;
case 3:
Res::Draw_IMG(16 + 24 * i, 84 + 24 * k,"2"); //对应数字3
break;
case 4:
Res::Draw_IMG(16 + 24 * i, 84 + 24 * k, "3"); //对应数字4
break;
case 5:
Res::Draw_IMG(16 + 24 * i, 84 + 24 * k, "4"); //对应数字5
break;
case 6:
Res::Draw_IMG(16 + 24 * i, 84 + 24 * k, "5"); //对应数字6
break;
case 7:
Res::Draw_IMG(16 + 24 * i, 84 + 24 * k, "6"); //对应数字7
break;
case 8:
Res::Draw_IMG(16 + 24 * i, 84 + 24 * k, "7"); //对应数字8
break;
}
}
else if (pData->getMap()[i][k] == -1)
{
Res::DrawIMG(16 + 24 * i, 84 + 24 * k, "地雷",0);
}
else if (pData->getMap()[i][k] >= 19 && pData->getMap()[i][k] <= 28) //画盖子---范围判断 最小和最大的
{
Res::DrawIMG(16 + 24 * i, 84 + 24 * k, "砖块",0);
}
else if (pData->getMap()[i][k] >= 39 && pData->getMap()[i][k] < 59) //-1 + 20 + 20
{
Res::DrawIMG(16 + 24 * i, 84 + 24 * k,"标识",0); //旗子
}
else if (pData->getMap()[i][k] >= 59)
{
Res::DrawIMG(16 + 24 * i, 84 + 24 * k, "标识", 1);//问号
}
}
}
}
扫雷.cpp 主函数部分
#include"DrawImg.h"
#include"Data.h"
#include"button.h"
#include"Res.h"
int main()
{
Draw draw;
Button* pp = new Button(); //按钮
ExMessage msg; //定义鼠标消息
while (true)
{
while (peekmessage(&msg, EM_MOUSE)) //获取鼠标消息
{
switch (msg.message)
{
case WM_LBUTTONDOWN: //鼠标左键和右键点击
case WM_RBUTTONDOWN:
pp->ClickButton(msg, draw.getData());
draw.showData(); //显示数据
int ret=pp->judge(draw.getData(), (msg.x - 16) / 24, (msg.y - 84) / 24); //每点击一次,判断一次
printf("judge:%d\n", ret); //根据返回值判断是赢了还是输了
if (ret == -1) //输了,输出哭脸|弹出对话框
{ //句柄|对话|标题|按钮
draw.Game_Draw(); //绘制
Res::DrawIMG(105, 20, "笑脸", 2);
Res::DrawIMG(16 + 24 * ((msg.x - 16) / 24), 84 + 24 * ((msg.y - 84) / 24), "地雷", 1);
int select_1=MessageBox(GetHWnd(), "不会吧?初级扫雷都玩不过?敢不敢再来一把?", "c++扫雷",MB_OKCANCEL);
//看用户的选择
if (select_1 == IDOK) //再来一把
{
//重新初始化
Res::DrawIMG(105, 20, "笑脸", 0);
draw.getTime() = 0;
draw.getData()->Set();
}
else //退出
{
exit(0);
}
}
else if (ret == 1) //赢了,输出帅脸|弹出对话框
{
Res::DrawIMG(105, 20, "笑脸", 1);
int select_2 = MessageBox(GetHWnd(), "不错哦!要不要再来一把?", "c++扫雷", MB_OKCANCEL);
if (select_2 == IDOK) //再来一把
{
//重新初始化
draw.getData()->Set();
Res::DrawIMG(105, 20, "笑脸", 0);
}
else //退出
{
exit(0);
}
}
break;
}
}
draw.Game_Draw(); //绘制
}
return 0;
}