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

news2025/6/19 14:49:34

目录

效果展示

游玩链接(无需安装图形库及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.头文件

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

#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为每格像素边长。

#define N 10  //格子数
#define M 50  //一个格子的像素

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

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

3.主函数框架

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

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

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

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.设置背景颜色及字体

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

setbkmode(TRANSPARENT); //设置背景 透明风格

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

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

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

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

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

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

//加载图片
/* 
从图片文件获取图像(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 的大小以适应图片
);
*/

 加载:

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,用法及参数如下:

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

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

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

2.选择模式 

2-1.获取鼠标信息

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

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

2-2.处理颜色变化

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

//判断鼠标位置是否在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,按下返回真,否则返回假。

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数组,并播随机数种子。

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.随机布雷

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。

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( )是用来画图显示的,下面会讲。

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));
}

画图 

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

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;
		}
	}
}

递归函数 

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

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--;
				}
			}
		}
	}
}

困难模式

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

{
	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.输出内容

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

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;
}

完整源代码 

#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字,真得不值得三连吗?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1503505.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构从入门到精通——队列

队列 前言一、队列1.1队列的概念及结构1.2队列的实现1.3队列的实现1.4扩展 二、队列面试题三、队列的具体实现代码Queue.hQueue.ctest.c队列的初始化队列的销毁入队列出队列返回队头元素返回队尾元素检测队列是否为空检测元素个数 前言 队列是一种特殊的线性数据结构&#xff…

141 Linux 系统编程18 ,线程,线程实现原理,ps –Lf 进程 查看

一 线程概念 什么是线程 LWP&#xff1a;light weight process 轻量级的进程&#xff0c;本质仍是进程(在Linux环境下) 进程&#xff1a;独立地址空间&#xff0c;拥有PCB 线程&#xff1a;有独立的PCB&#xff0c;但没有独立的地址空间(共享) 区别&#xff1a;在于是否共…

1、Ajax、get、post、ajax,随机颜色

一、Ajax初始 1、什么是Ajax&#xff1f; 异步的JavaScript和xml 2、xml是什么&#xff1f; 一种标记语言&#xff0c;传输和存储数据----------现在用JSON传输数据 3、Ajax的作用 局部加载 可以使网页异步更新 4、Ajax的原理或者步骤(6步) 创建Ajax对象 if (window.X…

【BUG】删除git本地仓库/远程仓库历史版本中的大文件以减少仓库大小

目录 前言排查过程解决过程通过如下命令查找历史大文件删除大文件&#xff0c;重构历史版本提交添加.ignore文件 总结 前言 某天像往常一样提交代码到gitee仓库&#xff0c;出现代码提交卡住不到的情况 2. 我百思不得姐&#xff0c;坚信bug会自己修复自己的原则等待了5分钟&am…

Python 一步一步教你用pyglet仿制鸿蒙系统里的时钟

目录 鸿蒙时钟 1. 绘制圆盘 2. 创建表类 3. 绘制刻度 4. 刻度数值 5. 添加指针 6. 转动指针 7. 联动时间 8. 时钟走动 鸿蒙时钟 本篇将用python pyglet库复刻华为手机鸿蒙系统闹钟程序的时钟&#xff0c;先在上图中抓取出时分秒针及刻度、表盘的颜色RGB值&#xff1a…

分布式解决方案

目录 1. 分布式ID1-1. 传统方案1-2. 分布式ID特点1-3. 实现方案1-4. 开源组件 1. 分布式ID 1-1. 传统方案 时间戳UUID 1-2. 分布式ID特点 全局唯一高并发高可用 1-3. 实现方案 方案总结&#xff1a; 号段模式 有两台服务器&#xff0c;给第一台服务器分配0-100&#xff0…

考虑局部遮阴的光伏PSO-MPPT控制MATLAB仿真

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 简介 光伏电池阵列的输出特性曲线不是线性变化的。当光伏电池遮荫时&#xff0c;产生的功 率会不断变化&#xff0c;致使光伏电池阵列的输出功率不断变化&#xff0c;其输出特性曲线呈现多峰值的现象。 多峰…

外包干了30天,技术明显退步。。

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 这次来聊一个大家可能也比较关心的问题&#xff0c;那就是就业城市选择的问题。而谈到这个问题&a…

简单BFF架构设计

又到周五了有了一个小时的闲暇时间简单写点东西&#xff0c;介绍一个简单的BFF的架构。BFF:Backends For Frontends,其实现在是个比较常见的前端架构设计的方案&#xff0c;其最大的优势便在于前端可以高度自由的在Node层做一些server端才可以做的东西&#xff0c;比如SSR、登录…

Day24:安全开发-PHP应用文件管理模块显示上传黑白名单类型过滤访问控制

目录 文件管理模块-上传-过滤机制 文件管理模块-显示-过滤机制 思维导图 PHP知识点 功能&#xff1a;新闻列表&#xff0c;会员中心&#xff0c;资源下载&#xff0c;留言版&#xff0c;后台模块&#xff0c;模版引用&#xff0c;框架开发等 技术&#xff1a;输入输出&#…

示波器探头的使用

无源探头(Tektronix P2220) 阻抗&#xff1a;1Mhz 衰减&#xff1a;10:1/1:1(与探头上的档位X10/X1相关&#xff0c;如果探头没有档位默认为10:1) 探头型号&#xff1a;电压 高压差分探头&#xff08;Tektronix P5200A) 阻抗&#xff1a;1Mhz 衰减&#xff1a;50:1/500:1(…

深入理解python之self

首先明确的是self只有在类的方法中才会有&#xff0c;独立的函数或方法是不必带有self的。self在定义类的方法时是必须有的&#xff0c;虽然在调用时不必传入相应的参数。 self名称不是必须的&#xff0c;在python中self不是关键词&#xff0c;你可以定义成a或b或其它名字都可…

Qt 定时器事件

文章目录 1 定时器事件1.1 界面布局1.2 关联信号槽1.3 重写timerEvent1.4 实现槽函数 启动定时器 2 定时器类 项目完整的源代码 QT中使用定时器&#xff0c;有两种方式&#xff1a; 定时器类&#xff1a;QTimer定时器事件&#xff1a;QEvent::Timer&#xff0c;对应的子类是QTi…

Vue.js+SpringBoot开发大学计算机课程管理平台

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 实验课程档案模块2.2 实验资源模块2.3 学生实验模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 实验课程档案表3.2.2 实验资源表3.2.3 学生实验表 四、系统展示五、核心代码5.1 一键生成实验5.2 提交实验5.3 批阅实…

Clock Verification IP

Clock Verification IP IP 参数及接口 IP 例化界面 相关函数 start_clock //产生时钟 <hierarchy_path>.IF.start_clockstop_clock //停止时钟 <hierarchy_path>.IF.stop_clockset_initial_value //设置时钟初始值为 0 <hierarchy_path>IF.set_initia…

Solidity攻击合约:“被偷走的资金”

在以太坊智能合约开发中&#xff0c;Solidity是最常用的编程语言。然而&#xff0c;由于代码编写不当或缺乏安全意识&#xff0c;合约可能面临各种攻击。本文将通过一个简单的Solidity合约示例&#xff0c;展示一个潜在的攻击合约&#xff0c;并分析其相对于原本合约的危害以及…

TS项目实战三:Express实现登录注册功能后端

使用express实现用户登录注册功能&#xff0c;使用ts进行代码开发&#xff0c;使用mysql作为数据库&#xff0c;实现用户登录、登录状态检测、验证码获取接口及用户注册相关接口功能的实现。 源码下载&#xff1a;[点击下载] (https://download.csdn.net/download/m0_37631110/…

设计模式-行为型模式-观察者模式

观察者模式定义了一种一对多的依赖关系&#xff0c;让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时&#xff0c;会通知所有观察者对象&#xff0c;使它们能够自动更新自己。[DP] //首先是Observer接口&#xff0c;定义了观察者需要实现的更新方法&…

【Claude 3】一文谈谈Anthropic(Claude) 亚马逊云科技(Bedrock)的因缘际会

文章目录 前言1. Anthropic的诞生2. Anthropic的“代表作”——Claude 3的“三驾马车”3. 亚马逊云科技介绍4. 强大的全托管服务平台——Amazon Bedrock5. 亚马逊云科技(AWS)和Anthropic的联系6. Claude 3模型与Bedrock托管平台的关系7. Clude 3限时体验入口分享【⚠️截止3月1…

[贰],万能开篇HelloWorld

1&#xff0c;新建项目 File/New/Project Android/Android Application Project 输入程序名字HelloWorld Next Next 选择Blank Activity 修改为HelloWorldActivity 2&#xff0c;异常点 No resource found that matches the given name Theme.AppCompat.Light import andro…