图形库实战丨C语言扫雷小游戏(超2w字,附图片素材)

目录

效果展示

游玩链接(无需安装图形库及VS)

开发环境及准备

1.VS2022版本

2.图形库

游戏初始化

1.头文件

2.创建窗口

3.主函数框架

开始界面函数

1.初始化

1-1.设置背景颜色及字体

1-2.处理背景音乐及图片素材

1-3.处理背景图位置

2.选择模式

2-1.获取鼠标信息

2-2.处理颜色变化

2-3.判断鼠标按下的键

普通模式

1.随机生成地雷

1-1.清空map数组,并播随机数种子。

1-2.随机布雷

2.确定数字

3.判断输赢

画图

递归函数

困难模式

生成日志

1.获取时间

2.输出内容

完整源代码

回顾编程过程


源代码三连博主+私信回复"扫雷"领取

效果展示

游玩链接(无需安装图形库及VS)

怎么样?还不错吧,快去链接处下载吧!

EXE链接:下载点我

开发环境及准备

1.VS2022版本

其他版本也可以,别太老就行。

2.图形库

本代码用到图形库,需要安装。

图形库简介:EasyX 是EasyX Graphics Library 是针对 Visual C++ 的免费绘图库,支持 VC6.0 ~VC2022,简单易用,学习成本极低,应用领域广泛。

安装网址:EasyX Graphics Library for C++

游戏初始化

1.头文件

在游戏过程中,需要用到很多头文件,包括但不仅限于:时间函数,图形库,......

cpp 复制代码
#include <iostream>//C++头文件
#include <time.h>//时间函数
#include <stdio.h>//标准输出输入库
#include <graphics.h> //图形库 
#include <mmsystem.h>//windows SDK 播放函数
#include <fstream>  //文件库
#include <windows.h>//windows api
#include <string>

2.创建窗口

因为窗口大小是以像素计数的,我们得先提前设定好格子的长和宽像素个数,我们用50就够了。

而扫雷一般是有10x10一百个格子的所以我们定义N为每行格子数,M为每格像素边长。

cpp 复制代码
#define N 10  //格子数
#define M 50  //一个格子的像素

那么窗口的边长就是:每格像素长×格子数=N×M。

cpp 复制代码
initgraph(N * M, N * M);//初始化绘图窗口宽500高500像素

3.主函数框架

我们先把主函数写好,再去实现相应的功能。

根据扫雷的不同难度,游戏划分为"15雷模式"和"35雷模式",增加了挑战性趣味性

注意:writeLogMsg是生成日志的函数,当初是方便我调试的,但对游戏体验没有影响。

cpp 复制代码
int main() 
{
	writeLogMsg("===天天扫雷=开始执行===");
	writeLogMsg("===开始界面=开始执行===");
	//initgraph(N * M, N * M,EW_SHOWCONSOLE);//, EW_SHOWCONSOLE初始化绘图窗口宽500高500像素
	initgraph(N * M, N * M);//初始化绘图窗口宽500高500像素
	

	//测试日志
	//int log[4] = {0,1,2,3};
	//writeLog(log, 9);
	writeLogMsg("===开始界面=开始执行===");
	StartWindow();//调用开始界面函数。
	writeLogMsg("===开始界面=结束执行===");
	//1.设置开始页面:背景透明、字体、背景音乐、背景图片,2.加载待使用的图片列表。3.判定鼠标位置和点击事件确定,用户选择的是15雷模式还是35雷模式
	if (gamemodel == 0) {
		normalModel();//15雷模式
	}
	else {
		BTmodel();//35雷模式
	}
	closegraph();
	
	writeLogMsg("===天天扫雷=结束执行===");
}

开始界面函数

1.初始化

1-1.设置背景颜色及字体

因为我们有图片,所有不需要有背景颜色,即把背景颜色设置为透明色。

cpp 复制代码
setbkmode(TRANSPARENT); //设置背景 透明风格

字体推荐楷体,当然你也可以自行调整,比如"宋体""仿宋"......

cpp 复制代码
settextstyle(40, 18, L"楷体"); //设置开始界面字体大小 L设置字符集

1-2.处理背景音乐及图片素材

音乐为天空之城,当然你也可以自己修改。

cpp 复制代码
mciSendString(L"open ./天空之城.mp3 alias bgm", 0, 0, 0); //L为设置字符集,./表示当前文件夹
mciSendString(L"play bgm repeat", 0, 0, 0);//重复播放bgm

图片素材名称不要打错了,加载图片的函数用法如下。

cpp 复制代码
//加载图片
/* 
从图片文件获取图像(bmp / gif / jpg / png / tif / emf / wmf / ico)
void loadimage(
	IMAGE * pDstImg,			// 保存图像的 IMAGE 对象指针
	LPCTSTR pImgFile,		// 图片文件名
	int nWidth = 0,			// 图片的拉伸宽度
	int nHeight = 0,		// 图片的拉伸高度
	bool bResize = false	// 是否调整 IMAGE 的大小以适应图片
);
*/

加载:

cpp 复制代码
loadimage(&image[0], L"./image/blank.jpg", M, M);
loadimage(&image[1], L"./image/1.jpg", M, M);
loadimage(&image[2], L"./image/2.jpg", M, M);
loadimage(&image[3], L"./image/3.jpg", M, M);
loadimage(&image[4], L"./image/4.jpg", M, M);
loadimage(&image[5], L"./image/5.jpg", M, M);
loadimage(&image[6], L"./image/6.jpg", M, M);
loadimage(&image[7], L"./image/7.jpg", M, M);
loadimage(&image[8], L"./image/8.jpg", M, M);
loadimage(&image[9], L"./image/lei.jpg", M, M);
loadimage(&image[10], L"./image/tag.jpg", M, M);
loadimage(&image[11], L"./image/start.jpg", N * M, N * M);
loadimage(&image[12], L"./image/0.jpg", M, M);

1-3.处理背景图位置

放一张图片到窗口的函数是putimage,用法及参数如下:

cpp 复制代码
/*
// 绘制图像
void putimage(
    int dstX,				// 绘制位置的 x 坐标
    int dstY,				// 绘制位置的 y 坐标
    IMAGE *pSrcImg,			// 要绘制的 IMAGE 对象指针
    DWORD dwRop = SRCCOPY	// 三元光栅操作码
);
*/

把开始界面的背景图放到窗口正中间,也就是(0,0)的位置。

cpp 复制代码
putimage(0, 0, &image[11]);//将image[11]显示在屏幕上,坐标0,0

2.选择模式

2-1.获取鼠标信息

这个函数我试了好多个,目前我的版本**GetMouseMsg( )**是可以的。

cpp 复制代码
MOUSEMSG msg = { 0 };//鼠标事件信息
while (1) 
{
	msg = GetMouseMsg();//获取鼠标位置,存到msg里面
}

2-2.处理颜色变化

当鼠标放到某一个模式上时,我们让那个模式的字体颜色变红,更加真实美观。

cpp 复制代码
//判断鼠标位置是否在15雷模式上,如果在设置颜色为红色
if ((msg.x > 180 && msg.x < 320) &&( msg.y >250 && msg.y < 290))
{
	settextcolor(RGB(255, 0, 0));		//设置字体颜色红色
	outtextxy(180, 250, L"15雷模式");//输出 15雷模式 在x180,y250//outtextxyS
}
//判断鼠标位置是否在35雷模式上,如果在设置颜色为红色
else if ((msg.x > 180 && msg.x < 320) &&( msg.y >330 && msg.y < 370))
{
	settextcolor(RGB(255, 0, 0));		//设置字体颜色红色
	outtextxy(180, 330, L"35雷模式");//输出 35雷模式 在x180,y330
}
//判断鼠标位置是否在退出上,如果在设置颜色为红色
else if (msg.x > 180 && msg.x < 320 && msg.y >410 && msg.y < 450)
{
	settextcolor(RGB(255, 0, 0));		//设置字体颜色红色
	outtextxy(180, 410, L"退出");//输出 退出 在x180,y410
}
//判断鼠标位置是否在背景部分,如果在设置颜色为黑色
else
{
	settextcolor(RGB(0, 0, 0));		//设置字体颜色为黑色
	outtextxy(180, 250, L"15雷模式");
	outtextxy(180, 330, L"35雷模式");
	outtextxy(180, 410, L"退出");
}

2-3.判断鼠标按下的键

根据不同的坐标,执行不同的模式或者退出程序。

其中判断是否按下左键的函数是WM_LBUTTONDOWN,按下返回真,否则返回假。

cpp 复制代码
switch (msg.uMsg)
{
    case WM_LBUTTONDOWN://当点击鼠标左键后,判定鼠标坐标位置。
	//如果以下坐标区域则设置为正常模式0
	if (msg.x > 180 && msg.x < 320 && msg.y >250 && msg.y < 290) {
		gamemodel = 0;//将游戏模式设为0(15雷模式)
		return;
	}//如果以下坐标区域则设置为风控模式1
	else if (msg.x > 180 && msg.x < 320 && msg.y >330 && msg.y < 370) {
		gamemodel = 1;//将游戏模式设为1(35雷模式)
		return;
	}//如果以下坐标区域则退出游戏
	else if (msg.x > 180 && msg.x < 320 && msg.y >410 && msg.y < 450) {
		exit(0);//退出游戏(终止)
	}
}

普通模式

1.随机生成地雷

1-1.清空map数组,并播随机数种子。

cpp 复制代码
srand((unsigned)time(NULL));//播种随机数种子
for (int i = 0; i < N + 2; i++) 
{
	for (int j = 0; j < N + 2; j++)
	{
		map[i][j] = 0;//把该数组的位置归0
	}
}	

1-2.随机布雷

cpp 复制代码
while (flag < 15)//控制总雷数不超过15个
{
	x = rand() % 10 + 1;//随机生成在第几行(1-10行)
	y = rand() % 10 + 1;//随机生成在第几列(1-10列)
	//随机方格坐标
	string msgXY = "may[" + to_string(x) + "][" + to_string(y )+ "]"+"当前数值value is "+to_string(map[x][y]);

	if (map[x][y] != -1) //随机获取坐标为map[x][y]的值,判定是否不等于-1(-1表示雷),如果等于-1则表示该位置已经是雷。
	{
		map[x][y] = -1;//如果不等于-1,则赋值该位置为-1(布雷)
		flag++;//总雷数增加1个
		writeLogMsg("may[" + to_string(x) + "][" + to_string(y) + "]" + "当前数值value is " + to_string(map[x][y]) + "埋雷成功!!!累计埋雷个数===>" + to_string(flag));
	}
}

2.确定数字

遍历整个数组,看八个方向,有一个雷就+1。

cpp 复制代码
for (int i = 1; i <= N; i++) //扫描数组10X10显示部分
{
	//writeLogMsg("===扫描第【"+to_string(i)+"】行开始执行===");
	for (int j = 1; j <= N; j++)
	{
		squ_num++;
		if (map[i][j] != -1)//如果这个数组的位置不是-1即表示不是地雷
		{
			//writeLogMsg("==方格编号【"+ to_string(squ_num) +"】=扫描第map【" + to_string(i) + "】行第【" + to_string(j) + "】不是雷!!!开始扫描周围雷总数。当前map【】【】 valuse is==="+to_string(map[i][j]));
			for (int m = i - 1; m <= i + 1; m++) //扫描包含该数字周围的九个格子
			{
				for (int n = j - 1; n <= j + 1; n++)
				{
					if (map[m][n] == -1)
					{
						map[i][j]++;//确定除了雷每个格子中间数字,(也就是周围八个格子的总雷数)									
					}						
				}	
			}
			//writeLogMsg("==方格编号【" + to_string(squ_num) + "】=扫描第map【" + to_string(i) + "】行第【" + to_string(j) + "】不是雷,扫描周围雷总数共计map[i][j]值为==>" + to_string(map[i][j]) + "个");				
		}
        else
        {
			//writeLogMsg("==方格编号【" + to_string(squ_num) + "】=扫描第map【" + to_string(i) + "】行第【" + to_string(j) + "】是雷,不扫锚周围!map[i][j]值为==>" + to_string(map[i][j]) );
		}
	}
	//writeLogMsg("===扫描第【" + to_string(i) + "】行结束执行===");
}

3.判断输赢

这里用到了一个函数**DrawGraph( )**是用来画图显示的,下面会讲。

cpp 复制代码
while (1)
{
	whileCount++;
	writeLogMsg("===根据map[i][j]存放数值设置对应图片开始执行===");
	DrawGraph();//更新雷的图片,数字对应的图片
	writeLogMsg("===根据map[i][j]存放数值设置对应图片结束执行===");
	type = MouseClick();
	if (type == -1) //判断用户是否点到雷了
	{
		DrawGraph();
		if (MessageBox(hWnd, L"按下确定重玩", L"很遗憾失败!", MB_ICONINFORMATION |MB_OK) == IDOK) break;//输出提示框
	}
	if (win == 0)//判断用户是否赢了,胜利条件为除了雷的所有方块点完即可
	{
		DrawGraph();
		if (MessageBox(hWnd, L"按下确定重玩", L"恭喜成功!", MB_ICONINFORMATION |MB_OK) == IDOK) break;//输出提示框
	}
	writeLogMsg("===type = MouseClick()当前type值===》" + to_string(type));
	writeLogMsg("===win当前win值===》" + to_string(win));
	writeLogMsg("===设置图片循环次数===>" + to_string(whileCount));
}

画图

这个函数是程序的精髓,特别难理解,一定要多推敲推敲!

cpp 复制代码
int count_num=0;//计数器方格数
for (int i = 1; i <= N; i++)
{
	for (int j = 1; j <= N; j++) {
		count_num++;
		//writeLogMsg("DrawGraph==>当前扫描第【"+ to_string(count_num) +"】map【" + to_string(i) + "】行第【" + to_string(j) + "】map[i][j]值为 ==> " + to_string(map[i][j]));
		switch (map[i][j])
		{
		case 9:putimage((i - 1) * M, (j - 1) * M, &image[9]); break;
		case 10:putimage((i - 1) * M, (j - 1) * M, &image[0]); break;
		case 11:putimage((i - 1) * M, (j - 1) * M, &image[1]); break;
		case 12:putimage((i - 1) * M, (j - 1) * M, &image[2]); break;
		case 13:putimage((i - 1) * M, (j - 1) * M, &image[3]); break;
		case 14:putimage((i - 1) * M, (j - 1) * M, &image[4]); break;
		case 15:putimage((i - 1) * M, (j - 1) * M, &image[5]); break;
		case 16:putimage((i - 1) * M, (j - 1) * M, &image[6]); break;
		case 17:putimage((i - 1) * M, (j - 1) * M, &image[7]); break;
		case 18:putimage((i - 1) * M, (j - 1) * M, &image[8]); break;
		case 29:putimage((i - 1) * M, (j - 1) * M, &image[10]); break;
		case 30:putimage((i - 1) * M, (j - 1) * M, &image[10]); break;
		case 31:putimage((i - 1) * M, (j - 1) * M, &image[10]); break;
		case 32:putimage((i - 1) * M, (j - 1) * M, &image[10]); break;
		case 33:putimage((i - 1) * M, (j - 1) * M, &image[10]); break;
		case 34:putimage((i - 1) * M, (j - 1) * M, &image[10]); break;
		case 35:putimage((i - 1) * M, (j - 1) * M, &image[10]); break;
		case 36:putimage((i - 1) * M, (j - 1) * M, &image[10]); break;
		case 37:putimage((i - 1) * M, (j - 1) * M, &image[10]); break;
		case 38:putimage((i - 1) * M, (j - 1) * M, &image[10]); break;
		default:putimage((i - 1) * M, (j - 1) * M, &image[12]); break;
		}
	}
}

递归函数

通过不断地递归(调用自己),来实现点一大片的情况。

cpp 复制代码
void loadingPlay(int x, int y) 
{
	map[x][y] += 10;
	win--;
	for (int i = x - 1; i <= x + 1; i++) {
		for (int j = y - 1; j <= y + 1; j++) {
			if (i <= 0 || i >= 11 || j <= 0 || j >= 11)  continue; //防止越界
			if (map[i][j] <= 8) {
				if (map[i][j] == 0) {
					loadingPlay(i, j);
				}
				else if (map[i][j] != -1) {
					map[i][j] += 10;
					win--;
				}
			}
		}
	}
}

困难模式

思路和普通模式一样,就不解析了,有注释。

cpp 复制代码
{
	while (1)
	{
		cleardevice();//清屏
		win = N * N - 35;//设置胜利条件为把除了雷的所有方块点完即可
		int type = 0;
		HWND hWnd = GetHWnd();//这个函数用于获取绘图窗口句柄
		int x, y, flag = 0;
		srand((unsigned)time(NULL));//播种随机数种子
		//通过双重for循环把所有数组归零
		for (int i = 0; i < N + 2; i++) 
		{
			for (int j = 0; j < N + 2; j++) 
			{
				map[i][j] = 0;//把该数组的位置归0
			}
		}
		//布雷循环
		while (flag < 35)//控制总雷数不超过35个
		{
			x = rand() % 10 + 1;//随机生成在第几行
			y = rand() % 10 + 1;//随机生成在第几列
			if (map[x][y] != -1) //为了判断所生成的雷有没有重复
			{
				map[x][y] = -1;//将此数组设为-1(-1表示雷)
				flag++;//总雷数增加1个
			}
		}
		//布数字
		for (int i = 1; i <= N; i++) //扫描数组10X10显示部分
		{
			for (int j = 1; j <= N; j++)
			{
				if (map[i][j] != -1)//如果这个数组的位置不是-1(地雷)
				{
					for (int m = i - 1; m <= i + 1; m++) //扫描包含该数字周围的九个格子
					{
						for (int n = j - 1; n <= j + 1; n++)
						{
							if (map[m][n] == -1)
							{
								map[i][j]++;//确定除了雷每个格子中间数字,(也就是周围八个格子的总雷数)
							}
						}
					}
				}
			}
		}
		//判断输赢
		while (1)
		{
			DrawGraph();
			type = MouseClick();
			if (type == -1) //判断用户是否点到雷了
			{
				DrawGraph();
				if (MessageBox(hWnd, L"按下确定重玩", L"很遗憾失败", MB_ICONINFORMATION|MB_OK) == IDOK) break;//输出提示框
			}
			if (win == 0)//判断用户是否赢了 
			{
				DrawGraph();
				if (MessageBox(hWnd, L"按下确定重玩", L"恭喜成功", MB_ICONINFORMATION| MB_OK) == IDOK) break;//输出提示框
			}
		}
	}
}

生成日志

1.获取时间

用到函数SYSTEMTIME,可以调取年月日时秒分。

2.输出内容

你想看到的后台调试信息都可以放进去。

cpp 复制代码
for (int i = 0; i < count; i++) 
{
	cout << "log.txt  【Write】===>第" << i <<"行" << endl;
	myFile << "【" << sys.wYear <<"-"<< sys.wMonth << "-"<< sys.wDay << "    "<< sys.wHour << ":"<< sys.wMinute << ":"<< sys.wSecond << "."<< sys.wMilliseconds << "星期" << sys.wDayOfWeek << "】" << "\t" << squ[0] << "\t" << squ[1] << "\t" << squ[2] << "\t" << squ[3] << endl;
}

完整源代码

cpp 复制代码
#include<iostream>//C++头文件
#include<time.h>//时间函数
#include<stdio.h>//标准输出输入库
#include<graphics.h>			//图形库   EasyX 是EasyX Graphics Library 是针对 Visual C++ 的免费绘图库,支持 VC6.0 ~VC2022,简单易用,学习成本极低,应用领域广泛
#include<mmsystem.h>//windows SDK 播放函数

#include <fstream>  //文件库
#include <windows.h>//windows api
#include<string>//

#pragma comment(lib,"winmm.lib")	
#define N 10  //格子数
#define M 50  //一个格子的像素
using namespace std;//命名空间

IMAGE image[13];		//存放图片数量为13张
int map[N + 2][N + 2];	//定义整形12行12列二维数组
int gamemodel;//定义游戏模式
int win = N * N - 15;
void StartWindow();		//开始界面
void normalModel();     //15雷模式
void BTmodel();			//35雷模式
void DrawGraph();		//画扫雷地图
int MouseClick();	    //鼠标点击事件
void loadingPlay(int x, int y);	//定义loadingPlay函数,为了运用递归实现点击一大片

//void writeLog(int a [],int b);
void writeLogMsg(string msgStr);
int map_num;//map格子编号
int SquareInfo[4];//定义单个方格信息整型数组 用来存在单方格的基本信息。包括方格编号,方格数值,方格x,y坐标值。
int squ_num;//方格编号 从左往右,从第一行到最后一行计数编号。
int squ_value;//方格存放数值
int index_x;//定义格子坐标 X表示横,Y表示纵
int index_y;
int Square[100][4];//存放100个方格基本信息


int  main() {
	writeLogMsg("===天天扫雷=开始执行===");
	writeLogMsg("===开始界面=开始执行===");
	//initgraph(N * M, N * M,EW_SHOWCONSOLE);//, EW_SHOWCONSOLE初始化绘图窗口宽500高500像素
	initgraph(N * M, N * M);//初始化绘图窗口宽500高500像素
	

	//测试日志
	//int log[4] = {0,1,2,3};
	//writeLog(log, 9);
	writeLogMsg("===开始界面=开始执行===");
	StartWindow();//调用开始界面函数。
	writeLogMsg("===开始界面=结束执行===");
	//1.设置开始页面:背景透明、字体、背景音乐、背景图片,2.加载待使用的图片列表。3.判定鼠标位置和点击事件确定,用户选择的是15雷模式还是35雷模式
	if (gamemodel == 0) {
		normalModel();//15雷模式
	}
	else {
		BTmodel();//35雷模式
	}
	closegraph();
	
	writeLogMsg("===天天扫雷=结束执行===");
}
void StartWindow() {

	writeLogMsg("===设置界面背景、字体=开始执行===");
	setbkmode(TRANSPARENT);		 //设置背景 透明风格
	settextstyle(40, 18, L"楷体");//设置开始界面字体大小 L设置字符集
	//初始化页面
	//播放音乐
	writeLogMsg("===设置界面背景、字体=结束执行===");
	writeLogMsg("===设置播放背景音乐=开始执行===");
	mciSendString(L"open ./天空之城.mp3 alias bgm", 0, 0, 0); //L为设置字符集,./表示当前文件夹
	mciSendString(L"play bgm repeat", 0, 0, 0);//重复播放bgm
	writeLogMsg("===设置播放背景音乐=结束执行===");
	//加载图片
	/* 
	从图片文件获取图像(bmp / gif / jpg / png / tif / emf / wmf / ico)
	void loadimage(
		IMAGE * pDstImg,			// 保存图像的 IMAGE 对象指针
		LPCTSTR pImgFile,		// 图片文件名
		int nWidth = 0,			// 图片的拉伸宽度
		int nHeight = 0,		// 图片的拉伸高度
		bool bResize = false	// 是否调整 IMAGE 的大小以适应图片
	);
	*/
	writeLogMsg("===加载图片资源=开始执行===");
	loadimage(&image[0], L"./image/blank.jpg", M, M);
	loadimage(&image[1], L"./image/1.jpg", M, M);
	loadimage(&image[2], L"./image/2.jpg", M, M);
	loadimage(&image[3], L"./image/3.jpg", M, M);
	loadimage(&image[4], L"./image/4.jpg", M, M);
	loadimage(&image[5], L"./image/5.jpg", M, M);
	loadimage(&image[6], L"./image/6.jpg", M, M);
	loadimage(&image[7], L"./image/7.jpg", M, M);
	loadimage(&image[8], L"./image/8.jpg", M, M);
	loadimage(&image[9], L"./image/lei.jpg", M, M);
	loadimage(&image[10], L"./image/tag.jpg", M, M);
	loadimage(&image[11], L"./image/start.jpg", N * M, N * M);
	loadimage(&image[12], L"./image/0.jpg", M, M);
	writeLogMsg("===加载图片资源=开始执行===");
	/*
	// 绘制图像
void putimage(
	int dstX,				// 绘制位置的 x 坐标
	int dstY,				// 绘制位置的 y 坐标
	IMAGE *pSrcImg,			// 要绘制的 IMAGE 对象指针
	DWORD dwRop = SRCCOPY	// 三元光栅操作码
);

	*/
	writeLogMsg("===设置开始界面显示图片=开始执行===");
	putimage(0, 0, &image[11]);//将image[11]显示在屏幕上,坐标0,0
	writeLogMsg("===设置开始界面显示图片=结束执行===");

	writeLogMsg("===获取鼠标事件信息、设置15、35雷模式字体,坐标区域=开始执行===");
	MOUSEMSG msg = { 0 };//鼠标事件信息
	while (1) 
	{
		msg = GetMouseMsg();//获取鼠标位置,存到msg里面
		//判断鼠标位置是否在15雷模式上,如果在设置颜色为红色
		if ((msg.x > 180 && msg.x < 320) &&( msg.y >250 && msg.y < 290))
		{
			settextcolor(RGB(255, 0, 0));		//设置字体颜色红色
			outtextxy(180, 250, L"15雷模式");//输出 15雷模式 在x180,y250//outtextxyS
		}
		//判断鼠标位置是否在35雷模式上,如果在设置颜色为红色
		else if ((msg.x > 180 && msg.x < 320) &&( msg.y >330 && msg.y < 370))
		{
			settextcolor(RGB(255, 0, 0));		//设置字体颜色红色
			outtextxy(180, 330, L"35雷模式");//输出 35雷模式 在x180,y330
		}
		//判断鼠标位置是否在退出上,如果在设置颜色为红色
		else if (msg.x > 180 && msg.x < 320 && msg.y >410 && msg.y < 450)
		{
			settextcolor(RGB(255, 0, 0));		//设置字体颜色红色
			outtextxy(180, 410, L"退出");//输出 退出 在x180,y410
		}
		//判断鼠标位置是否在背景部分,如果在设置颜色为黑色
		else
		{
			settextcolor(RGB(0, 0, 0));		//设置字体颜色为黑色
			outtextxy(180, 250, L"15雷模式");
			outtextxy(180, 330, L"35雷模式");
			outtextxy(180, 410, L"退出");
		}
		switch (msg.uMsg)
		{
			case WM_LBUTTONDOWN://当点击鼠标左键后,判定鼠标坐标位置。
			//如果以下坐标区域则设置为正常模式0
			if (msg.x > 180 && msg.x < 320 && msg.y >250 && msg.y < 290) {
				gamemodel = 0;//将游戏模式设为0(15雷模式)
				return;
			}//如果以下坐标区域则设置为风控模式1
			else if (msg.x > 180 && msg.x < 320 && msg.y >330 && msg.y < 370) {
				gamemodel = 1;//将游戏模式设为1(35雷模式)
				return;
			}//如果以下坐标区域则退出游戏
			else if (msg.x > 180 && msg.x < 320 && msg.y >410 && msg.y < 450) {
				exit(0);//退出游戏(终止)
			}
		}
	}
	writeLogMsg("===获取鼠标事件信息、设置15、35雷模式字体,坐标区域=结束执行===");
}
void normalModel()//15雷模式(普通模式)
{

	writeLogMsg("===15雷模式=开始执行===");
	while (1)
	{
		cleardevice();//清屏
		win = N * N - 15;//设置胜利条件为把除了雷的所有方块点完即可
		int type = 0;
		HWND hWnd = GetHWnd();//GetHWnd()这个函数用于获取绘图窗口句柄
		int x, y, flag = 0;
		srand((unsigned)time(NULL));//播种随机数种子
		writeLogMsg("===初始化map[][]每个坐标的值开始执行===");
		//通过双重for循环把所有数组归零
		for (int i = 0; i < N + 2; i++) 
		{
			for (int j = 0; j < N + 2; j++)
			{
				map[i][j] = 0;//把该数组的位置归0
			}
			
		}
		
		writeLogMsg("===初始化map[][]每个坐标的值结束执行===");
		//随机布雷
		writeLogMsg("===随机布雷开始执行===");
		while (flag < 15)//控制总雷数不超过15个
		{
			x = rand() % 10 + 1;//随机生成在第几行(1-10行)
			y = rand() % 10 + 1;//随机生成在第几列(1-10列)

			//随机方格坐标
			string msgXY = "may[" + to_string(x) + "][" + to_string(y )+ "]"+"当前数值value is "+to_string(map[x][y]);
		
			if (map[x][y] != -1) //随机获取坐标为map[x][y]的值,判定是否不等于-1(-1表示雷),如果等于-1则表示该位置已经是雷。
			{
				map[x][y] = -1;//如果不等于-1,则赋值该位置为-1(布雷)
				
				flag++;//总雷数增加1个
				writeLogMsg("may[" + to_string(x) + "][" + to_string(y) + "]" + "当前数值value is " + to_string(map[x][y]) + "埋雷成功!!!累计埋雷个数===>" + to_string(flag));
				
			}
		

		}
		writeLogMsg("===随机布雷结束执行===");
		writeLogMsg("===计算每个方格周围雷数量开始执行===");
		//计算除了已布雷的格子之外,每个格子周围雷的数量并确定数字
		for (int i = 1; i <= N; i++) //扫描数组10X10显示部分
		{

			//writeLogMsg("===扫描第【"+to_string(i)+"】行开始执行===");
			for (int j = 1; j <= N; j++)
			{
				squ_num++;
				
				if (map[i][j] != -1)//如果这个数组的位置不是-1即表示不是地雷
				{
					
					//writeLogMsg("==方格编号【"+ to_string(squ_num) +"】=扫描第map【" + to_string(i) + "】行第【" + to_string(j) + "】不是雷!!!开始扫描周围雷总数。当前map【】【】 valuse is==="+to_string(map[i][j]));

					for (int m = i - 1; m <= i + 1; m++) //扫描包含该数字周围的九个格子
					{

						for (int n = j - 1; n <= j + 1; n++)
						{
							
							if (map[m][n] == -1)
							{
								map[i][j]++;//确定除了雷每个格子中间数字,(也就是周围八个格子的总雷数)
																	
							}
													
						}
						
					}
					//writeLogMsg("==方格编号【" + to_string(squ_num) + "】=扫描第map【" + to_string(i) + "】行第【" + to_string(j) + "】不是雷,扫描周围雷总数共计map[i][j]值为==>" + to_string(map[i][j]) + "个");
								
				}else{
					//writeLogMsg("==方格编号【" + to_string(squ_num) + "】=扫描第map【" + to_string(i) + "】行第【" + to_string(j) + "】是雷,不扫锚周围!map[i][j]值为==>" + to_string(map[i][j]) );
				}
				
			}
			//writeLogMsg("===扫描第【" + to_string(i) + "】行结束执行===");
		}
		writeLogMsg("===计算每个方格周围雷数量结束执行===");
		//判断输赢
		int whileCount = 0;
		
		while (1)
		{
			whileCount++;
			writeLogMsg("===根据map[i][j]存放数值设置对应图片开始执行===");
			DrawGraph();//更新雷的图片,数字对应的图片
			writeLogMsg("===根据map[i][j]存放数值设置对应图片结束执行===");
			
			type = MouseClick();
			
			
			if (type == -1) //判断用户是否点到雷了
			{
				DrawGraph();
				
				if (MessageBox(hWnd, L"按下确定重玩", L"很遗憾失败!", MB_ICONINFORMATION |MB_OK) == IDOK) break;//输出提示框
			}
			if (win == 0)//判断用户是否赢了,胜利条件为除了雷的所有方块点完即可
			{
				DrawGraph();
				if (MessageBox(hWnd, L"按下确定重玩", L"恭喜成功!", MB_ICONINFORMATION |MB_OK) == IDOK) break;//输出提示框
			}
			writeLogMsg("===type = MouseClick()当前type值===》" + to_string(type));
			writeLogMsg("===win当前win值===》" + to_string(win));
			writeLogMsg("===设置图片循环次数===>" + to_string(whileCount));
		}
	}
	writeLogMsg("===15雷模式=结束执行===");
}
void BTmodel() //35雷模式(疯狂模式)
{
	while (1)
	{
		cleardevice();//清屏
		win = N * N - 35;//设置胜利条件为把除了雷的所有方块点完即可
		int type = 0;
		HWND hWnd = GetHWnd();//这个函数用于获取绘图窗口句柄
		int x, y, flag = 0;
		srand((unsigned)time(NULL));//播种随机数种子
		//通过双重for循环把所有数组归零
		for (int i = 0; i < N + 2; i++) 
		{
			for (int j = 0; j < N + 2; j++) 
			{
				map[i][j] = 0;//把该数组的位置归0
			}
		}
		//布雷循环
		while (flag < 35)//控制总雷数不超过35个
		{
			x = rand() % 10 + 1;//随机生成在第几行
			y = rand() % 10 + 1;//随机生成在第几列
			if (map[x][y] != -1) //为了判断所生成的雷有没有重复
			{
				map[x][y] = -1;//将此数组设为-1(-1表示雷)
				flag++;//总雷数增加1个
			}
		}
		//布数字
		for (int i = 1; i <= N; i++) //扫描数组10X10显示部分
		{
			for (int j = 1; j <= N; j++)
			{
				if (map[i][j] != -1)//如果这个数组的位置不是-1(地雷)
				{
					for (int m = i - 1; m <= i + 1; m++) //扫描包含该数字周围的九个格子
					{
						for (int n = j - 1; n <= j + 1; n++)
						{
							if (map[m][n] == -1)
							{
								map[i][j]++;//确定除了雷每个格子中间数字,(也就是周围八个格子的总雷数)
							}
						}
					}
				}
			}
		}
		//判断输赢
		while (1)
		{
			DrawGraph();
			type = MouseClick();
			if (type == -1) //判断用户是否点到雷了
			{
				DrawGraph();
				if (MessageBox(hWnd, L"按下确定重玩", L"很遗憾失败", MB_ICONINFORMATION|MB_OK) == IDOK) break;//输出提示框
			}
			if (win == 0)//判断用户是否赢了 
			{
				DrawGraph();
				if (MessageBox(hWnd, L"按下确定重玩", L"恭喜成功", MB_ICONINFORMATION| MB_OK) == IDOK) break;//输出提示框
			}
		}
	}
}
void DrawGraph()
{
	int count_num=0;//计数器方格数

	for (int i = 1; i <= N; i++)
	{
		for (int j = 1; j <= N; j++) {
			count_num++;
			//writeLogMsg("DrawGraph==>当前扫描第【"+ to_string(count_num) +"】map【" + to_string(i) + "】行第【" + to_string(j) + "】map[i][j]值为 ==> " + to_string(map[i][j]));
			switch (map[i][j])
			{

			case 9:putimage((i - 1) * M, (j - 1) * M, &image[9]); break;
			case 10:putimage((i - 1) * M, (j - 1) * M, &image[0]); break;
			case 11:putimage((i - 1) * M, (j - 1) * M, &image[1]); break;
			case 12:putimage((i - 1) * M, (j - 1) * M, &image[2]); break;
			case 13:putimage((i - 1) * M, (j - 1) * M, &image[3]); break;
			case 14:putimage((i - 1) * M, (j - 1) * M, &image[4]); break;
			case 15:putimage((i - 1) * M, (j - 1) * M, &image[5]); break;
			case 16:putimage((i - 1) * M, (j - 1) * M, &image[6]); break;
			case 17:putimage((i - 1) * M, (j - 1) * M, &image[7]); break;
			case 18:putimage((i - 1) * M, (j - 1) * M, &image[8]); break;
			case 29:putimage((i - 1) * M, (j - 1) * M, &image[10]); break;
			case 30:putimage((i - 1) * M, (j - 1) * M, &image[10]); break;
			case 31:putimage((i - 1) * M, (j - 1) * M, &image[10]); break;
			case 32:putimage((i - 1) * M, (j - 1) * M, &image[10]); break;
			case 33:putimage((i - 1) * M, (j - 1) * M, &image[10]); break;
			case 34:putimage((i - 1) * M, (j - 1) * M, &image[10]); break;
			case 35:putimage((i - 1) * M, (j - 1) * M, &image[10]); break;
			case 36:putimage((i - 1) * M, (j - 1) * M, &image[10]); break;
			case 37:putimage((i - 1) * M, (j - 1) * M, &image[10]); break;
			case 38:putimage((i - 1) * M, (j - 1) * M, &image[10]); break;
			default:putimage((i - 1) * M, (j - 1) * M, &image[12]); break;
			}
		}
	}
}
int MouseClick()
{
	

	MOUSEMSG msg = { 0 };
	int loadingPlayCout = 0;
	while (1) {
		
		msg = GetMouseMsg();
		
		writeLogMsg("===MouseClick循环while开始执行===msg.x==》【" +to_string( msg.x)+"】msg.y==》【"+ to_string(msg.y)+"】");
		switch (msg.uMsg)
		{
		case WM_LBUTTONDOWN:
			writeLogMsg("===msg.uMsg.WM_LBUTTONDOWN===正在点击左键");
			if (map[msg.x / M + 1][msg.y / M + 1] == 0) {
				writeLogMsg("===map【"+to_string( msg.x / M) +"】【"+ to_string(msg.y / M) +"】的值是===>"+to_string(map[msg.x / M][msg.x / M]));
				writeLogMsg("当前方格周围没有雷!!!");
				writeLogMsg("开始加载loadingPlay  没有雷更换为无雷的图片递归循环判断!");
				loadingPlay(msg.x / M + 1, msg.y / M + 1);
				loadingPlayCout++;
				writeLogMsg("当前loadingPlayCout的值得是===>" + to_string(loadingPlayCout));
				writeLogMsg("结束加载loadingPlay");
			}
			else if (map[msg.x / M + 1][msg.y / M + 1] <= 8) {
				writeLogMsg("当前方格周围有雷!!!");
				writeLogMsg("map[][]<=8开始执行");
				writeLogMsg("===map【" + to_string(msg.x / M) + "】【" + to_string(msg.y / M) + "】的值是===>" + to_string(map[msg.x / M][msg.x / M]));
				map[msg.x / M + 1][msg.y / M + 1] += 10;
				win--;
				writeLogMsg("当前win的值是==>"+to_string(win));
				writeLogMsg("map[][]<=8结束执行");
			}
			if (map[msg.x / M + 1][msg.y / M + 1] == 9) {
				writeLogMsg("map[][]等于9");
				return -1;
			}
			break;
		case WM_RBUTTONDOWN:
			writeLogMsg("===msg.uMsg.WM_LBUTTONDOWN===正在点击右键");
			if (map[msg.x / M + 1][msg.y / M + 1] <= 8) {
				writeLogMsg("map[][]<=8开始执行");
				map[msg.x / M + 1][msg.y / M + 1] += 30;
			}
			else if (map[msg.x / M + 1][msg.y / M + 1] >= 29) {
				writeLogMsg("map[][]>=29开始执行");
				map[msg.x / M + 1][msg.y / M + 1] -= 30;
			}
			break;
		}
		writeLogMsg("===MouseClick循环while结束执行===" );

		return 0;
	}
	
}
void loadingPlay(int x, int y) {
	
	
	
	map[x][y] += 10;
	win--;
	for (int i = x - 1; i <= x + 1; i++) {
		for (int j = y - 1; j <= y + 1; j++) {
			if (i <= 0 || i >= 11 || j <= 0 || j >= 11)  continue; //防止越界
			if (map[i][j] <= 8) {
				if (map[i][j] == 0) {
					loadingPlay(i, j);
				}
				else if (map[i][j] != -1) {
					map[i][j] += 10;
					win--;
				}
			}
		}
	}
	
}
 void writeLog(int squ[], int n){
	 //获取当前系统时间
	 SYSTEMTIME sys;
	 GetLocalTime(&sys);
	fstream myFile;
	myFile.open("log.txt", ios::out | ios::binary);
	if (!myFile) {
		cout << "log.txt can't open!" << endl;
		abort();
	}
	int count = n;
	myFile << "累计行数===>"<<count << endl;
	
	for (int i = 0; i < count; i++) {
		cout << "log.txt  【Write】===>第" << i <<"行" << endl;

		myFile << "【" << sys.wYear <<"-"<< sys.wMonth << "-"<< sys.wDay << "    "<< sys.wHour << ":"<< sys.wMinute << ":"<< sys.wSecond << "."<< sys.wMilliseconds << "星期" << sys.wDayOfWeek << "】" << "\t" << squ[0] << "\t" << squ[1] << "\t" << squ[2] << "\t" << squ[3] << endl;

		
	}
	myFile.close();
}

 void writeLogMsg(string msg) {
	 //获取当前系统时间
	 SYSTEMTIME sys;
	 GetLocalTime(&sys);
	 fstream myFile;
	 myFile.open("log.txt", ios::out | ios::app);
	 if (!myFile) {
		 cout << "log.txt can't open!" << endl;
		 abort();
	 }
	 
		 myFile << "【" << sys.wYear << "-" << sys.wMonth << "-" << sys.wDay << "    " << sys.wHour << ":" << sys.wMinute << ":" << sys.wSecond << "." << sys.wMilliseconds << "   星期" << sys.wDayOfWeek << "】" << "\t" << msg << endl;
	     myFile.close();


	 /*
 #include<fstream>
#include<iostream>
using namespace std;
int main()
{
	fstream f;
	//追加写入,在原来基础上加了ios::app 
	f.open("data.txt",ios::out|ios::app);
	//输入你想写入的内容 
	f<<"今天天气不错"<<endl;
	f.close();
	return 0;
}

	 */

 }

回顾编程过程

今天,我们一起探索了奇妙的扫雷游戏。运用到了很多知识点,有我们的老朋友递归,也有我们的新朋友日志。本篇文章制作不易,断更3周都在写这一篇。

本篇文章共20198字,真得不值得三连吗?

相关推荐
羊小猪~~3 分钟前
数据结构C语言描述2(图文结合)--有头单链表,无头单链表(两种方法),链表反转、有序链表构建、排序等操作,考研可看
c语言·数据结构·c++·考研·算法·链表·visual studio
binishuaio9 分钟前
Java 第11天 (git版本控制器基础用法)
java·开发语言·git
zz.YE11 分钟前
【Java SE】StringBuffer
java·开发语言
就是有点傻15 分钟前
WPF中的依赖属性
开发语言·wpf
洋24024 分钟前
C语言常用标准库函数
c语言·开发语言
进击的六角龙25 分钟前
Python中处理Excel的基本概念(如工作簿、工作表等)
开发语言·python·excel
wrx繁星点点26 分钟前
状态模式(State Pattern)详解
java·开发语言·ui·设计模式·状态模式
NoneCoder44 分钟前
Java企业级开发系列(1)
java·开发语言·spring·团队开发·开发
苏三有春44 分钟前
PyQt5实战——UTF-8编码器功能的实现(六)
开发语言·qt
Aniay_ivy1 小时前
深入探索 Java 8 Stream 流:高效操作与应用场景
java·开发语言·python