要学很简单 C/C++都能学 没有类
单纯想玩可以直接到最后下载放到桌面
目录
C++桌面程序 扫雷教学
创建项目
选择 windows 桌面向导 点击下一步(有桌面程序自带的主代码)
命名 选择 文件位置 点击创建
创建好后会有主代码(如果没有或想要自己创建 或者 想要了解是什么)
自建 以及每段的解释(VS给你创建的和这个会有些许出入但基本是这样)
#include<windows.h>
//窗口处理函数(自定义,处理消息)
LRESULT CALLBACK WindowProc(HWND hWnd, UINT msgID, WPARAM wParam, LPARAM lParam);
//入口函数
int WINAPI WinMain(_In_ HINSTANCE hIns, _In_opt_ HINSTANCE hpreIns, _In_ LPSTR IpCmdLine, _In_ int nCmdShow)
{
//窗口名(后面要一样防止出错提前定义)wchar_tz 字符串类型
wchar_t wdLpszClassName[] = L"窗口名";
//注册窗口类
//创建窗口类
WNDCLASS wc;
//申请缓冲区(两种不同的缓冲区)
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
//窗口背景色
wc.hbrBackground = CreateSolidBrush(RGB(255, 255, 255));
//光标(NULL 默认光标)
wc.hCursor = NULL; //LoadCursor(NULL, IDC_ARROW);
//图标(NULL 默认图标)
wc.hIcon = NULL; //LoadIcon(NULL, IDI_APPLICATION);
//给当前程序实例句柄(HINSTANCE 实例句柄类)
wc.hInstance = hIns;
//给窗口处理函数
wc.lpfnWndProc = WindowProc;
//窗口类名称
wc.lpszClassName = wdLpszClassName;
//窗口菜单(NULL 不要菜单)
wc.lpszMenuName = NULL;
//窗口风格 (窗口( 水平有变化 或 垂直有变化)时 重绘)
wc.style = CS_HREDRAW | CS_VREDRAW;
//将以上赋值全部写入操作系统
RegisterClass(&wc);
//在内存创建窗口(返回值窗口句柄)HWND 窗口句柄
HWND hwnd = CreateWindow(
wdLpszClassName, // 窗口名(要和 窗口类名称 相同)
L"标题栏信息", // 标题栏信息
WS_OVERLAPPEDWINDOW, // 窗口风格
CW_USEDEFAULT, // 位置 宽(可以用 整形 距离屏幕有多远的位置)
CW_USEDEFAULT, // 位置 高
CW_USEDEFAULT, // 大小 宽
CW_USEDEFAULT, // 大小 高
NULL, // 父窗口(NULL 没有父窗口)
NULL, // 菜单(NULL 没有)
hIns, // 当前程序实例句柄
NULL // 没什么用给 空就行了
);
//显示窗口(SW_SHOW 原样显示)
ShowWindow(hwnd, SW_SHOW);
//刷新窗口 不调也行(微软建议我们调)
UpdateWindow(hwnd);
//消息循环
//MSG 是一个 结构体(用来保存 GetMessageW 捕住消息 的 )
MSG msg;
while (GetMessageW(&msg, NULL, 0, 0))
{
//翻译消息
TranslateMessage(&msg);
//派发消息(将消息 交给 窗口处理函数 来处理)
DispatchMessage(&msg);
}
return 0;
}
//窗口处理函数(自定义,处理消息)
LRESULT CALLBACK WindowProc(HWND hWnd, UINT msgID, WPARAM wParam, LPARAM lParam)
{
switch(msgID)
{
case WM_DESTROY:
//退出程序(给消息循环返回 0 退出循环)
PostQuitMessage(0);
break;
}
return DefWindowProc(hWnd, msgID, wParam, lParam);
}
开始正式编辑
定义全局常量 和 变量
我们先不管什么桌面不桌面的 把他当成你学过的那种去做一个C++的扫雷逻辑
定义全局的常量 (这里给一下萌新 解释一下 全局常量 是指 整个代码块不能修改的数值)
我这里是成品 一开始 可以把 行 列 雷 设置成10 这样方便调试(找错)其他4个可以先不管
#define COL 20 //行 (高)
#define ROW 40 //列 (宽)
#define MINE 100 //雷
#define BMPX 20 //位图X
#define BMPY 20 //位图Y
#define SCREENX 120 // 距离窗口X
#define SCREENY 50 // 距离窗口Y
让后是全局变量 (同理 这是全局可一改变的量)
这里为什么加2 知道的人肯定已经想到了(不知道的看到后面就懂了)
map_x 和 map_y 是绘制的位置 加二是让其有空隙看到不会太挤
Mine 雷个数让其可以改变
MineNum 标记雷的个数
//地图大小
int Map[ROW + 2][COL + 2];
//遮罩层
int MapCover[ROW + 2][COL + 2];
//标记雷
int MapMine[ROW + 2][COL + 2];
int map_x = BMPX + 2;
int map_y = BMPY + 2;
int Mine = MINE;
int MineNum;
地图初始化
把上面三个二为数组初始化
for (int i = 0; i < ROW + 2; i++)
{
for (int j = 0; j < COL + 2; j++)
{
Map[i][j] = 0;
MapCover[i][j] = 0;
MapMine[i][j] = 0;
}
}
初始化雷 (雷 -1)
雷的位置不是固定的所以这里我们用随机数(可能有的人已经发现了 这里我们是伪随机)
把伪随机改为随机 只需要加一个时间值( srand((unsigned int)time(NULL)); )
这里是还加了一条判断 应为 随机值有概率随机到一样的 导致雷刷到同一个位置
//雷
for (int i = 0; i < MINE;)
{
int x_Mine = (rand() % ROW);
int y_Mine = (rand() % COL);
if (Map[x_Mine][y_Mine] != -1)
{
Map[x_Mine][y_Mine] = -1;
i++;
}
}
提示周围的有几个雷
先找到每一个雷的位置在这定义一个函数去处理
雷的周围 应该是
/*
(x+1 ,y - 1) (x+1 ,y)(x+1 ,y + 1)
(x , y-1) ( 0) ( x , y+1)
(x-1 , y+1) (x-1 , y) (x-1, y+1)
*/
看懂了这个因该就懂了
这里我们用一个循环去得到这九个位置(之前一个一个写麻烦还容易出错 哎~)
然后判断一下这九个位置只要不是雷就让它++ 这样我们就得到了雷周围的提示数据
容器上下多出2个共4个这里就体现出来了 如果雷在边上在判段的会就会保存 我们让他上下多两个就不会到边上了当仍方法有很多 也可以加些判段条件等等
void get_board1_count(int Map[ROW + 2][COL + 2], int x, int y);
//雷周围的
for (int i = 0; i < ROW + 2; i++)
{
for (int j = 0; j < COL + 2; j++)
{
if (Map[i][j] == -1)
{
get_board1_count(Map, i, j);
}
}
}
void get_board1_count(int Map[ROW + 2][COL + 2], int x, int y)
{
for (int i = x - 1; i <= x + 1; i++)
{
for (int j = y - 1; j <= y + 1; j++)
{
if (Map[i][j] != -1)
{
Map[i][j]++;
}
}
}
}
绘图
资源加载 (位图)
位图头文件 一般是 #include "Resource.h" 如果不是 看一下有没有多建资源文件
位图就是图片 视图>其他窗口>资源视图
点击添加资源
选择 Bitmap 文件 新建之后要自己编辑
如果有BMP图片文件就可以 导入(注意:一定是 BMP 的图片 导入后最好不要在动图片不然容易出错)
添加好后就可以看到 Bitmap 下的 就是你添加的图片
LoadBitmap(hInst, (wchar_t*)图片的ID); //加载图片资源(要添加图片)
在窗口上绘制图
现在开始接触窗口编程了 不会很难
如果有觉得难的(开始接触如果决的有些难得人一定义撑过去 万事开头难 我是在B站自学 开始的装软件 搞配置 找教学 真的难 但是搞好之后慢慢的就好起来了 如果有不懂 的可以评论告诉我我尽力帮你解答)
有了基础信息之后我们来绘制看看
消息函数都是在 switch 里调用
初始化消息
SetTimer(hWnd, 1, 1000, NULL); //创建一个定时器 让其每一秒开始检测一下有没有赢(感觉不应该放在这里 但也不清楚放哪感觉放哪都行 有清楚的可以评论一下)
//初始化
case WM_CREATE:
SetTimer(hWnd, 1, 1000, NULL);
CreateMap();
break;
定时器消息
消息处理(定时器消息都会到这里处理)
//定时器
case WM_TIMER:
{
TimeMine(hInst, hWnd);
}
break;
每秒检测是否胜利 可以把其他的看完再来看这个
Mine 雷都排完了 并且 排的雷个数和雷相同
KillTimer(hWnd, 1); // 结束定时器 (1 要结束的定时器 ID)
MessageBox(hWnd, L"失败", L"结束", MB_OK); //提示框 (第一个文本是 内容 第二个是标题)
Game(hInst, hWnd); //全部初始初始化
/检测是否胜利
void TimeMine(HINSTANCE hInst, HWND hWnd)
{
if (Mine == 0 && MineNum == MINE)
{
KillTimer(hWnd, 1);
MessageBox(hWnd, L"胜利", L"结束", MB_OK);
Game(hInst, hWnd);
}
}
游戏逻辑 (初始化)
InvalidateRect(hWnd, NULL, true); //地图重绘 (true 要删除 false 不删除)重绘
//游戏逻辑
void Game(HINSTANCE hInst, HWND hWnd)
{
InvalidateRect(hWnd, NULL, true);
Mine = MINE;
MineNum = 0;
CreateMap();
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
DrawMap(hInst, hdc);
EndPaint(hWnd, &ps);
}
WM_PAINT 这个是绘图消息要绘制的东西可以放到这里调用
(句柄你们暂时可以看作一个类型)
自定义定义一个函数 (把绘制代码放到函数里)
DrawMap(hInst, hdc);
case WM_PAINT:
{
//HDC 初始化 (HDC 是绘图句柄 要绘图就要用到)
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// 绘画的内容(自定义的要绘画就写在这里)
DrawMap(hInst, hdc);
//绘画完释放
EndPaint(hWnd, &ps);
}
这里定义了两个函数一个绘制地图 一个绘制遮罩层
void DrawMap(HINSTANCE hInst, HDC hdc)
{
printMap(hInst, hdc, BMPX, BMPY, SCREENX, SCREENY);
//printMap(hInst, hdc, 20, 20, 250,0);
OnLdownMap(hInst, hdc, BMPX, BMPY, SCREENX, SCREENY);
}
绘制地图
这里肯定有不少萌新需要头痛了这都是些什么呀
下面我给你们简单的讲解一下 (如果有需要 评论一下 我可以单独讲解一张)
函数参数
HINSTANCE hInst //当前程序实例句柄
HDC hdc //绘图句柄
x y //位图的小 (这是增加代码的复用性)
x1 y1 //距离窗口的位置 绘制位置
void printMap(HINSTANCE hInst, HDC hdc, int x, int y, int x1, int y1)
{
HBITMAP hBmp = LoadBitmap(hInst, (wchar_t*)IDB_BITMAP2);
HDC hdcMap = CreateCompatibleDC(hdc);
HGDIOBJ hPenDC = SelectObject(hdcMap, hBmp);
for (int i = 0; i < ROW; i++)
{
for (int j = 0; j < COL; j++)
{
if (Map[i][j] == -1)
{
TextOut(hdc, x1 + (i * map_x), y1 + (j * map_y), L"雷", 1);
}
else
{
BitBlt(hdc, x1 + (i * map_x), y1 + (j * map_y), x, y, hdcMap, x * Map[i][j] + Map[i][j], 0, SRCCOPY);
}
}
}
HBITMAP hBmp = LoadBitmap(hInst, (wchar_t*)IDB_BITMAP2); //加载图片资源(要添加图片)IDB_BITMAP2 (图片的ID)
HDC hdcMap = CreateCompatibleDC(hdc); // 构建一个虚拟区域 放到一个绘图句柄中
HGDIOBJ hPenDC = SelectObject(hdcMap, hBmp); // 把位图 给 虚拟区域 让他在虚拟区域绘制 (返回原先的 绘图对象)
//中间是自由绘图 (可以根据实际情况自行调整)
TextOut //文本绘制 (绘制文字)
(hdc, //绘图句柄
x1 + (i * map_x), // 距离窗口X 坐标 (X 左右)
y1 + (j * map_y), // 距离窗口Y 坐标(Y 上下)
L"雷", // 要打印的文本
1 //文本的大小
);
BitBlt // 图片打印
(
hdc, // 窗口句柄
x1 + (i * map_x), y1 + (j * map_y), // 距离窗口X Y 坐标
x, y, //图片的大小 X Y
hdcMap, //你创建的虚拟区域绘图句柄
x * Map[i][j] + Map[i][j], 0, // 从虚拟区域 的 什么位置开始绘制(就是图片的什么位置)
SRCCOPY //成像方法 (这里这个是原样成像)
);
SelectObject(hdcMap, hPenDC); //用返回的原先对象把虚拟区域的位图拿走
DeleteObject(hBmp); //位图释放
DeleteDC(hdcMap); //虚拟区域释放
雷没有绘图的原因没有美术的天赋好难 还是做两个框框容易
鼠标点击事件
鼠标事件讲解
附带信息:
wParam 其他按键的状态(Ctrl Shift …)
lParam 鼠标的位置、窗口客户区坐标系
LOWORD(lParam) X 坐标位置 返回值(short)
HIWORD(lParam) Y 坐标位置
双击消息://使用时需要在注册窗口类的时候添加 CS_DBLCLKS 风格(在注册窗口类里找到 wcex.style 后面加上 CS_DBLCLKS ( | 添加不同效果 有点类似于 || 或者))
//鼠标左键按下
case WM_LBUTTONDOWN:
OnLdown(hInst, lParam, hWnd, g_hOutput);
break;
//鼠标右键双击
case WM_RBUTTONDBLCLK:
TabMine(hInst, lParam, hWnd, g_hOutput);
break;
//鼠标右键按下
case WM_RBUTTONDOWN:
OnRdown(hInst, lParam, hWnd, g_hOutput);
break;
//鼠标右键松开
case WM_RBUTTONUP:
OnRup(hInst, lParam, hWnd, g_hOutput);
break;
鼠标左键按下
左键按下我们然他 按下位置显示处理 是零的话就让他延申
//也是初始化(绘图消息外绘图用)
HDC hdc = GetDC(hWnd);
ReleaseDC(hWnd, hdc);
void OnLdown(HINSTANCE hInst, LPARAM lParam, HWND hWnd, HANDLE g_hOutput)
{
HDC hdc = GetDC(hWnd);
//TCHAR szText[256];
HBITMAP hBmp = LoadBitmap(hInst, (wchar_t*)IDB_BITMAP2);
HDC hdcMap = CreateCompatibleDC(hdc);
HGDIOBJ hPenDC = SelectObject(hdcMap, hBmp);
for (int i = 0; i < ROW + 2; i++)
{
for (int j = 0; j < COL + 2; j++)
{
if (LOWORD(lParam) >= SCREENX + (i * map_x) && LOWORD(lParam) <= (SCREENX + (i * map_x)) + BMPX
&& HIWORD(lParam) >= SCREENY + (j * map_y) && HIWORD(lParam) <= (SCREENY + (j * map_y)) + BMPY && MapCover[i][j] == 0)
{
if (Map[i][j] == -1)
{
KillTimer(hWnd, 1);
printMap(hInst, hdc, BMPX, BMPY, SCREENX, SCREENY);
MessageBox(hWnd, L"失败", L"结束", MB_OK);
Game(hInst, hWnd);
/*CreateMap();
DrawMap(hInst, hdc);*/
}
else if (Map[i][j] >= 0)
{
MapCover[i][j] = 1;
MapCoverDown(hdc, hdcMap, i, j, SCREENX, SCREENY);
}
}
}
}
SelectObject(hdcMap, hPenDC);
DeleteObject(hBmp);
DeleteDC(hdcMap);
ReleaseDC(hWnd, hdc);
}
if 判断的是图片位置区间 并且 是没有打开的图不能
if (Map[i][j] == -1) // 点击的位置是雷的话我们要做的
KillTimer(hWnd, 1); // 结束定时器
printMap(hInst, hdc, BMPX, BMPY, SCREENX, SCREENY); //之前写的 绘图 函数
MessageBox(hWnd, L"失败", L"结束", MB_OK); //提示框
else if (Map[i][j] >= 0) // 点击的位置不是雷的话我们要做的
MapCover[i][j] = 1; // 让遮罩层赋值1(之后1 就是打开)
MapCoverDown(hdc, hdcMap, i, j, SCREENX, SCREENY); //递归函数(自定义)
注意: 一定要设置不能超过行高(我这里就忘了导致搞了1个多小时 才发现我们有4行初始化是隐藏掉的)
void MapCoverDown(HDC hdc, HDC hdcMap, int x, int y, int x1, int y1)
{
BitBlt(hdc, x1 + (x * map_x), y1 + (y * map_y), BMPX, BMPY, hdcMap, BMPX * Map[x][y] + Map[x][y], 0, SRCCOPY);
if (Map[x][y] == 0)
{
for (int i = x - 1; i <= x + 1; i++)
{
for (int j = y - 1; j <= y + 1; j++)
{
//只有上下左右(有可能只有斜角是零)
if ((i == x && j != y) || (j == y && i != x))
{
把遮罩层打开(应为递归的时候是 再次进入是根据地图大小来的 所以要限制一下递归的 x y)
if ((i >= 0 && i < ROW && j >= 0 && j < COL) && MapCover[i][j] == 0)
{
MapCover[i][j] = 1;
BitBlt(hdc, x1 + (i * map_x), y1 + (j * map_y), BMPX, BMPY, hdcMap, BMPX * Map[i][j] + Map[i][j], 0, SRCCOPY);
MapCoverDown(hdc, hdcMap, i, j, x1, y1);
}
}
}
}
}
}
右键双击标记雷
标记雷的位置
没有标记的话 就标记 MapMine[i][j] = 1; 标记的个数+1 MineNum++;
标记的话 就取消标记 MapMine[i][j] = 0; 标记的个数-1 MineNum--;
void TabMine(HINSTANCE hInst, LPARAM lParam, HWND hWnd, HANDLE g_hOutput)
{
HDC hdc = GetDC(hWnd);
HBITMAP hBmp = LoadBitmap(hInst, (wchar_t*)IDB_BITMAP3);
HDC hdcCover = CreateCompatibleDC(hdc);
HGDIOBJ hPenDC = SelectObject(hdcCover, hBmp);
for (int i = 0; i < ROW + 2; i++)
{
for (int j = 0; j < COL + 2; j++)
{
if (LOWORD(lParam) >= SCREENX + (i * map_x) && LOWORD(lParam) <= (SCREENX + (i * map_x)) + BMPX
&& HIWORD(lParam) >= SCREENY + (j * map_y) && HIWORD(lParam) <= (SCREENY + (j * map_y)) + BMPY && MapCover[i][j] == 0)
{
if (MapMine[i][j] != 1)
{
MineNum++;
MapMine[i][j] = 1;
BitBlt(hdc, SCREENX + (i * map_x), SCREENY + (j * map_y), BMPX, BMPY, hdcCover, 21, 0, SRCCOPY);
if (Map[i][j] == -1)
{
Mine--;
}
}
else if (MapMine[i][j] == 1)
{
MineNum--;
MapMine[i][j] = 0;
BitBlt(hdc, SCREENX + (i * map_x), SCREENY + (j * map_y), BMPX, BMPY, hdcCover, 0, 0, SRCCOPY);
if (Map[i][j] == -1)
{
Mine++;
}
}
}
}
}
SelectObject(hdcCover, hPenDC);
DeleteObject(hBmp);
DeleteDC(hdcCover);
ReleaseDC(hWnd, hdc);
}
右键按下
让他打印周围提示框
//右键按下
void OnRdown(HINSTANCE hInst, LPARAM lParam, HWND hWnd, HANDLE g_hOutput)
{
HDC hdc = GetDC(hWnd);
HBITMAP hBmp = LoadBitmap(hInst, (wchar_t*)IDB_BITMAP3);
HDC hdcCover = CreateCompatibleDC(hdc);
HGDIOBJ hPenDC = SelectObject(hdcCover, hBmp);
for (int i = 0; i < ROW + 2; i++)
{
for (int j = 0; j < COL + 2; j++)
{
if (LOWORD(lParam) >= SCREENX + (i * map_x) && LOWORD(lParam) <= (SCREENX + (i * map_x)) + BMPX
&& HIWORD(lParam) >= SCREENY + (j * map_y) && HIWORD(lParam) <= (SCREENY + (j * map_y)) + BMPY
&& MapCover[i][j] == 1 && Map[i][j] != 0)
{
pointMine(hInst,hdc, hdcCover, i, j);
}
}
}
SelectObject(hdcCover, hPenDC);
DeleteObject(hBmp);
DeleteDC(hdcCover);
ReleaseDC(hWnd, hdc);
}
void pointMine(HINSTANCE hInst, HDC hdc, HDC hdcCover, int x, int y)
{
for (int i = x - 1; i <= x + 1; i++)
{
for (int j = y - 1; j <= y + 1; j++)
{
if ((i >= 0 && i < ROW && j >= 0 && j < COL) && MapCover[i][j] == 0 && MapMine[i][j] != 1)
{
BitBlt(hdc, SCREENX + (i * map_x), SCREENY + (j * map_y), BMPX, BMPY, hdcCover, 42, 0, SRCCOPY);
}
}
}
}
右键松开
周围变回去
//右键松开
void OnRup(HINSTANCE hInst, LPARAM lParam, HWND hWnd, HANDLE g_hOutput)
{
HDC hdc = GetDC(hWnd);
HBITMAP hBmp = LoadBitmap(hInst, (wchar_t*)IDB_BITMAP3);
HDC hdcCover = CreateCompatibleDC(hdc);
HGDIOBJ hPenDC = SelectObject(hdcCover, hBmp);
for (int i = 0; i < ROW + 2; i++)
{
for (int j = 0; j < COL + 2; j++)
{
if (LOWORD(lParam) >= SCREENX + (i * map_x) && LOWORD(lParam) <= (SCREENX + (i * map_x)) + BMPX
&& HIWORD(lParam) >= SCREENY + (j * map_y) && HIWORD(lParam) <= (SCREENY + (j * map_y)) + BMPY
&& MapCover[i][j] == 1 && Map[i][j] != 0)
{
pointMine_f(hInst, hdc, hdcCover, i, j);
}
}
}
SelectObject(hdcCover, hPenDC);
DeleteObject(hBmp);
DeleteDC(hdcCover);
ReleaseDC(hWnd, hdc);
}
//右键松开提示
void pointMine_f(HINSTANCE hInst, HDC hdc, HDC hdcCover, int x, int y)
{
for (int i = x - 1; i <= x + 1; i++)
{
for (int j = y - 1; j <= y + 1; j++)
{
if ((i >= 0 && i < ROW && j >= 0 && j < COL) && MapCover[i][j] == 0 && MapMine[i][j] != 1)
{
BitBlt(hdc, SCREENX + (i * map_x), SCREENY + (j * map_y), BMPX, BMPY, hdcCover, 0, 0, SRCCOPY);
}
}
}
}
//定时器
case WM_TIMER:
{
TimeMine(hInst, hWnd);
}
break;
成品演示代码下载
下载方式
代码位置 (可以下载下去玩玩扫雷也不错)
游戏开发练习: 制作或复刻些许小游戏 - Gitee.com
下载方式 (把扫雷2.0文件下下来)
下完后找到 x64/Debug 文件点进去
快捷方式(把快捷方式放到桌面就可以玩了)
成品游玩视频
双击标雷
20221114-201425
第一次写这么长文章的有些不好的地方多见谅
我是自学有些不好不对的还请多多建议