Easyx-----c++实现经典Windows扫雷

一些说明

关于扫雷的基本实现,我在这篇博客已经详细介绍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;
}

部分素材展示

  • 21
    点赞
  • 74
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qiuqiuyaq

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值